How to observe touched event on Angular 2 NgForm?

2020-03-08 08:05发布

问题:

It is possible to subscribe a callback to an NgForm's valueChanges observable property in order to react to changes in the values of the controls of the form.

I need, in the same fashion, to react to the event of the user touching one of the form controls.

This class seem to define the valueChanges Observable and the touched property is defined as a boolean.

Is there a way to to react to the "control touched" event?

回答1:

There is not direct way provided by ng2 to react on touched event. It uses (input) event to fire the valueChanges event and (blur) event to set touched/untouched property of AbstractControl. So you need to manually subscribe on desired event in the template and handle it in your component class.



回答2:

You can extend default FormControl class, and add markAsTouched method that will call native method, plus your side effect.

import { Injectable } from '@angular/core';
import { FormControl, AsyncValidatorFn, ValidatorFn } from '@angular/forms';
import { Subscription, Subject, Observable } from 'rxjs';

export class ExtendedFormControl extends FormControl {
  statusChanges$: Subscription;
  touchedChanges: Subject<boolean> = new Subject<boolean>();

  constructor(
    formState: Object,
    validator: ValidatorFn | ValidatorFn[] = null,
    asyncValidator: AsyncValidatorFn | AsyncValidatorFn[] = null
  ) {
    super(formState, validator, asyncValidator);

    this.statusChanges$ = Observable.merge(
      this.valueChanges,
      this.touchedChanges.distinctUntilChanged()
    ).subscribe(() => {
      console.log('new value or field was touched');
    });
  }

  markAsTouched({ onlySelf }: { onlySelf?: boolean } = {}): void {
    super.markAsTouched({ onlySelf });

    this.touchedChanges.next(true);
  }
}


回答3:

If your issue was anything like mine, I was trying to mark a field as touched in one component and then respond to that in another component. I had access to the AbstractControl for that field. The way I got around it was

field.markAsTouched();
(field.valueChanges as EventEmitter<any>).emit(field.value);

And then I just subscribed to valueChanges in my other component. Noteworthy: field.valueChanges is exported as an Observable, but at runtime it's an EventEmitter, making this a less than beautiful solution. The other limitation of this would obviously be the fact that you're subscribing to a lot more than just the touched state.



回答4:

Had this same issue - put together this helper method to extract an observable which you can subscribe to in a form to be notified when touched status changes:

// Helper types

/**
 * Extract arguments of function
 */
export type ArgumentsType<F> = F extends (...args: infer A) => any ? A : never;

/**
 * Creates an object like O. Optionally provide minimum set of properties P which the objects must share to conform
 */
type ObjectLike<O extends object, P extends keyof O = keyof O> = Pick<O, P>;


/**
 * Extract a touched changed observable from an abstract control
 * @param control AbstractControl like object with markAsTouched method
 */
export const extractTouchedChanges = (control: ObjectLike<AbstractControl, 'markAsTouched' | 'markAsUntouched'>): Observable<boolean> => {
  const prevMarkAsTouched = control.markAsTouched;
  const prevMarkAsUntouched = control.markAsUntouched;

  const touchedChanges$ = new Subject<boolean>();

  function nextMarkAsTouched(...args: ArgumentsType<AbstractControl['markAsTouched']>) {
    touchedChanges$.next(true);
    prevMarkAsTouched.bind(control)(...args);
  }

  function nextMarkAsUntouched(...args: ArgumentsType<AbstractControl['markAsUntouched']>) {
    touchedChanges$.next(false);
    prevMarkAsUntouched.bind(control)(...args);
  }

  control.markAsTouched = nextMarkAsTouched;
  control.markAsUntouched = nextMarkAsUntouched;

  return touchedChanges$;
}
// Usage (in component file)

...
    this.touchedChanged$ = extractTouchedChanges(this.form);
...

I then like to do merge(this.touchedChanged$, this.form.valueChanges) to get an observable of all changes required to update validation.