Angular2 - Validate and submit form from outside

Found out how to do it:

  • trigger submit with <formname>.ngSubmit.emit()
  • get form status with <formname>.form.valid

Example:

<form (ngSubmit)="save()" #documentEditForm="ngForm">
...
</form>

<button class="btn-save button primary"
(click)="documentEditForm.ngSubmit.emit()"
[disabled]="!documentEditForm.form.valid">SAVE</button>

Edit: As @yuriy-yakovenko has pointed out, you should add in your component code the following:

@ViewChild('documentEditForm') documentEditForm: FormGroupDirective; 

And don't forget to import the FormGroupDirective if you haven't done yet


You can link the button to the form using the form attribute on the button:

<form (ngSubmit)="save()" id="ngForm" #documentEditForm="ngForm"> 
  ... 
</form>

<button form="ngForm">
  SAVE
</button>

You can still check its validity like this:

<button form="ngForm" [disabled]="!documentEditForm.form.valid">
  SAVE
</button>

The form needs to have an ID id="example-form" and the submit button a matching ID in the form="example-form"

See here for more details: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-form


Important: if using Angular material form controls + reactive forms

Call onSubmit(undefined) to properly set submitted = true on the [formGroup] directive

Note: The directive is not the same object as the angular form itself (more below on that)

Here's part of the sourcecode for the [formGroup] directive. (for reactive forms)

@Directive({
  selector: '[formGroup]',
  providers: [formDirectiveProvider],
  host: {'(submit)': 'onSubmit($event)', '(reset)': 'onReset()'},
  exportAs: 'ngForm'
})
export class FormGroupDirective extends ControlContainer implements Form,
    OnChanges {
  /**
   * @description
   * Reports whether the form submission has been triggered.
   */
  public readonly submitted: boolean = false;

 .....

 onSubmit($event: Event): boolean {
    (this as{submitted: boolean}).submitted = true;
    syncPendingControls(this.form, this.directives);
    this.ngSubmit.emit($event);
    return false;
 }

You use it like this:

<form [formGroup]="form" #formRef="ngForm">

And you can get a reference to the FormGroupDirective in your ts file with:

@ViewChild('formRef') 
formRef: FormGroupDirective;
  • Note: NgForm is another directive automatically applied when you create a <form> tag.
  • Confusing note: Both NgForm and FormGroupDirective have exportAs: 'ngForm' in their source code, (and they also both declare submitted and ngSubmit properties). But when I put #ngForm='ngForm' I get an object of type FormGroupDirective and not NgForm (verified in debugger). I'm not sure exactly why - but that's why i declared it as FormGroupDirective and not NgForm - I think maybe the first wins.

You use it like this:

this.formRef.onSubmit(undefined)

Example:

// html
<form [formGroup]="form" #formRef="ngForm">
    // ...Form Controls
</form>

// component.ts
export class MyComponent {

    @ViewChild('formRef')
    formRef: FormGroupDirective;

    form: FormGroup = new FormGroup({
        myInput: new FormControl(''),
        //etc...
    });

    submitFormProgrammatically() {
        this.formRef.onSubmit(undefined);
    }
}

If you were to just call this.formRef.ngSubmit.emit() as some other answers have suggested you won't get the all important submitted = true set.

Why does this matter?

If you're using any Angular CDK or Angular Material controls the error condition isn't displayed unless the form field has been touched (clicked or gained focus) OR the form as a whole has been submitted.

So if you have a missing required field that the mouse/cursor has never entered then that field won't be shown in red even if you do ngSubmit.emit() (because submitted = false for the form and the control has touched = false).

So how does it work normally with a regular submit button?

Normally if you have <button type='submit'>Submit</button> (inside the <form> tag) it will trigger the standard HTML <form> to submit (nothing to do with Angular) - and that raises a standard submit event on the form tag.

IF that <form> tag also has a [formGroup] directive on it (as shown above) then the HTML form submit event is 'caught' by the directive and that's what causes the onSubmit() function above to be called.

This in turn raises the ngSubmit event - which you can catch yourself if you need to do further processing - like showing an alert.

So it's very important to call onSubmit and not ngSubmit.emitto get the validation handling to work when using material (reactive form) controls. The $event parameter can just be null or undefined.

Further reading: Look at ErrorStateMatcher (for Angular CDK/Material only) to see the exact rules. You can create your own if working around the limitations of the default becomes too complex.

Even more confusing: The [formGroup] directive is NOT the same object as FormGroup which just holds data. Only the directive has submitted on it - whereas FormGroup has things like touched, pristine, dirty.