skip to content
BKB
Table of Contents

Users can read and update existing topics. Now we will let them create one with a validated form.

The Add Topic form

Reactive form mental model

The TypeScript class owns the form model and validation. The HTML template connects inputs to that model and displays feedback.

1. Add the route

Generate the component:

Terminal window
ng generate component pages/topic-form

Place this route before topics/:id:

{
path: 'topics/new',
loadComponent: () =>
import('./pages/topic-form/topic-form').then(module => module.TopicForm)
}

Otherwise Angular may interpret the word new as an ID.

2. Build a typed form

Import ReactiveFormsModule in the component and create the form:

private readonly formBuilder = inject(FormBuilder);
protected readonly topicForm = this.formBuilder.nonNullable.group({
name: ['', [Validators.required, Validators.minLength(3)]],
completed: [false]
});

nonNullable means controls produce string and boolean, not string | null and boolean | null.

3. Connect the template

<form [formGroup]="topicForm" (ngSubmit)="submit()">
<label for="name">Topic name</label>
<input id="name" type="text" formControlName="name">
@if (topicForm.controls.name.touched) {
@if (topicForm.controls.name.hasError('required')) {
<p class="error">Topic name is required.</p>
}
}
<label>
<input type="checkbox" formControlName="completed">
Already completed
</label>
<button type="submit">Add topic</button>
</form>

4. Add domain validation

private validateTopicName(
control: AbstractControl
): ValidationErrors | null {
const name = String(control.value).trim().toLowerCase();
if (!name) return { whitespace: true };
const duplicate = this.topicService.topics().some(
topic => topic.name.toLowerCase() === name
);
return duplicate ? { duplicate: true } : null;
}

Add it to the name control with an explicitly typed parameter if strict TypeScript reports an implicit any.

5. Submit

submit(): void {
if (this.topicForm.invalid) {
this.topicForm.markAllAsTouched();
return;
}
const topic = this.topicService.addTopic(
this.topicForm.controls.name.value,
this.topicForm.controls.completed.value
);
this.router.navigate(['/topics', topic.id]);
}

Checkpoint

Try an empty value, two letters, spaces, a duplicate such as Signals, and a valid new topic. Only the valid value should create a topic.

Small challenge

Disable the submit button while the form is invalid, then consider whether hiding submission entirely gives better or worse feedback to a beginner.

Navigation: Previous: Signal state · Series overview · Next: SSR