Angular 38: Module D Capstone — A Form + Routing Feature
easy⏱ 5 mincourseangular
A custom validator: cross-field checks live at the group level
Built-in validators like Validators.required only ever see a single control's value. To compare two sibling fields — password and confirmPassword — you write a plain function of your own with the ValidatorFn shape (control: AbstractControl) => ValidationErrors | null and pass it as the second argument to fb.group({...}, { validators: passwordsMatchValidator }), so it runs against the whole group instead of one control.
function passwordsMatchValidator(group: AbstractControl): ValidationErrors | null {
const password = group.get('password')?.value;
const confirmPassword = group.get('confirmPassword')?.value;
return password === confirmPassword ? null : { passwordsMismatch: true };
}
Tracking submission state with a signal, not a class field
A submitting = signal(false) gives the template a reactive flag to disable the submit button and show a loading label, flipped to true right before the (simulated) async work and used purely for UI feedback — it has nothing to do with the form's own valid/invalid state.
inject(Router).navigate(...): moving on after a successful submit
inject(Router) grabs the router service without a constructor; calling .navigate(['/success']) on it programmatically routes the app to the /success path — the same mechanism a routerLink triggers from a click, just invoked from code once your submit logic decides the flow is complete.
private router = inject(Router);
afterSuccess() {
this.router.navigate(['/success']);
}
How this scales beyond one file
In a real project, SignUpComponent, SuccessComponent, the passwordsMatchValidator, and the routes array would each live in their own file, and provideRouter(routes) would be registered once in main.ts (or app.config.ts). Everything is combined here purely so this single-file lesson can show the whole feature — form, validation, and routing — in one place.