Angular 如何将羽毛笔编辑器与插入自定义MatFormFieldControl的角度材质集成
我正在尝试将羽毛笔编辑器与角材质集成到自定义MatFormFieldControl中: 您可以在此处看到工作演示(请参见“说明”字段): 以下是创建自定义MatFormFieldControl的指南: 这是一种工作,但不是它应该是。 例如(转到空表单:):Angular 如何将羽毛笔编辑器与插入自定义MatFormFieldControl的角度材质集成,angular,angular-material,quill,Angular,Angular Material,Quill,我正在尝试将羽毛笔编辑器与角材质集成到自定义MatFormFieldControl中: 您可以在此处看到工作演示(请参见“说明”字段): 以下是创建自定义MatFormFieldControl的指南: 这是一种工作,但不是它应该是。 例如(转到空表单:): 如果单击“标题”字段,则该字段仍然有效,如果在字段外再次单击,则该字段将变为无效。但如果您单击“描述”,它将立即失效 在quill-material.component.ts方法writeValue()中,我将避免执行类似this.edito
this.editor.root.innerHTML=contents的操作
将初始内容写入编辑器,但我不知道如何执行此操作非常感谢如果您能帮助他人,这是组件的源代码,在Angular 9上测试。 我将其命名为羽毛材质,因此您可以像这样使用它:
<mat-form-field>
<quill-material
formControlName="your_control_name"
placeholder="Type your text here..."
required
></quill-material>
<mat-error *ngIf="formGroup.get('your_control_name').hasError('required')">
Field is <strong>required</strong>
</mat-error>
</mat-form-field>
字段是必需的
导入{
组成部分,
输入,
奥尼特,
ElementRef,
ViewChild,
forwardRef,
OnDestroy,
注射器,
多切克,
主机绑定
}从“@angular/core”开始;
从“@angular/forms”导入{NG_VALUE_访问器,ControlValueAccessor,NgControl};
从“@angular/material”导入{MatFormFieldControl};
从'rxjs'导入{Subject};
从'@angular/cdk/a11y'导入{FocusMonitor};
从“@angular/cdk/concurvation”导入{concurveboleanproperty};
从“纬管”进口纬管;
从'quill delta to html'导入{QuillDeltaToHtmlConverter};
常量选择器=‘纬管材料’;
@组成部分({
选择器:选择器,
模板:`
`,
样式:[`img{
位置:相对位置;
}`],
供应商:[{
提供:NG_值访问器,
useExisting:forwardRef(()=>QuillMaterialComponent),
多:真的
},
{
提供:MatFormFieldControl,
使用现有:纬管材料组件
}],
主持人:{
“[id]”:“id”,
“[attr.aria descripbedby]”:“descripbedby”
}
})
导出类QuillMaterialComponent实现OnInit、DoCheck、OnDestroy、ControlValueAccessor、MatFormFieldControl{
静态nextId=0;
@HostBinding()id=`quill material-${QuillMaterialComponent.nextId++}`;
@ViewChild('container',{read:ElementRef,static:true})container:ElementRef;
stateChanges=新主题();
羽毛笔:任何=羽毛笔;
艺术经纬:有;
controlType=‘纬管材料’;
errorState=false;
对照组:任何;
触摸=假;
聚焦=假;
_价值:任何;
获取值():任意{
返回此值;
}
设定值(值){
此值为._值=值;
this.editor.setContents(this.\u值);
这个。onChange(值);
this.stateChanges.next();
}
@输入()
获取占位符(){
返回此占位符。\u;
}
设置占位符(plh){
这。_占位符=plh;
this.stateChanges.next();
}
公共占位符:字符串;
@输入()
获取必需的(){
返回此文件。_需要时;
}
需要设置(req){
此._required=强制执行一个属性(req);
this.stateChanges.next();
}
public _required=false;
@输入()
禁用(){
返回此项。\u已禁用;
}
设置为禁用(已禁用){
此._disabled=强制BoleAnProperty(已禁用);
this.stateChanges.next();
}
public _disabled=false;
变空{
const text=this.editor.getText().trim();
返回文本?false:true;
}
@主机绑定('class.floating')
获取shouldLabelFloat(){
返回this.focused | | |!this.empty;
}
@HostBinding('attr.aria descripeby')descripeby='';
SetDescripteById(ID:string[]){
this.descripeby=id.join(“”);
}
构造函数(公共elRef:ElementRef,公共注入器:注入器,公共fm:FocusMonitor){
fm.monitor(elRef.nativeElement,true).subscribe(origin=>{
这个。聚焦=!!原点;
this.stateChanges.next();
});
}
ngOnInit():void{
//避免循环依赖
this.ngControl=this.injector.get(ngControl);
如果(this.ngControl!=null){this.ngControl.valueAccessor=this;}
const editorRef=this.container.nativeElement.querySelector('.editor');
this.editor=newquill(editorRef,{theme:'snow'});
this.editor.on('text-change',()=>{
如果(此.ngControl.toucted){
this.onChange(this.getValue());
}
});
}
ngDoCheck():void{
如果(此.ngControl){
this.errorState=this.ngControl.invalid&&this.ngControl.toucted&&this.focused;
this.stateChanges.next();
}
}
恩贡德斯特罗(){
this.stateChanges.complete();
this.fm.stopMonitoring(this.elRef.nativeElement);
}
writeValue(内容:任意):无效{
if(this.editor&&contents){
const delta=this.editor.clipboard.convert(contents);//将html转换为delta
this.editor.setContents(delta);
该值=内容;
}
}
onChange=(delta:any)=>{};
registerOnChange(fn:(v:any)=>void:void{
this.onChange=fn;
}
onTouched=()=>{};
注册表项(fn:()=>void):void{
this.ontoched=fn;
}
onContainerClick(事件:MouseeEvent){
如果(!this.focused){
this.editor.focus();
这是真的;
this.stateChanges.next();
}
}
private getValue():任意|未定义{
如果(!this.editor){
返回未定义;
}
const delta:any=this.editor.getContents();
如果(此为空(增量)){
返回未定义;
}
const converter=新的QuillDeltaToHtmlConverter(delta.ops,{});
const html=converter.convert();
返回html;
}
private isEmpty(内容:任意):布尔值{
如果(contents.ops.length>1){
返回false;
}
常量opsTypes:Array=Object.keys(contents.ops[0]);
如果(opsTypes.length>1){
返回false;
}
如果(opsTypes[0]!=='插入
import {
Component,
Input,
OnInit,
ElementRef,
ViewChild,
forwardRef,
OnDestroy,
Injector,
DoCheck,
HostBinding
} from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor, NgControl } from '@angular/forms';
import { MatFormFieldControl } from '@angular/material';
import { Subject } from 'rxjs';
import { FocusMonitor } from '@angular/cdk/a11y';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import Quill from 'quill';
import { QuillDeltaToHtmlConverter } from 'quill-delta-to-html';
const SELECTOR = 'quill-material';
@Component({
selector: SELECTOR,
template: `<div class="quill-material-container" #container>
<div class="editor" (click)="onTouched()" [ngStyle]="{'height': '200px'}"></div>
</div>`,
styles: [`img {
position: relative;
}`],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => QuillMaterialComponent),
multi: true
},
{
provide: MatFormFieldControl,
useExisting: QuillMaterialComponent
}],
host: {
'[id]': 'id',
'[attr.aria-describedby]': 'describedBy'
}
})
export class QuillMaterialComponent implements OnInit, DoCheck, OnDestroy, ControlValueAccessor, MatFormFieldControl<any> {
static nextId = 0;
@HostBinding() id = `quill-material-${QuillMaterialComponent.nextId++}`;
@ViewChild('container', { read: ElementRef, static: true }) container: ElementRef;
stateChanges = new Subject<void>();
quill: any = Quill;
editor: any;
controlType = 'quill-material';
errorState = false;
ngControl: any;
touched = false;
focused = false;
_value: any;
get value(): any {
return this._value;
}
set value(value) {
this._value = value;
this.editor.setContents(this._value);
this.onChange(value);
this.stateChanges.next();
}
@Input()
get placeholder() {
return this._placeholder;
}
set placeholder(plh) {
this._placeholder = plh;
this.stateChanges.next();
}
public _placeholder: string;
@Input()
get required() {
return this._required;
}
set required(req) {
this._required = coerceBooleanProperty(req);
this.stateChanges.next();
}
public _required = false;
@Input()
get disabled() {
return this._disabled;
}
set disabled(disabled) {
this._disabled = coerceBooleanProperty(disabled);
this.stateChanges.next();
}
public _disabled = false;
get empty() {
const text = this.editor.getText().trim();
return text ? false : true;
}
@HostBinding('class.floating')
get shouldLabelFloat() {
return this.focused || !this.empty;
}
@HostBinding('attr.aria-describedby') describedBy = '';
setDescribedByIds(ids: string[]) {
this.describedBy = ids.join(' ');
}
constructor(public elRef: ElementRef, public injector: Injector, public fm: FocusMonitor) {
fm.monitor(elRef.nativeElement, true).subscribe(origin => {
this.focused = !!origin;
this.stateChanges.next();
});
}
ngOnInit(): void {
// avoid Cyclic Dependency
this.ngControl = this.injector.get(NgControl);
if (this.ngControl != null) { this.ngControl.valueAccessor = this; }
const editorRef = this.container.nativeElement.querySelector('.editor');
this.editor = new Quill(editorRef, { theme: 'snow' });
this.editor.on('text-change', () => {
if (this.ngControl.touched) {
this.onChange(this.getValue());
}
});
}
ngDoCheck(): void {
if (this.ngControl) {
this.errorState = this.ngControl.invalid && this.ngControl.touched && !this.focused;
this.stateChanges.next();
}
}
ngOnDestroy() {
this.stateChanges.complete();
this.fm.stopMonitoring(this.elRef.nativeElement);
}
writeValue(contents: any): void {
if (this.editor && contents) {
const delta = this.editor.clipboard.convert(contents); // convert html to delta
this.editor.setContents(delta);
this._value = contents;
}
}
onChange = (delta: any) => { };
registerOnChange(fn: (v: any) => void): void {
this.onChange = fn;
}
onTouched = () => { };
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
onContainerClick(event: MouseEvent) {
if (!this.focused) {
this.editor.focus();
this.focused = true;
this.stateChanges.next();
}
}
private getValue(): any | undefined {
if (!this.editor) {
return undefined;
}
const delta: any = this.editor.getContents();
if (this.isEmpty(delta)) {
return undefined;
}
const converter = new QuillDeltaToHtmlConverter(delta.ops, {});
const html = converter.convert();
return html;
}
private isEmpty(contents: any): boolean {
if (contents.ops.length > 1) {
return false;
}
const opsTypes: Array<string> = Object.keys(contents.ops[0]);
if (opsTypes.length > 1) {
return false;
}
if (opsTypes[0] !== 'insert') {
return false;
}
if (contents.ops[0].insert !== '\n') {
return false;
}
return true;
}
}