如何将 mat-error 动态添加到 mat-input-field?

San*_*_36 5 typescript angular-material angular

我想通过将 < mat-error > 动态添加到 DOM 来在用户超过 maxLength 时显示错误。

我已经有一个属性指令来限制输入字段的最大长度。我把它作为一个指令,因为它被应用于项目中不同文件的许多输入字段。但现在的问题是,当用户超过限制时,我必须显示一个mat-error。我不想自己在所有文件的每个输入字段下添加 < mat-error > ,我想要一个模块化的解决方案。这可以通过使用现有指令本身来完成吗?

<mat-form-field floatLabel="auto">
      <input [formControl]="displayNameControl"
        mongoIndexLimit
        [charLength]="charLength"
        matInput
        name="displayName"
        placeholder="Stack Name"
        autocomplete="off"
        required />
    </mat-form-field>
Run Code Online (Sandbox Code Playgroud)

这是我的指示

import { Directive, OnInit, NgModule, ElementRef, OnChanges, Input, SimpleChanges, Renderer2 } from '@angular/core';

@Directive({
  selector: '[mongoIndexLimit]'
})
export class MongoIndexLimitDirective implements OnInit, OnChanges {
  @Input() public charLength?: number;
  private maxLength = 5;
  constructor(
    private el: ElementRef<HTMLElement>,
    private renderer: Renderer2
  ) { }

  public ngOnInit() {
    this.el.nativeElement.setAttribute('maxLength', this.maxLength.toString());
  }

  public ngOnChanges(changes: SimpleChanges) {
    if (changes.charLength.currentValue >= 5) {
      const child = document.createElement('mat-error');
      this.renderer.appendChild(this.el.nativeElement.parentElement.parentElement.parentElement, child);
    }
  }

}
Run Code Online (Sandbox Code Playgroud)

当我尝试上述操作时,我能够将 < mat-error > 元素附加到 DOM,但 angular 不会将其视为编译器 < mat-error > 角度材料。它只是一个虚拟的 < mat-error > 而不是材料组件。

我希望结果是设置了 maxLength 和动态生成的 mat-error 的输入组件,该错误显示何时超出限制,就像下面的示例一样。

https://material.angular.io/components/input/examples (标题为带有自定义错误状态匹配器的输入)

对不起,我的英语不好 。

Eli*_*seo 7

当然,您可以动态地添加一个 mat-error。NetBasal 中有一篇关于此的精彩文章

我制作的一个简单版本是在stackblitz 中。在此 stackblitz 中,我将指令附加到 mat-form-field 并进行变通以附加新组件 mat-error-component。这允许我使用 css 和动画。

关键是使用 ViewContainerRef 使用 ComponentFactoryResolver 动态添加组件

好吧,指令的代码:

export class MongoIndexLimitDirective implements AfterViewInit {
  ref: ComponentRef<MatErrorComponent>;
  constructor(
    private vcr: ViewContainerRef,
    private resolver: ComponentFactoryResolver,
    private formField:MatFormField
  ) { }

  public ngAfterViewInit()
  {
    this.formField._control.ngControl.statusChanges.subscribe(res=>this.onChange(res))

  }

  public onChange(res) {
    if (this.formField._control.ngControl.invalid)
    {
      this.setError('error')
    }      
    else
      this.setError('')
  }
  setError(text: string) {
    if (!this.ref) {
     const factory = this.resolver.resolveComponentFactory(MatErrorComponent);
     this.formField._elementRef
     this.ref = this.vcr.createComponent(factory);
   }
   this.ref.instance.error=text;
}
Run Code Online (Sandbox Code Playgroud)

MatErrorComponent(为了方便起见,我称之为它。小心你需要放入主模块的 entryComponents)看起来比实际更复杂,因为“动画”,但本质上是一个 <mat-error>{{message}}</mat-error>

@Component({
  selector: 'custom-error',
  template:`
  <div [@animation]="_state" style="margin-top:-1rem;font-size:.75rem">
      <mat-error >
      {{message}}
    </mat-error>
    </div>
  `,
  animations: [
    trigger('animation', [
      state('show', style({
        opacity: 1,
      })),
      state('hide',   style({
        opacity: 0,
        transform: 'translateY(-1rem)'
      })),
      transition('show => hide', animate('200ms ease-out')),
      transition('* => show', animate('200ms ease-in'))

    ]),
  ]

})
export class MatErrorComponent{
  _error:any
  _state:any
  message;

