I'm having a hard time trying to use both *ngIf inside a form, and ngFormModel to validate said form.
Use case is this : based on user input, hide or deactivate some specific fields in the form. But in case these inputs are shown, they must be validated.
When only basic validation is required, I can make do one way or another :
ngControl
and required
pattern
attribute. It's not Angular, but it works.But in order to implement more complex validations, I've been trying to use ngControl
coupled to ngFormModel
so as to use custom checks. I used pieces of code found on the following pages :
How to add form validation pattern in angular2 (and the links referenced there)
Angular2 Forms :Validations, ngControl, ngModel etc
My code is as follows :
HTML
<div>
<h1>Rundown of the problem</h1>
<form (ngSubmit)="submitForm()" #formState="ngForm" [ngFormModel]="myForm">
<div class="checkbox">
<label>
<input type="checkbox" [(ngModel)]="model.hideField" ngControl="hideField"> Is the input below useless for you ?
</label>
</div>
<div *ngIf="!model.hideField">
<div class="form-group">
<label for="optionalField">Potentially irrelevant field </label>
<input type="text" class="form-control" [(ngModel)]="model.optionalField" ngControl="optionalField" required #optionalField="ngForm">
<div [hidden]="optionalField.valid || optionalField.pristine" class="alert alert-warning">
This input must go through myCustomValidator(), so behave.
</div>
</div>
</div>
<button type="submit" class="btn btn-primary" [disabled]="!formState.form.valid">I can't be enabled without accessing the input :(</button>
<button type="submit" class="btn btn-default">Submit without using form.valid (boo !)</button>
</form>
</div>
Typescript
import {Component, ChangeDetectorRef, AfterViewInit } from 'angular2/core';
import {NgForm, FormBuilder, Validators, ControlGroup, FORM_DIRECTIVES} from 'angular2/common';
@Component({
selector: 'accueil',
templateUrl: 'app/accueil.component.bak.html',
directives:[FORM_DIRECTIVES],
providers: [FormBuilder]
})
export class AccueilComponent implements AfterViewInit {
private myForm: ControlGroup;
model: any;
constructor(fb: FormBuilder, cdr: ChangeDetectorRef) {
this.cdr = cdr ;
this.model = {} ;
this.myForm = fb.group({
"hideField": [false],
"optionalField": [this.model.optionalField, Validators.compose([this.myCustomValidator])]
});
}
ngAfterViewInit() {
// Without this, I get the "Expression has changed after it was checked" exception.
// See also : https://stackoverflow.com/questions/34364880/expression-has-changed-after-it-was-checked
this.cdr.detectChanges();
}
submitForm(){
alert("Submitted !");
}
myCustomValidator(optionalField){
// Replace "true" by "_someService.someCheckRequiringLogicOrData()"
if(true) {
return null;
}
return { "ohNoes": true };
}
}
Even if the input is removed from the template with *ngIf, the constructor is still referencing the control. Which in turns prevents me from using the [disabled]="!formState.form.valid", as myForm
is understandably INVALID.
Is what I am aiming at possible using Angular 2 ? I'm sure this is not that uncommon a use case, but then again with my current knowledge, I cannot see how I could make it work.
Thanks !
What you can try to do is reset the validators on your control. Which means, when you want a new set of validators to be bound to a control because of a state change, you would redefine the validator function.
In your case, when your checkbox is checked/unchecked you want the following to occur:
form.valid
is updated.See my plnkr sample based on Angular.io's Forms Guide
if (optional)
this.heroFormModel.controls['name'].validator = Validators.minLength(3);
else
this.heroFormModel.controls['name'].validator =
Validators.compose([Validators.minLength(3), Validators.required]);
this.heroFormModel.controls['name'].updateValueAndValidity();
I just ran into exactly the same problem, and found a workaround that relies on manually including and excluding the controls:
import {Directive, Host, SkipSelf, OnDestroy, Input, OnInit} from 'angular2/core';
import {ControlContainer} from 'angular2/common';
@Directive({
selector: '[ngControl]'
})
export class MyControl implements OnInit, OnDestroy {
@Input() ngControl:string;
constructor(@Host() @SkipSelf() private _parent:ControlContainer) {}
ngOnInit():void {
// see https://github.com/angular/angular/issues/6005
setTimeout(() => this.formDirective.form.include(this.ngControl));
}
ngOnDestroy():void {
this.formDirective.form.exclude(this.ngControl);
}
get formDirective():any {
return this._parent.formDirective;
}
}
To make this work, all dynamic controls have to be excluded from the form initially; see the plunkr for details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With