feat(fp): WP-06 — kill $any() in templates (18x)

Make AsyncLoadedDirective generic with a static ngTemplateContextGuard for
AsyncComponent's own internal typing. That can't propagate to consumer
`<ng-template appAsyncLoaded let-p>` sites though -- Angular only infers a
structural directive's type parameter from an input bound on that same
node, not from a sibling input on the parent component -- so the ~9
root-cause consumers (dashboard, registration-detail, aanvraag-detail,
registratie-wizard) instead unwrap the RemoteData Success value via a
typed computed() and narrow it locally with `@if (x(); as p)`. The
remaining union-narrowing casts (registration-summary, showcase concepts
page) are replaced with a stable @let binding and a direct resource read,
respectively. Documented as a deviation in WP-06's backlog file.
This commit is contained in:
eho
2026-07-03 21:27:01 +02:00
parent 34d34512b3
commit 199cbe1f8c
10 changed files with 514 additions and 328 deletions
+27 -19
View File
@@ -1,4 +1,4 @@
import { Component, inject } from '@angular/core';
import { Component, computed, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { PageShellComponent } from '@shared/layout/page-shell/page-shell.component';
import { SkeletonComponent } from '@shared/ui/skeleton/skeleton.component';
@@ -30,24 +30,26 @@ import { detailRows } from '@registratie/domain/aanvraag-view';
backLink="/dashboard"
>
<app-async [data]="store.applications()">
<ng-template appAsyncLoaded let-list>
@let a = find($any(list));
@if (a) {
<app-data-block
i18n-ariaLabel="@@aanvraagDetail.ariaLabel"
ariaLabel="Aanvraaggegevens"
>
@for (row of rows(a); track row.key) {
<div app-data-row [key]="row.key" [value]="row.value"></div>
}
</app-data-block>
<app-alert class="app-section" type="info" i18n="@@aanvraagDetail.stub">
De volledige afhandeling van deze aanvraag is nog niet beschikbaar in deze POC.
</app-alert>
} @else {
<app-alert type="warning" i18n="@@aanvraagDetail.nietGevonden"
>Deze aanvraag is niet gevonden.</app-alert
>
<ng-template appAsyncLoaded>
@if (applications(); as list) {
@let a = find(list);
@if (a) {
<app-data-block
i18n-ariaLabel="@@aanvraagDetail.ariaLabel"
ariaLabel="Aanvraaggegevens"
>
@for (row of rows(a); track row.key) {
<div app-data-row [key]="row.key" [value]="row.value"></div>
}
</app-data-block>
<app-alert class="app-section" type="info" i18n="@@aanvraagDetail.stub">
De volledige afhandeling van deze aanvraag is nog niet beschikbaar in deze POC.
</app-alert>
} @else {
<app-alert type="warning" i18n="@@aanvraagDetail.nietGevonden"
>Deze aanvraag is niet gevonden.</app-alert
>
}
}
</ng-template>
<ng-template appAsyncLoading>
@@ -63,4 +65,10 @@ export class AanvraagDetailPage {
protected find = (list: Aanvraag[]): Aanvraag | undefined => list.find((a) => a.id === this.id);
protected rows = detailRows;
/** See DashboardPage's `profile` for why this narrows via a computed instead of `let-`. */
protected readonly applications = computed(() => {
const rd = this.store.applications();
return rd.tag === 'Success' ? rd.value : undefined;
});
}