How to observe touched event on Angular 2 NgForm?

后端 未结 4 1848
青春惊慌失措
青春惊慌失措 2020-12-18 18:53

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

相关标签:
4条回答
  • 2020-12-18 19:30

    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.

    0 讨论(0)
  • 2020-12-18 19:36

    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.

    0 讨论(0)
  • 2020-12-18 19:37

    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);
      }
    }
    
    0 讨论(0)
  • 2020-12-18 19:37

    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.

    0 讨论(0)
提交回复
热议问题