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:
2026-07-03 21:27:01 +02:00
parent 34d34512b3
commit 199cbe1f8c
10 changed files with 514 additions and 328 deletions

View File

@@ -6,10 +6,20 @@ import { AlertComponent } from '@shared/ui/alert/alert.component';
import { ButtonComponent } from '@shared/ui/button/button.component';
import { RemoteData, fromResource, foldRemote } from '@shared/application/remote-data';
/* Slot markers. Put on <ng-template> children of <app-async>. */
/* Slot markers. Put on <ng-template> children of <app-async>. Generic so the
$implicit context is typed as the resource's T instead of unknown — see
AsyncComponent's contentChild<AsyncLoadedDirective<T>> below, which threads the
host's own T through the query result type. */
@Directive({ selector: '[appAsyncLoaded]' })
export class AsyncLoadedDirective {
constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}
export class AsyncLoadedDirective<T = unknown> {
constructor(public tpl: TemplateRef<{ $implicit: T }>) {}
static ngTemplateContextGuard<T>(
_dir: AsyncLoadedDirective<T>,
_ctx: unknown,
): _ctx is { $implicit: T } {
return true;
}
}
@Directive({ selector: '[appAsyncLoading]' })
export class AsyncLoadingDirective {
@@ -88,7 +98,7 @@ export class AsyncComponent<T> {
retryText = input($localize`:@@async.retry:Opnieuw proberen`);
emptyText = input($localize`:@@async.empty:Geen gegevens gevonden.`);
loadedTpl = contentChild.required(AsyncLoadedDirective);
loadedTpl = contentChild.required<AsyncLoadedDirective<T>>(AsyncLoadedDirective);
loadingTpl = contentChild(AsyncLoadingDirective);
emptyTpl = contentChild(AsyncEmptyDirective);
errorTpl = contentChild(AsyncErrorDirective);