Angular Examples
هذا المحتوى غير متوفر بلغتك بعد.
Illustrative only. The Angular + PrimeNG Standards page states the rules; this page shows the three that a reader cannot infer from the rule alone.
1. A page-scoped signal store
Section titled “1. A page-scoped signal store”The store itself is unremarkable. The lifetime is the point — no providedIn, listed in the routed page’s providers, so it dies with the page.
@Injectable() // ← no providedIn: 'root'export class RouteOptimizationStore { private readonly api = inject(RouteOptimizationApi);
private readonly _branches = signal<Branch[]>([]); private readonly _selectedId = signal<string | null>(null); private readonly _loading = signal(false);
readonly branches = this._branches.asReadonly(); readonly loading = this._loading.asReadonly(); readonly selected = computed(() => this._branches().find(b => b.id === this._selectedId()) ?? null);
async load(): Promise<void> { this._loading.set(true); const result = await this.api.getBranches(); if (result.kind === 'ok') this._branches.set(result.branches); this._loading.set(false); }}@Component({ selector: 'app-route-optimization-page', changeDetection: ChangeDetectionStrategy.OnPush, providers: [RouteOptimizationStore], // ← created and destroyed with this page templateUrl: './route-optimization-page.html',})export class RouteOptimizationPage { protected readonly store = inject(RouteOptimizationStore);}Make it providedIn: 'root' and the second visit to the page shows the first visit’s data before the reload lands. That bug looks like a caching feature until a user notices stale numbers.
2. An API client that never throws
Section titled “2. An API client that never throws”Transport failure becomes a value at the boundary, so the store handles it with a switch, not a try.
export type BranchesResult = | { kind: 'ok'; branches: Branch[] } | { kind: 'unavailable' };
@Injectable({ providedIn: 'root' })export class RouteOptimizationApi { private readonly http = inject(HttpClient);
getBranches(): Promise<BranchesResult> { return firstValueFrom( this.http.get<BranchDto[]>('/api/branches').pipe( map(dtos => ({ kind: 'ok', branches: dtos.map(toBranch) }) satisfies BranchesResult), catchError(() => of({ kind: 'unavailable' } satisfies BranchesResult)), ), ); }}satisfies on both arms means adding a third result variant is a compile error at every call site, instead of a silent fall-through.
Per-request opt-out uses a context token, never the URL:
export const SKIP_AUTH = new HttpContextToken(() => false);
// callerthis.http.get('/api/public/config', { context: new HttpContext().set(SKIP_AUTH, true) });
// interceptorexport const authInterceptor: HttpInterceptorFn = (req, next) => req.context.get(SKIP_AUTH) ? next(req) : next(withBearerToken(req));Sniffing req.url.includes('token') works until an unrelated endpoint contains that substring.
3. Theming: preset over override
Section titled “3. Theming: preset over override”// app.config.ts — client branding as a preset. One place, follows dark mode, survives upgrades.providePrimeNG({ theme: { preset: definePreset(Aura, { semantic: { primary: { 500: '{sky.500}', 600: '{sky.600}' }, }, }), },});<!-- A single instance that must differ: the scoped dt property. --><p-button label="Save" [dt]="{ root: { borderRadius: '0' } }" />/* Banned. Wins by specificity today, breaks on the next library upgrade, and the override loads in lazy-chunk order — so it applies in one feature and not another, for reasons nobody can see from the component. */::ng-deep .p-button { border-radius: 0; }Test setup, for reference
Section titled “Test setup, for reference”There is no Karma config and no Jest config. The runner is wired in two places:
"test": { "builder": "@angular/build:unit-test" }{ "compilerOptions": { "types": ["vitest/globals"] } }Then npm test → ng test.
Back to Angular + PrimeNG Standards.