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:
@@ -87,59 +87,61 @@ import { tasksFromProfile } from '@registratie/domain/tasks';
|
||||
}
|
||||
|
||||
<app-async [data]="store.profile()">
|
||||
<ng-template appAsyncLoaded let-p>
|
||||
@let tasks = tasksFor($any(p).registration);
|
||||
<ng-template appAsyncLoaded>
|
||||
@if (profile(); as p) {
|
||||
@let tasks = tasksFor(p.registration);
|
||||
|
||||
<section>
|
||||
@if (tasks.length) {
|
||||
<app-task-list
|
||||
class="app-section"
|
||||
i18n-listHeading="@@dashboard.watMoetIkRegelen"
|
||||
listHeading="Wat moet ik regelen"
|
||||
[tasks]="tasks"
|
||||
/>
|
||||
} @else {
|
||||
<app-heading [level]="2" i18n="@@dashboard.watMoetIkRegelen"
|
||||
>Wat moet ik regelen</app-heading
|
||||
<section>
|
||||
@if (tasks.length) {
|
||||
<app-task-list
|
||||
class="app-section"
|
||||
i18n-listHeading="@@dashboard.watMoetIkRegelen"
|
||||
listHeading="Wat moet ik regelen"
|
||||
[tasks]="tasks"
|
||||
/>
|
||||
} @else {
|
||||
<app-heading [level]="2" i18n="@@dashboard.watMoetIkRegelen"
|
||||
>Wat moet ik regelen</app-heading
|
||||
>
|
||||
<p class="app-text-subtle" i18n="@@dashboard.nietsOpenstaan">
|
||||
U heeft op dit moment niets openstaan.
|
||||
</p>
|
||||
}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<app-heading [level]="2" i18n="@@dashboard.mijnRegistratie"
|
||||
>Mijn registratie</app-heading
|
||||
>
|
||||
<p class="app-text-subtle" i18n="@@dashboard.nietsOpenstaan">
|
||||
U heeft op dit moment niets openstaan.
|
||||
</p>
|
||||
}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<app-heading [level]="2" i18n="@@dashboard.mijnRegistratie"
|
||||
>Mijn registratie</app-heading
|
||||
>
|
||||
<div class="app-section">
|
||||
<app-registration-summary [reg]="$any(p).registration" />
|
||||
</div>
|
||||
<app-data-block
|
||||
class="app-section"
|
||||
i18n-heading="@@dashboard.persoonsgegevens"
|
||||
heading="Persoonsgegevens (BRP)"
|
||||
>
|
||||
<div
|
||||
app-data-row
|
||||
i18n-key="@@dashboard.straat"
|
||||
key="Straat"
|
||||
[value]="$any(p).person.adres.straat"
|
||||
></div>
|
||||
<div
|
||||
app-data-row
|
||||
i18n-key="@@dashboard.postcode"
|
||||
key="Postcode"
|
||||
[value]="$any(p).person.adres.postcode"
|
||||
></div>
|
||||
<div
|
||||
app-data-row
|
||||
i18n-key="@@dashboard.woonplaats"
|
||||
key="Woonplaats"
|
||||
[value]="$any(p).person.adres.woonplaats"
|
||||
></div>
|
||||
</app-data-block>
|
||||
</section>
|
||||
<div class="app-section">
|
||||
<app-registration-summary [reg]="p.registration" />
|
||||
</div>
|
||||
<app-data-block
|
||||
class="app-section"
|
||||
i18n-heading="@@dashboard.persoonsgegevens"
|
||||
heading="Persoonsgegevens (BRP)"
|
||||
>
|
||||
<div
|
||||
app-data-row
|
||||
i18n-key="@@dashboard.straat"
|
||||
key="Straat"
|
||||
[value]="p.person.adres.straat"
|
||||
></div>
|
||||
<div
|
||||
app-data-row
|
||||
i18n-key="@@dashboard.postcode"
|
||||
key="Postcode"
|
||||
[value]="p.person.adres.postcode"
|
||||
></div>
|
||||
<div
|
||||
app-data-row
|
||||
i18n-key="@@dashboard.woonplaats"
|
||||
key="Woonplaats"
|
||||
[value]="p.person.adres.woonplaats"
|
||||
></div>
|
||||
</app-data-block>
|
||||
</section>
|
||||
}
|
||||
</ng-template>
|
||||
<ng-template appAsyncLoading>
|
||||
<app-skeleton height="2.5rem" [count]="6" />
|
||||
@@ -152,8 +154,10 @@ import { tasksFromProfile } from '@registratie/domain/tasks';
|
||||
>
|
||||
<div class="app-section">
|
||||
<app-async [data]="store.aantekeningen()">
|
||||
<ng-template appAsyncLoaded let-r>
|
||||
<app-registration-table [rows]="$any(r)" />
|
||||
<ng-template appAsyncLoaded>
|
||||
@if (aantekeningen(); as r) {
|
||||
<app-registration-table [rows]="r" />
|
||||
}
|
||||
</ng-template>
|
||||
<ng-template appAsyncLoading>
|
||||
<app-skeleton height="2.5rem" [count]="3" />
|
||||
@@ -239,6 +243,19 @@ export class DashboardPage {
|
||||
return tasksFromProfile(reg, this.eligible());
|
||||
}
|
||||
|
||||
/** Typed narrowing for the `<app-async>` loaded slot — `<ng-template>`'s own
|
||||
context can't inherit a generic from a sibling host input (Angular only infers
|
||||
a structural directive's type parameter from an input on that same node), so
|
||||
the Success value is unwrapped here instead of through `let-`. */
|
||||
protected readonly profile = computed(() => {
|
||||
const rd = this.store.profile();
|
||||
return rd.tag === 'Success' ? rd.value : undefined;
|
||||
});
|
||||
protected readonly aantekeningen = computed(() => {
|
||||
const rd = this.store.aantekeningen();
|
||||
return rd.tag === 'Success' ? rd.value : undefined;
|
||||
});
|
||||
|
||||
/** Primary transactional actions, as an "aanvragen" list (see CIBG's
|
||||
componenten/aanvragen). The core portal sections live in the header nav now;
|
||||
the teaching pages (concepts/brief) are only reachable from here. */
|
||||
|
||||
Reference in New Issue
Block a user