pnpm install
pnpm exec ng servepnpm exec ng testsrc/app/
├── core/
│ └── services/
│ └── nickname-validation.ts # Async validation service
├── shared/
│ └── components/
│ └── nickname-editor/ # Reusable nickname editor
│ └── validators/
│ └── nickname-async.validator.ts
└── features/
└── building/
├── building.model.ts
└── building-edit/ # Smart host component
The component implements ControlValueAccessor and Validator interfaces
to make it fully reusable across different forms. It can be used with
formControlName in any reactive form without any changes:
<!-- buildings -->
<app-nickname-editor formControlName="nicknames" />
<!-- future: rooms -->
<app-nickname-editor formControlName="nicknames" />Validation runs on every change event with 300ms debounce via timer(300)
to avoid triggering the validation service on every keystroke.
The Save button is disabled when the form is in PENDING state
(validation in progress) or INVALID state. This prevents saving
while async validation is still running.
BuildingEditComponent uses signal-based APIs (toSignal, computed, input.required)
introduced in Angular 17+ for more efficient change detection.
Building data is passed via input.required<Building>() to make
BuildingEditComponent reusable — it can be used both as a standalone
page and inside a MatDialog without any changes.
Empty fields are not async-validated to avoid unnecessary HTTP requests.
Instead, Validators.required is added as a synchronous validator to
ensure empty fields are always treated as invalid.
The assignment asked for Angular Material only, but the default Material theme
references Roboto and Material Icons, which Angular’s build can try to
fetch from Google at production build time. To keep ng build working
without network access, the app bundles fonts and icon glyphs locally:
@fontsource/roboto— Roboto font CSS (imported insrc/styles.scss)material-icons— self-hosted Material Icons font CSS (imported insrc/styles.scss)
Runtime and nickname validation still use no HTTP; these packages only ship static assets with the app bundle.