为什么Angular没有显示字符串[]的值

时间:2017-08-15 13:22:23

标签: angular

我在这里创建了一个Plunker - https://plnkr.co/edit/1Aztv5K2gqIc4erNgRnG?p=preview

TL; DR 要查看问题,您应该打开开发人员工具窗口并查看控制台日志输出。然后转到<input>框并重复添加一些文本然后清除它。您将看到errors输出到控制台的值,但这些错误永远不会出现在组件的视图中。

我编写了一个实现NG_VALUE_ACCESSOR的自定义组件来为我的控件提供数据绑定上下文,并NG_VALIDATORS这样我就可以访问正在验证的数据源(AbstractControl)。

我无法理解为什么我的组件模板中的*ngFor没有通过查看console.log输出列出我能看到的错误。

import {Component, NgModule, forwardRef} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import {
  AbstractControl, ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors,
  Validator
} from '@angular/forms';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import 'rxjs/add/operator/distinctUntilChanged';

@Component({
  selector: 'validation-errors',
  template: `
    <div>Errors should appear below this line</div>
    <div *ngFor='let item of errors'>{{ item }}!!!</div>
    `,
  providers: [
    {
      provide: NG_VALIDATORS,
      useClass: forwardRef(() => ValidationErrorsComponent),
      multi: true
    },
    {
      provide: NG_VALUE_ACCESSOR,
      useClass: forwardRef(() => ValidationErrorsComponent),
      multi: true
    }
  ]
})
export class ValidationErrorsComponent implements Validator, ControlValueAccessor  {
  public errors: string[] = [];

  private control: AbstractControl = null;

  private updateErrors(errorsObject: any) {
    this.errors = [];
    if (errorsObject) {
      for (const errorType of Object.keys(errorsObject)) {
        this.errors.push(errorType);
      }
    }
    console.log('Errors: ' + JSON.stringify(this.errors));
  }

  validate(c: AbstractControl): ValidationErrors | any {
    if (this.control === null && c !== null && c !== undefined) {
      this.control = c;
      this.control.statusChanges
        .distinctUntilChanged()
        .subscribe(x => this.updateErrors(this.control.errors));
    }
  }

  writeValue(obj: any): void {
  }

  registerOnChange(fn: any): void {
  }

  registerOnTouched(fn: any): void {
  }

}

它似乎不是一个更改检测问题,因为我也尝试使用Observables和异步管道来实现它,但它仍然没有显示任何错误。

消费者代码如下所示

//our root app component
import {Component, NgModule, forwardRef} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import {
  AbstractControl, ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors,
  Validator
} from '@angular/forms';
import { ValidationErrorsComponent } from './validation-errors.component';

@Component({
  selector: 'my-app',
  template: `
    <div [formGroup]='form'>
      <input formControlName='name' style="width: 100%"/> 
      <validation-errors formControlName='name'></validation-errors>
    </div>
  `,
})
export class App {
  public form: FormGroup;

  constructor(formBuilder: FormBuilder) {
    this.form = formBuilder.group({
      name: ['Delete this text to trigger required vaidation error', Validators.required]
    })
  }
}

@NgModule({
  imports: [ BrowserModule, FormsModule, ReactiveFormsModule ],
  exports: [    
    FormsModule,
    ReactiveFormsModule,
    ValidationErrorsComponent
  ],
  declarations: [ App, ValidationErrorsComponent ],
  bootstrap: [ App ]
})
export class AppModule {}

1 个答案:

答案 0 :(得分:1)

更新:问题是正在创建3个组件实例。一个用于模板中的标记,一个用于NG_VALIDATORS提供程序,另一个用于NG_VALUE_ACCESSOR提供程序。这是因为我在提供者声明中指定了useClass而不是useExisting。您可以使用原始代码,但我认为通过将此指令添加到<input>可以更好地实现,因此它可以共享其formControlName

我已经恢复了我的旧代码,该代码由一个创建验证器实例的指令组成。对于任何希望实现相同目标的人来说,这是源头。请注意,我创建了ValidationError的实例,这只是一个简单的类。

/**
 * Used to denote a validation error of some kind
 *
 * @class ValidationError
 */
export class ValidationError {
  /**
   * @constructor
   * @param (string) message Key to the translation of the text to display
   * @param parameters Any additional parameters (max-length, etc)
   */
  constructor(public message: string, public parameters: any) {}
}

以下是该指令的来源:

import { ComponentFactoryResolver, Directive, forwardRef, OnDestroy, OnInit, ViewContainerRef } from '@angular/core';
import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator } from '@angular/forms';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { ValidationError } from '../../validation-error';
import { ValidationErrorsComponent } from '../../components/validation-errors/validation-errors.component';

@Directive({
  selector:
    '[formControl][showValidationErrors], ' +
    '[formControlName][showValidationErrors], ' +
    '[ngModel][showValidationErrors]',
  providers: [
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => ShowValidationErrorsDirective), multi: true
    }
  ]
})
export class ShowValidationErrorsDirective implements Validator, OnInit, OnDestroy  {
  errors: Observable<ValidationError[]>;

  private isSubscribedToControl = false;
  private isDestroyed = false;
  private validationErrorsComponent: ValidationErrorsComponent;
  private errorsSubject: BehaviorSubject<ValidationError[]>;

  constructor(
    private viewContainerRef: ViewContainerRef,
    private componentFactoryResolver: ComponentFactoryResolver
  ) {
    this.errorsSubject = new BehaviorSubject([]);
    this.errors = Observable.from(this.errorsSubject);
  }

  ngOnInit() {
    const factory = this.componentFactoryResolver.resolveComponentFactory(ValidationErrorsComponent);
    const componentReference = this.viewContainerRef.createComponent(factory);
    this.validationErrorsComponent = componentReference.instance;
    this.validationErrorsComponent.errors = this.errors;
  }

  validate(control: AbstractControl): ValidationErrors | any {
    this.subscribeToControlErrors(control);
    return null; // We haven't added any errors
  }

  private subscribeToControlErrors(control: AbstractControl) {
    if (!this.isSubscribedToControl) {
      this.isSubscribedToControl = true;
      control.statusChanges
        .takeWhile(x => !this.isDestroyed)
        .distinctUntilChanged()
        .map(x => control.errors)
        .subscribe(x => this.populateErrors(x));
    }
  }

  private populateErrors(errorsObject: any) {
    const errors = [];
    if (errorsObject) {
      for (const errorType of Object.keys(errorsObject)) {
        errors.push(new ValidationError(errorType, errorsObject[errorType]));
      }
    }
    this.errorsSubject.next(errors);
  }

  registerOnValidatorChange(fn: () => void): void {
  }


  ngOnDestroy(): void {
    this.isDestroyed = true;
  }
}

以下是组件的模板:

<div *ngFor="let error of errors | async">
  {{ error.message | translate }}
</div>

组件的来源:

import { Component, Input } from '@angular/core';
import { ValidationError } from '../../validation-error';
import { Observable } from 'rxjs/Observable';

@Component({
  selector: 'validation-errors',
  templateUrl: './validation-errors.component.html',
  styleUrls: ['./validation-errors.component.scss'],
})
export class ValidationErrorsComponent  {
  @Input()
  errors: Observable<ValidationError[]>;
}

最后,这就是它在消费者中的使用方式:

<input formControlName="mobileNumber" showValidationErrors />