Search code examples
angulartypescriptangular-directiveangular-forms

Getting a NgForm instance from an Angular directive


I'm trying to create a behavior where all my forms that are attached to a preventFormChangesLoss directive will behave so that if there are changes to the form (if it's not pristine or submitted), the page shows a warning when the user wants to leave.

For that, I am trying to get my directive to get the NgForm instance my directive is attached to. For now, I have

import { Directive, Input, ElementRef, OnInit, AfterViewInit, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms';

@Directive({
  selector: '[preventFormChangesLoss]'
})
export class PreventFormChangesLoss implements OnInit, AfterViewInit {
  private origin: string = '';

  @ViewChild('myForm') form : NgForm;

  constructor(private elRef: ElementRef) {
    console.log(this.elRef);
  }

  ngOnInit() {
    console.log(this.elRef, this.form);
  }

  ngAfterViewInit() {
    console.log(this.elRef, this.form); // shows the native element, + 'undefined'
  }
}

And I use it with

<form preventFormChangesLoss #myForm="ngForm">
...
</form>

Unfortunately, this does not work, and I also would like my directive to get the NgForm instance without having to do the #myForm="ngForm" part, so that I can name my forms however I want and keep this behavior separate.

How can I do that, and can someone point me to the right place in the docs?


Solution

  • You cannot grab the form element like that because it's not part of directive's view. Instead you should directly inject it.

    import { Directive, Input, ElementRef, OnInit } from '@angular/core';
    import { NgForm } from '@angular/forms';
    
    @Directive({
      selector: '[preventFormChangesLoss]'
    })
    export class PreventFormChangesLoss implements OnInit {
      private origin: string = '';
    
      constructor(private ngForm: NgForm) {
        console.log(this.ngForm);
      }
    
      ngOnInit() {
        console.log(this.ngForm)
      }
    }
    

    This will allow you to manipulate and subscribe to events of the specific Angular-enhanced <form> from within the directive, so you'll only be using it like so:

    <form preventFormChangesLoss>
    ...
    </form>
    

    Not as per your design, but a tip: Since you said that you want to do this for all your forms, it might be simpler to change your selector to form:not([allowFormChangesLoss]). Then you just need to import the module to your shared module, and all your already created forms will have this functionality. In case you want to disable it on a specific form, you can use <form allowFormChangesLoss>, thus inversing the "default" behavior.