Angular 如何将羽毛笔编辑器与插入自定义MatFormFieldControl的角度材质集成

Angular 如何将羽毛笔编辑器与插入自定义MatFormFieldControl的角度材质集成,angular,angular-material,quill,Angular,Angular Material,Quill,我正在尝试将羽毛笔编辑器与角材质集成到自定义MatFormFieldControl中: 您可以在此处看到工作演示(请参见“说明”字段): 以下是创建自定义MatFormFieldControl的指南: 这是一种工作,但不是它应该是。 例如(转到空表单:): 如果单击“标题”字段,则该字段仍然有效,如果在字段外再次单击,则该字段将变为无效。但如果您单击“描述”,它将立即失效 在quill-material.component.ts方法writeValue()中,我将避免执行类似this.edito

我正在尝试将羽毛笔编辑器与角材质集成到自定义MatFormFieldControl中:

您可以在此处看到工作演示(请参见“说明”字段):

以下是创建自定义MatFormFieldControl的指南:

这是一种工作,但不是它应该是。 例如(转到空表单:):

  • 如果单击“标题”字段,则该字段仍然有效,如果在字段外再次单击,则该字段将变为无效。但如果您单击“描述”,它将立即失效
  • 在quill-material.component.ts方法writeValue()中,我将避免执行类似
    this.editor.root.innerHTML=contents的操作
    将初始内容写入编辑器,但我不知道如何执行此操作
  • 有人能帮我改进一下纬管材料吗? 如果您想使用我的github代码,请随意签出它


    非常感谢

    如果您能帮助他人,这是组件的源代码,在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;
      }
    }