How to observe touched event on Angular 2 NgForm?

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);
  }
}

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.


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.bind(control);
  const prevMarkAsUntouched = control.markAsUntouched.bind(control);

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

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

  function nextMarkAsUntouched(...args: ArgumentsType<AbstractControl['markAsUntouched']>) {
    prevMarkAsUntouched(...args);
    touchedChanges$.next(false);
  }
  
  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.

*Edit - on @marked-down's suggestion I've moved the call to the previous function to before emitting the new value, in case you query directly after receiving the value and end up out of sync