The underlying data model / API is nearly the same - it's about where the data model is defined - either the view or the component class.
const contactForm = new FormGroup<ContactFormType>({
name: new FormGroup<NameFormType>({
first: new FormControl('Juana'),
last: new FormControl('Garcia')
}),
email: new FormControl('')
});
// types used above
interface ContactFormType {
name: FormGroup<NameFormType>;
email: FormControl<string | null>;
}
type NameFormType = {
first: FormControl<string | null>;
last: FormControl<string | null>;
};
... and can be bound by
<form [formGroup]="contactForm" novalidate>
<div formGroupName="name">
<div>
<label>First Name</label>
<input type="text" formControlName="first">
</div>
<div>
<label>Last Name</label>
<input type="text" formControlName="last">
</div>
</div>
<div>
<label>E-Mail</label>
<input type="email" formControlName="email">
</div>
<button type="submit">Submit</button>
</form>
[formGroup]="contactForm"
binds upper FormGroupformGroupName
binds child FormGroupformControlName
binds child FormControl... creates the same FormGroup-FormControl tree
interface ContactFormType {
name: FormGroup<NameFormType>;
email: FormControl<string | null>;
}
type NameFormType = {
first: FormControl<string | null>;
last: FormControl<string | null>;
};
@Component({ ... })
export class FormTestComponent implements OnInit {
contactForm: FormGroup;
constructor(private formBuilder: FormBuilder) { }
ngOnInit(): void {
contactForm = this.formBuilder.group({
name: this.formBuilder.group({
first: "Juana",
last: "Garcia",
}),
email: "",
});
}
}
... in Model Driven forms
this.contactForm = this.formBuilder.group({
name: this.formBuilder.group({
first: "Juana",
last: ["Garcia", Validators.required],
}),
email: ["", Validators.email],
});
FormControl
and FormGroup
both extend AbstractControl
valueChanges
and statusChanges
provide ObservablesFormControl
has single controls statusFormGroup
has aggregated status of child controlsinterface ValidatorFn {
(c: AbstractControl): {
[key: string]: any;
} | null;
}
ValidatorFn
takes an AbstractControl
to validate.
Returns null if valid.
Returns {[key: string]: any} as error definition.
Higher order function can return ValidatorFn
which returns { forbiddenName: name } as error.
function forbiddenName(forbiddenName: string): ValidatorFn {
return (control: AbstractControl): ValidationErrors => {
const name = control.value;
const isForbidden = forbiddenName === name;
return isForbidden ? { forbiddenName: name } : null;
};
}
behaves just like Angular Validators
FormControl.errors is extended by custom error key-value pairs from validation method.
<div
*ngIf="contactForm.controls.name.controls.first.getError('forbiddenName')">
The name is not allowed.
</div>
Live Code Examples & Backup slides below
Following slides are not subject of the lecture.
Ask trainers in case of questions.
<form #contactForm="ngForm" (ngSubmit)="submit(contactForm.value)">
<div ngModelGroup="name">
<label for="first">First Name</label>
<input type="text" name="first" ngModel>
<label for="last">Last Name</label>
<input type="text" name="last" ngModel>
</div>
<label for="email">E-Mail</label>
<input type="text" name="email" ngModel>
<button type="submit">Submit</button>
</form>
{ name: { first: string, last: string }, email:string }
<input name=first ngModel>
creates property inside forms data model with key 'first'
<input name=first [ngModel]="varName">
creates one-way binding - binds a default value
<input name=first [(ngModel)]="varName">
<input name=first [ngModel]="varName" (ngModelChange)="varName=$event">
creates two-way binding. Note: state at two places (component member and forms model)
<div ngModelGroup=group></div>
creates nested object called group inside forms data model (form.value)
... in Template Driven forms
<input name=otto ngModel required minlength=4 maxlength=24>
<form #form=ngForm>
<input name=otto ngModel required>
<div *ngIf=form.controls.otto.errors>
<div [hidden]="!form.controls.otto.errors.required">
<span>Otto is really important!</span>
</div>
</div>
</form>
form
and ngModelGroup
aggregate underlying controls (combined errors)
Adding custom validators to Template Driven forms via Directives
<input type="text" name="first" ngModel noForbiddenName>
NG_VALIDATORS
dependency via providers
property (directive meta data)validate()
for directive and call validator - e.g. forbiddenNameValidator@Directive({
selector: '[noForbiddenName][ngModel],[noForbiddenName][formControl],[noForbiddenName][formControlName]',
providers: [{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ForbiddenNameValidator),
multi: true
}]
})
export class ForbiddenNameValidator implements Validator {
validator = forbiddenNameValidatorFn;
validate(c: FormControl) {
return this.validator(c);
}
}