如何获取指令以对组件中的 EventEmitter 做出反应

How to get a directive to react to an EventEmitter in a component

我有一个 CustomComponent,如果对后端的 http 请求 api returns 出错,它会发出一个值(我们就称它为“错误”)。我怎样才能获得应用于此表单的指令(称为表单指令),以识别 CustomComponent 何时发出“错误”值?

自定义组件代码:

export class CustomComponent extends FormComponent<Custom> {

  constructor(
    protected fb: FormBuilder,
    private httpService: HttpService) {
    super(fb);
  }

  currentVal: string = '';
  inputType: string = 'password';
  showPasswordTitle: string = 'Show Password';
  showPasswordStatus: boolean = false;
  form: FormGroup;

  @Output() invalidOnError = new EventEmitter<string>();

  protected buildForm(): FormGroup {
    return this.form = this.fb.group({
      fieldA: ['', Validators.required],
      fieldB: ['', Validators.required],
      fieldC: [''],
      fieldD: ['', [Validators.required, Validators.pattern('[0-9]{10}')]]
    }

  protected doSubmit(): Observable<Custom> {
    return this.httpService.callDatabase<Custom>('post', '/api/users/custom', this.value);
  };

  protected get value(): Registration {
    return {
      fieldA: this.fieldA.value,
      fieldB: this.fieldB.value,
      fieldC: this.fieldC.value,
      fieldD: this.fieldD.value
    };
  }

  get fieldA() { return this.form.get('fieldA'); }
  get fieldB() { return this.form.get('fieldB'); }
  get fieldC() { return this.form.get('fieldC'); }
  get fieldD() { return this.form.get('fieldD'); }

  protected onError() {
    if (this.error.length) {//error.length indicates some of the fields in the form are already registered in the database
      Object.keys(this.error).forEach(element => {
        let formControl = this.form.get(this.error[element])
        this.currentVal = formControl.value;
        formControl.setValidators(formControl.validator ? [formControl.validator, unique(this.currentVal)] : unique(this.currentVal))
        formControl.updateValueAndValidity()
        this.invalidOnError.emit('error');
      })
    }
  }

FormComponent 代码:

export abstract class FormComponent<T> implements OnInit {
  protected form: FormGroup = null;
  submitted = false;
  completed = false;
  error: string = null;

  constructor(protected fb: FormBuilder) {}

  ngOnInit() {
    this.form = this.buildForm();
  }

  onSubmit() {
    this.submitted = true;
    if (this.form.valid) {
      this.doSubmit().subscribe(
        () => {
          this.error = null;
          this.onSuccess();
        },
        err => {
          this.error = err
          this.onError();
        },
        () => {
          this.submitted = false;
          this.completed = true;
        }
      )
    }
  }

  protected abstract get value(): T;
  protected abstract buildForm(): FormGroup;
  protected abstract doSubmit(): Observable<T>;

  protected onSuccess() {}
  protected onError() {}
}

表单指令代码(当用户点击提交按钮时效果很好,这会触发 CustomComponent 中的 onSubmit 事件):

@Directive({
  selector: 'form'
})
export class FormSubmitDirective {
  submit$ = fromEvent(this.element, 'submit').pipe(shareReplay(1));

  constructor(private host: ElementRef<HTMLFormElement>) {}

  get element() {
    return this.host.nativeElement;
  }
}

我希望这样的方法可以解决我的问题,但这肯定行不通。

invalidOnError$ = fromEvent(this.element, 'error').pipe(shareReplay(1));

想法是使用指令中的 submit$ 或 invalidOnError$ 来关注表单中的第一个无效字段。适用于 submit$,但不适用于 invalidOnError$。感谢一些帮助 - Angular.

还很陌生

我通过在另一个表单指令中使用 @Input 装饰器来使它以一种循环的方式工作,该指令也从表单指令导入 submit$。

与问题中显示的内容相比,FormComponent 和 Form 指令的代码没有变化。

自定义组件的相关代码:

export class CustomComponent extends FormComponent<Custom> {
  invalidOnError: string = '';
  form: FormGroup;

  protected buildForm(): FormGroup {
    return this.form = this.fb.group({
      fieldA: ['', Validators.required],
      fieldB: ['', Validators.required],
      fieldC: [''],
      fieldD: ['', [Validators.required, Validators.pattern('[0-9]{10}')]]
    }

  protected doSubmit(): Observable<Custom> {
    invalidOnError = '';
    return this.httpService.callDatabase<Custom>('post', '/api/users/custom', this.value);
  };

  protected get value(): Registration {
    return {
      fieldA: this.fieldA.value,
      fieldB: this.fieldB.value,
      fieldC: this.fieldC.value,
      fieldD: this.fieldD.value
    };
  }

  get fieldA() { return this.form.get('fieldA'); }
  get fieldB() { return this.form.get('fieldB'); }
  get fieldC() { return this.form.get('fieldC'); }
  get fieldD() { return this.form.get('fieldD'); }

  protected onError() {
    if (this.error.length) {//error.length indicates some of the fields in the form are already registered in the database
      invalidOnError = 'invalid'
      Object.keys(this.error).forEach(element => {
        let formControl = this.form.get(this.error[element])
        this.currentVal = formControl.value;
        formControl.setValidators(formControl.validator ? [formControl.validator, unique(this.currentVal)] : unique(this.currentVal))
        formControl.updateValueAndValidity()
        this.invalidOnError.emit('error');
      })
    }
  }

CustomComponentTemplate中的相关代码:

<form class="bg-light border" appFocus="FieldA" [formGroup]="CustomForm" 
[invalidOnError]="invalidOnError" (ngSubmit)="onSubmit()">

来自 invalidFormControlDirective 的相关代码(从 Form 指令导入 submit$):

@Directive({
  selector: 'form[formGroup]'
})
export class FormInvalidControlDirective {
  private form: FormGroup;
  private submit$: Observable<Event>;
  @Input() invalidOnError: string = ''; //this is the @Input variable invalidOnError

  constructor(
    @Host() private formSubmit: FormDirective,
    @Host() private formGroup: FormGroupDirective,
    @Self() private el: ElementRef<HTMLFormElement>
  ) {
    this.submit$ = this.formSubmit.submit$;
  }

  ngOnInit() {
    this.form = this.formGroup.form;
    this.submit$.pipe(untilDestroyed(this)).subscribe(() => {
      if (this.form.invalid) {
        const invalidName = this.findInvalidControlsRecursive(this.form)[0];
        this.getFormElementByControlName(invalidName).focus();
      }
    });
  }

  ngOnChanges(){
    of(this.invalidOnError).pipe(filter(val => val == 'invalid')).subscribe(() => {
        if (this.form.invalid) {
          const invalidName = this.findInvalidControlsRecursive(this.form)[0];
          this.getFormElementByControlName(invalidName).focus();
        }
      });
  }

  ngOnDestroy() { }

  // findInvalidControlsRecursive and getFormElementByControlName defined functions to get invalid controls references
}

就是说,我会对 1) 以某种方式将 onChanges 生命周期下的代码带入 invalidFormControlDirective 中的 ngOnInit 生命周期(无法使其工作)感兴趣,以及 2) 找出是否有某种方法可以发出事件并使用 Rxjs fromEventPattern 处理它,而不是将 @Input 变量 invalidOnError 传递给 invalidFormControlDirective。