Как заставить директиву реагировать на EventEmitter в компоненте - PullRequest
2 голосов
/ 13 июля 2020

У меня есть CustomComponent, который выдает значение (назовем его просто «ошибкой»), если HTTP-запрос к внутреннему API-интерфейсу возвращает ошибку. Как я могу получить директиву (назовите ее Form Directive), применяемую к этой форме, чтобы распознавать, когда CustomComponent генерирует значение "error"?

Код для 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() {}
}

Код для директивы формы (хорошо работает, когда пользователь нажимает кнопку «Отправить», которая запускает событие onSubmit в CustomComponent):

@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.

1 Ответ

0 голосов
/ 15 июля 2020

Я заставил это работать по кругу, используя декоратор @Input в другой директиве формы, которая также импортирует submit $ из директивы формы.

Никаких изменений в коде для FormComponent и директивы формы vs. что показано в вопросе.

Соответствующий код из пользовательского компонента:

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 (импортирует submit $ из Form Directive):

@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 в жизненный цикл ngOnInit в invalidFormControlDirective (не удалось заставить это работать) и 2) узнать если есть способ генерировать событие и обрабатывать его с помощью Rx js fromEventPattern, а не передавать переменную @Input invalidOnError в invalidFormControlDirective.

...