  @Input() 
  set error(value)
  {
    if (value && !this.message)
    {
      this.message=value;
      this._state='hide'
      setTimeout(()=>
      {
        this._state='show'
      })
    }
    else{
    this._error=value;
    this._state=value?'show':'hide'
    }
  }
Run Code Online (Sandbox Code Playgroud)

更新了 mat-error-component 的更好方法。

我们可以考虑不同的错误并改进过渡,例如

@Component({
  selector: '[custom-error]',
  template: `
  <div [@animation]="increment" *ngIf="show" style="margin-top:-1rem;font-size:.75rem">
      <mat-error >
      {{message}}
    </mat-error>
    </div>
  `,
  animations: [
    trigger('animation', [
      transition(':increment', [
        style({ opacity: 0}),
        animate('200ms ease-in', style({ opacity: 1 })),
      ]),
      transition(':enter', [
        style({ opacity: 0, transform: 'translateY(-1rem)' }),
        animate('200ms ease-in', style({ opacity: 1, transform: 'translateY(0)' })),
      ]),
      transition(':leave', [
        animate('200ms ease-out', style({ opacity: 0, transform: 'translateY(-1rem)' }))
      ])])
  ]

})
export class MatErrorComponent {
  show: boolean
  message: string;
  increment:number=0;

  @Input()
  set error(value) {
    if (value)
    {
      if (this.message!=value)
        this.increment++;

      this.message = value;
    }

    this.show = value ? true : false;
  }
} 
Run Code Online (Sandbox Code Playgroud)

这允许当消息错误改变时,一个新的动画发生 - 在这种情况下,如果将不透明度从 0 更改为 1,例如在我们的指令中将函数 onChange 更改为

  public onChange(res) {
    if (this.control.invalid)
    {
      if (this.control.errors.required)
        this.setError(this.formField._control.placeholder+' required')
      else
        this.setError(this.formField._control.placeholder+' incorrect')
    }      
    else
      this.setError('')
  }
Run Code Online (Sandbox Code Playgroud)

查看改进堆栈闪电战

更新 2 存在模糊问题。如果最初控件无效,状态不会改变,因此我们需要添加模糊事件。为此,我们使用 renderer2 和 ViewContent 来获取输入

@ContentChild(MatInput,{read:ElementRef}) controlElementRef:ElementRef
Run Code Online (Sandbox Code Playgroud)

并更改 ngAfterViewInit

public ngAfterViewInit()
  {
    this.control=this.formField._control.ngControl;
    this.renderer.listen(this.controlElementRef.nativeElement,'blur',()=>this.onChange(null))
    this.control.statusChanges.subscribe(res=>this.onChange(res))

  }
Run Code Online (Sandbox Code Playgroud)

stackblitz 考虑到“模糊”

如果可以的话,我们可以有一个预定义的错误,最后在自定义错误中添加一个“错误”,这样如果我们的自定义错误返回一些类似{error:'error text'}的错误,我们就可以显示错误。

重要的部分是

export const defaultErrors = {
  minlength: ({ requiredLength, actualLength }) =>
    `Expect ${requiredLength} but got ${actualLength}`,
  email: error=>'The email is incorrect',
  error:error=>error,
  required: error => `This field is required`

};
Run Code Online (Sandbox Code Playgroud)

并且 OnChnage 变得像

public onChange(res) {
    if (this.control.invalid && this.control.touched) {
      let error: string = this.formField._control.placeholder + " incorrect";
      Object.keys(defaultErrors).forEach(k => {
        console.log(k,this.control.hasError(k),this.control.errors[k])
        if (this.control.hasError(k)) error = defaultErrors[k](this.control.errors[k]);
      });
      this.setError(error);
    } else this.setError("");
  }
Run Code Online (Sandbox Code Playgroud)

  • 这不使用“&lt;mat-error&gt;”,而是一个自定义的自我维护的错误组件,它不会自动应用 Angular Material 提供的可访问性。您最好在模板中为每个表单控件内联 `&lt;mat-error&gt;`,并从 Netanel Basal 文章中提供的类似注入器动态生成消息,这对您来说需要更多工作,但可用性更好对于用户来说。 (3认同)