The page held six teaching sections and a 142-line `styles:` block, at 471 effective lines against a limit of 250. It is now 36 lines of composition. Angular scopes a component's CSS to markup that component rendered, so the split had to move each rule to its owner. `concept-card` owns the card vocabulary and renders it. `.app-code`, `.app-lead`, `.app-cols` and `.app-note` become globals, because their targets are projected or arrive through `[innerHTML]`. That constraint exposed a live bug. The syntax-highlighting rules compiled to `pre[_ngcontent-%COMP%] .k[_ngcontent-%COMP%]`, but `highlight-ts` injects the `.k`/`.s`/`.c` spans through `[innerHTML]`, so they carry no scope attribute and the rule never matched. Keywords, strings and comments have always rendered in the plain foreground colour. The rules are global now, on five new `--app-code-*` tokens. Widen the colour guard while here: it scanned only `*.component.ts`, so every `*.page.ts`, `*.section.ts` and `*.step.ts` was invisible to it. That is how this page collected 21 hardcoded colours. One other file needed a fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
93 lines
3.9 KiB
TypeScript
93 lines
3.9 KiB
TypeScript
import { Component } from '@angular/core';
|
|
import type { Resource } from '@angular/core';
|
|
import { HeadingComponent } from '@shared/ui/heading/heading.component';
|
|
import { ASYNC } from '@shared/ui/async/async.component';
|
|
import { SkeletonComponent } from '@shared/ui/skeleton/skeleton.component';
|
|
import { ConceptCardComponent } from './concept-card.component';
|
|
import { SNIPPETS } from './snippets.generated';
|
|
import { highlightTs } from './highlight-ts';
|
|
|
|
/** Minimal fake Resource so <app-async> can be driven through every state without HTTP. */
|
|
function fakeResource<T>(status: string, value?: T, error?: Error): Resource<T> {
|
|
return {
|
|
value: () => value as T,
|
|
status: () => status,
|
|
error: () => error,
|
|
hasValue: () => value !== undefined,
|
|
reload: () => {},
|
|
} as unknown as Resource<T>;
|
|
}
|
|
|
|
/** Section 2: RemoteData fold. One value with four mutually exclusive states, instead of
|
|
three loose booleans. Composition-only; owns its own fake resources. Each of the four
|
|
demo states nests its own `<app-concept-card>` for its label — a plain `.tag` element
|
|
written here would carry this section's scope, not the card's, and stay unstyled. */
|
|
@Component({
|
|
selector: 'app-concepts-remote-data-section',
|
|
imports: [HeadingComponent, ...ASYNC, SkeletonComponent, ConceptCardComponent],
|
|
template: `
|
|
<section class="app-section">
|
|
<app-heading [level]="2">2 · RemoteData fold</app-heading>
|
|
<p class="app-lead">
|
|
Eén waarde met vier elkaar uitsluitende toestanden in plaats van drie losse booleans.
|
|
</p>
|
|
<div class="app-cols">
|
|
<app-concept-card variant="good" tag="Vier toestanden, één molecuul">
|
|
<div class="app-stack">
|
|
<app-concept-card variant="plain" tag="Loading">
|
|
<app-async [resource]="loadingRes"
|
|
><ng-template appAsyncLoaded let-v>{{ v }}</ng-template
|
|
><ng-template appAsyncLoading
|
|
><app-skeleton [count]="2" height="1.2rem" [delay]="0" /></ng-template
|
|
></app-async>
|
|
</app-concept-card>
|
|
<app-concept-card variant="plain" tag="Empty">
|
|
<app-async [resource]="emptyRes" [isEmpty]="isEmpty"
|
|
><ng-template appAsyncLoaded let-v>{{ v }}</ng-template></app-async
|
|
>
|
|
</app-concept-card>
|
|
<app-concept-card variant="plain" tag="Failure">
|
|
<app-async [resource]="errorRes"
|
|
><ng-template appAsyncLoaded let-v>{{ v }}</ng-template></app-async
|
|
>
|
|
</app-concept-card>
|
|
<app-concept-card variant="plain" tag="Success">
|
|
<app-async [resource]="successRes" [isEmpty]="isEmpty"
|
|
><ng-template appAsyncLoaded
|
|
><ul>
|
|
@for (i of successRes.value(); track i) {
|
|
<li>{{ i }}</li>
|
|
}
|
|
</ul></ng-template
|
|
></app-async
|
|
>
|
|
</app-concept-card>
|
|
</div>
|
|
</app-concept-card>
|
|
<app-concept-card
|
|
variant="good"
|
|
tag="De exhaustieve fold"
|
|
[code]="code['fold']"
|
|
[src]="src['fold']"
|
|
>
|
|
<p class="app-note">
|
|
Een nieuwe variant toevoegen breekt de compile via <code>assertNever</code> tot je hem
|
|
afhandelt.
|
|
</p>
|
|
</app-concept-card>
|
|
</div>
|
|
</section>
|
|
`,
|
|
})
|
|
export class RemoteDataSection {
|
|
isEmpty = (v: string[]) => !v || v.length === 0;
|
|
|
|
loadingRes = fakeResource<string[]>('loading');
|
|
emptyRes = fakeResource<string[]>('resolved', []);
|
|
errorRes = fakeResource<string[]>('error', undefined, new Error('Demo'));
|
|
successRes = fakeResource<string[]>('resolved', ['Huisartsgeneeskunde', 'Spoedeisende hulp']);
|
|
|
|
protected readonly code: Record<string, string> = { fold: highlightTs(SNIPPETS['fold']) };
|
|
protected readonly src: Record<string, string> = { fold: 'shared/application/remote-data.ts' };
|
|
}
|