Dieses Blatt ist zum Danebenlegen gedacht: die Entscheidungen und Regeln, die man im Angular-Alltag ständig braucht und regelmäßig vergisst. Es bringt keine davon bei — dafür sind die Module des Seminars da. Maßgeblich bleibt die offizielle Angular-Dokumentation samt Style Guide; der Mehrwert hier liegt in der Auswahl. Warum getrennte Projekte im Monorepo beim Ausliefern trotzdem aneinanderhängen, erklärt der Beitrag Das Monorepo entkoppelt eure Teams — beim Ausliefern nicht.
Strict-Schalter, die gesetzt sein müssen
| Schalter | Was er verhindert |
|---|---|
strict | Variablen vor Definition, unbehandeltes null, falsche Argumenttypen |
strictInjectionParameters | Laufzeitfehler, wenn ein Service oder Token nicht auflösbar ist |
strictInputAccessModifiers | Inputs, die von außen gar nicht gesetzt werden können |
strictTemplates | ungültige Property-Bindings und fehlende Null-Prüfungen im Template |
Zwei Blöcke, nicht einer: strict bei den compilerOptions, die übrigen drei
unter angularCompilerOptions. Im falschen Block ist ein Schalter gültiges
JSON und wirkungslos.
{
"compilerOptions": { "strict": true },
"angularCompilerOptions": {
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
Welcher Builder läuft, verrät eine Zeile in den Dev-Dependencies:
@angular/build ist Vite, @angular-devkit/build-angular noch webpack.
Umstellen mit ng update @angular/cli --name=use-application-builder.
Modul: Moderne Standards
Signals oder RxJS
| Signals | RxJS |
|---|---|
| lokaler Komponentenzustand | echte Datenströme und Events |
| Inputs und Outputs | komplexe asynchrone Orchestrierung |
abgeleitete Werte (computed) | reaktive Transformationen über die Zeit |
| einmaliges Laden von Daten | Zusammenführen mehrerer Quellen |
Prüffrage: Ist der Zustand ein Wert, der jetzt gilt, oder ein Verlauf
über die Zeit? Umzustellen sind in jedem Fall die Dekoratoren: @Input() →
input(), @Output() → output(), @ViewChild → viewChild().
Modul: Moderne Standards
Store-Formen im Vergleich
Drei Ausbaustufen, in dieser Reihenfolge zu prüfen. Kanonische Kurzform:
// 1 — Service mit Signalen: Quelle privat, Ableitungen öffentlich
export class CartService {
private items = signal<CartItem[]>([]);
readonly cart = this.items.asReadonly();
readonly count = computed(() => this.items().length);
}
// 2 — Daten aus einer API
private res = httpResource<Part[]>(() => '/api/parts');
readonly parts = computed(() => this.res.value() ?? []);
// 3 — NgRx SignalStore, wenn Struktur über viele Stores nötig wird
export const CartStore = signalStore(
withState({ items: [] as CartItem[] }),
withComputed(({ items }) => ({ count: computed(() => items().length) })),
withMethods(store => ({ add: (p: Part) => patchState(store, …) })),
);
effect() ist für Seiteneffekte da (Persistenz, Protokollierung), nicht für
Ableitungen — dafür gibt es computed. Die Abhängigkeit entsteht allein
dadurch, dass ein Signal im Callback gelesen wird.
Vor NgRx gehört eine Fassade: ein schmaler Service, der den Store intern
nutzt und nach außen nur fachliche Signale zeigt. Ohne sie taucht der NgRx-Typ
StateSource im abgeleiteten Typ jedes Konsumenten auf.
Modul: State Management
Architekturstufen und ihr Auslöser
| Stufe | Löst welchen Schmerz |
|---|---|
| Ein Projekt, wenige Routen | — Ausgangspunkt |
| Feature-Shells | träger Erststart, unklare Zuständigkeiten |
| Sheriff | ungewollte Kopplung zwischen Feature-Shells |
| Monorepo mit Projekten | Merge-Konflikte, divergierende Team-Prioritäten |
| Micro-Frontends | Deploy-Kopplung, Compliance-Isolation |
Von rechts lesen: Wer den Schmerz nicht wiedererkennt, braucht die Stufe nicht. Im Monorepo bleibt ein Kopplungspunkt bestehen:
| Geteilt über alle Projekte | Je Projekt eigen |
|---|---|
eine package.json mit allen Versionen | eigene tsconfig.json |
ein angular.json | eigene main.ts |
ein node_modules | eigene App-Konfiguration |
| ein Release-/Deploy-Verbund | eigenes Build-Artefakt in dist/ |
Feature-Shells und Monorepo ergänzen einander — das eine ersetzt das andere
nicht. Sheriffs depRules sind eine Whitelist: Was nicht dasteht, ist
verboten; src und src/app brauchen ein eigenes Tag.
Module: Architektur · Monorepo & Micro-Frontends
Micro-Frontends — Bausteine zur Laufzeit
| Baustein | Rolle |
|---|---|
federation.config.js | legt fest, was ein Remote nach außen gibt |
| Remote-Entry-URL | sagt dem Konsumenten, was verfügbar ist |
loadRemoteModule | lädt einen Export zur Laufzeit nach |
Host-app.routes.ts | hängt die Remote-Routen als Kindrouten ein |
MenuItems-Export | liefert die Navigationseinträge des Remotes |
Der Host kennt seine Remotes nur über URLs, nicht über Importe — deshalb ist
gleichgültig, ob sie im selben Repository liegen. Einrichtung je Projekt:
ng add @angular-architects/native-federation --project <name> --type host|remote --port <n>.
Modul: Monorepo & Micro-Frontends
Namen, Selektoren und Klassenmember
| Ebene | Regel | Beispiel |
|---|---|---|
| Datei | Wörter mit Bindestrich | part-details.ts |
| Datei | alle Dateien einer Komponente gleich benannt | part-details.html, .css |
| Datei | Testdateien auf .spec.ts | part-details.spec.ts |
| Datei | Name passt zum exportierten Bezeichner | PartDetails |
| Selektor | eigenes Präfix, nie ng | nw-part-details |
| Selektor | Komponenten in kebab-case | nw-cart-summary |
| Selektor | Attribut-Direktiven in camelCase | nwHighlight |
| Property | keine Kollision mit DOM-Namen | nicht id, style, title |
| Property | kein Präfix bei Inputs, kein on bei Outputs | icon, saved |
Klassenmember: Angular-Properties vor den Methoden, protected für alles, was
nur das Template braucht, readonly für alles, was Angular initialisiert,
Event-Handler nach ihrer Wirkung benennen (addToCart(), nicht
onClick()). Abhängigkeiten kommen per inject() herein.
Beim Umhüllen eines Standard-Elements lohnt der Attribut-Selektor
selector: 'button[nwIconButton]' — so bleiben dessen Events und Attribute
erreichbar. Gegliedert wird nach Fachbereichen, nicht nach Dateityp; je
Datei eine Verantwortung.
Modul: Standalone & Style Guide
Change Detection und Ladezeit
OnPush ist mit Signals und Zoneless die naheliegende Voreinstellung — eine Zeile je Komponente, eine globale Einstellung gibt es nicht:
@Component({
selector: 'nw-part-details',
changeDetection: ChangeDetectionStrategy.OnPush,
})
Bilder über der Falz gezielt priorisieren — alle zu priorisieren ist dasselbe
wie keines. NgOptimizedImage verlangt dafür width und height:
<img [ngSrc]="part.image" width="125" height="125" [priority]="priority()" />
Im Netzwerk-Tab zuerst das Caching abschalten, sonst misst man den zweiten Besuch:
| Beobachtung | Einzuordnen als |
|---|---|
| die meisten Anfragen unter 100 KB | unauffällig |
| einzelne Anfragen über 1 MB | prüfen und begründen können |
| Anfragen im Megabyte-Bereich | in jedem Fall nachgehen |
| viele Bilder in der Typ-Ansicht | Kandidaten für Optimierung |
| eine Datei löst sehr viele Anfragen aus | Auslöser-Spalte ansehen |
Absolute Grenzwerte gibt es nicht — der Vergleich mit dem eigenen Normalfall zählt. Die Angular DevTools brauchen den Entwicklungsmodus, die Chrome-Werkzeuge den Produktionsmodus.
Modul: Performance
Bundle-Budgets
| Typ | Überwacht |
|---|---|
initial | was beim ersten Aufruf geladen wird |
anyComponentStyle | die CSS-Größe jeder einzelnen Komponente |
bundle | ein namentlich genanntes Bundle (braucht name) |
allScript | das gesamte JavaScript der Anwendung |
all | alles zusammen — JS, CSS, HTML |
any | jede einzelne Datei beliebigen Typs |
Mit baseline wird relativ statt absolut budgetiert — praktisch, weil es auf
„wie groß darf die App sein” keine allgemeine Antwort gibt:
{ "type": "initial", "baseline": "296kB",
"maximumWarning": "5%", "maximumError": "10%" }
Modul: Performance
Typische Fallen
- Strict-Schalter im falschen Block. Gültiges JSON, keine Fehlermeldung, keine Wirkung.
- Modernes Angular, alter Builder.
ng updateerlaubt das Überspringen des Vite-Wechsels — ein Projekt auf Angular 20 kann weiterhin webpack bauen. - Standalone-Migration von oben. Eine Elternkomponente zieht alle Kinder mit, die wiederum ihre Eltern. Bei den Blättern anfangen.
async-Pipe vergessen. Im Template steht dann der Objekt-Platzhalter statt der Daten.- Manuell abonnierte Observables ohne Zone.js. Kein
markForCheck, kein Re-Render — und keine Fehlermeldung. - Schreibbares Signal öffentlich gemacht. Jede Komponente kann den Zustand überschreiben; die Kapselung ist dahin.
- Signale im Signal mutieren. Ein verändertes Array bleibt dasselbe Array — die Änderung wird nicht bemerkt. Dasselbe gilt für OnPush-Inputs.
effectstattcomputed. Ein Effekt, der sein eigenes Signal setzt, läuft endlos.- Sheriff nur lokal. Eine Prüfung, die nicht in der CI läuft, ist eine Erinnerung. Und bei jeder Meldung die Regel aufzuweichen führt zur Whitelist, die alles erlaubt.
src-Ebene ohne Sheriff-Tag. Dann meldet schonmain.tseinen Verstoß.- Federation: Exportschlüssel ≠ Ladestring. Zeichenketten, nicht typgeprüft — der Fehler kommt erst beim Klick.
- Getrennte
dist-Ordner als Release-Unabhängigkeit lesen. Im Monorepo bleibt die Deploy-Kopplung bestehen: Eine fremde Änderung wird erst sichtbar, wenn das nutzende Projekt neu baut. - Budget angehoben statt Ursache gesucht. Nach einem Jahr steht die Zahl doppelt so hoch, und niemand weiß, wann.
- Nur
initialüberwacht. Lazy geladene Bereiche wachsen dann unbeobachtet. - Im falschen Modus gemessen. Angular DevTools im Produktionsbuild zeigen einen leeren Tab, Chrome-Profile im Dev-Build unbrauchbare Zahlen.
Alle Module des Seminars