Was ist Angular?
Angular ist ein vollständiges Application Framework, das von Google gepflegt wird. Während React eine Library ist, um die man sich selbst einen Stack zusammenstellt, liefert Angular den gesamten Stack mit: Komponenten, Routing, Formulare, HTTP, Dependency Injection, Testing und eine leistungsstarke CLI – alles darauf ausgelegt, nahtlos zusammenzuarbeiten. Es ist TypeScript-first und opinionated, was genau der Grund ist, warum große Teams darauf setzen.
Diese Vollständigkeit ist der zentrale Trade-off. Man nimmt eine steilere Lernkurve und mehr Framework-Konventionen in Kauf, erhält dafür aber Konsistenz, ein starkes Tooling und muss weniger Architektur-Entscheidungen von Grund auf selbst treffen. Für einen kleinen Prototyp kann sich das schwerfällig anfühlen; für eine fünf Jahre alte Enterprise-Codebase ist es oft eine Erleichterung.
Komponenten und Templates
Eine Komponente ist eine TypeScript-Klasse, die mit @Component dekoriert ist und mit einem HTML-Template sowie optionalen Styles gekoppelt wird.
// user-card.component.ts
import { Component, Input } from "@angular/core";
@Component({
selector: "app-user-card",
standalone: true,
template: `
<article class="card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</article>
`,
})
export class UserCardComponent {
@Input({ required: true }) user!: { name: string; email: string };
}
Das Template verwendet {{ }} für die Interpolation und Directives für die Logik. Modernes Angular nutzt integrierte Control-Flow-Blöcke anstelle der älteren strukturellen Directives.
<!-- list.html -->
@if (users.length > 0) {
<ul>
@for (user of users; track user.id) {
<li>{{ user.name }}</li>
}
</ul>
} @else {
<p>No users yet.</p>
}
Der track-Ausdruck in @for übernimmt die gleiche Rolle wie ein Key in React oder Vue: Er teilt Angular mit, wie Elemente zwischen Updates zugeordnet werden sollen.
Data Binding
Angular-Templates unterstützen vier Arten von Binding.
- Interpolation
{{ value }}rendert einen Wert als Text. - Property Binding
[src]="url"setzt eine Element-Property über einen Ausdruck. - Event Binding
(click)="save()"ruft eine Methode auf, wenn ein Event ausgelöst wird. - Two-way Binding
[(ngModel)]="name"kombiniert beides für Formular-Inputs.
<!-- bindings.html -->
<img [src]="user.avatar" [alt]="user.name" />
<button (click)="save()">Save</button>
<input [(ngModel)]="query" />
Two-way Binding ist praktisch für einfache Formulare, aber für alles, was eine Validierung benötigt, sind Reactive Forms in der Regel die bessere Wahl.
Signals
Signals sind das moderne Reaktivitäts-Primitiv von Angular. Ein Signal hält einen Wert und benachrichtigt interessierte Konsumenten, wenn sich dieser ändert. Dadurch kann das Framework gezielt nur die Teile der View aktualisieren, die von diesem Signal abhängen.
// cart.component.ts
import { Component, signal, computed } from "@angular/core";
@Component({
selector: "app-cart",
standalone: true,
template: `<p>Total: {{ total() }}</p>`,
})
export class CartComponent {
items = signal([{ price: 10 }, { price: 20 }]);
total = computed(() =>
this.items().reduce((sum, i) => sum + i.price, 0),
);
add(price: number) {
this.items.update((items) => [...items, { price }]);
}
}
computed leitet einen Wert aus anderen Signals ab und cached diesen, bis sich eine Abhängigkeit ändert. effect führt einen Side Effect aus, wenn sich die gelesenen Signals ändern. Zusammen decken sie den Großteil der Anforderungen von Komponenten ab.
RxJS und Observables
Angular verfügt über eine tiefe RxJS-Integration. Ein Observable repräsentiert einen Stream von Werten über einen Zeitraum hinweg, und Operatoren ermöglichen es, diese Streams zu transformieren und zu kombinieren.
// search.component.ts
import { Component } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
import { debounceTime, distinctUntilChanged, switchMap } from "rxjs";
@Component({ /* ... */ })
export class SearchComponent {
private query$ = new Subject<string>();
results = toSignal(
this.query$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap((q) => this.api.search(q)),
),
{ initialValue: [] },
);
}
Hier spielt RxJS seine Stärken aus: Debouncing, das Abbrechen veralteter Requests und das Kombinieren von Streams lassen sich mit einfachen Promises nur schwer ausdrücken. Für einfachen synchronen State sind Signals einfacher. Modernes Angular erlaubt es, beides zu mischen und mithilfe von toSignal und toObservable zu konvertieren.
Services und Dependency Injection
Gemeinsame Logik und Daten gehören in Services, nicht in Komponenten. Dependency Injection stellt diese dort bereit, wo sie benötigt werden.
// users.service.ts
import { Injectable, inject } from "@angular/core";
import { HttpClient } from "@angular/common/http";
@Injectable({ providedIn: "root" })
export class UsersService {
private http = inject(HttpClient);
getAll() {
return this.http.get<User[]>("/api/users");
}
}
// users.component.ts
export class UsersComponent {
private users = inject(UsersService);
list = toSignal(this.users.getAll(), { initialValue: [] });
}
providedIn: "root" registriert ein Singleton für die gesamte App. DI macht es einfach, Services in Tests auszutauschen, und sorgt dafür, dass Komponenten auf die Präsentation fokussiert bleiben.
Routing
Der Angular Router bildet URLs auf Komponenten ab und unterstützt verschachtelte Routen, Guards, Resolver und Lazy Loading.
// app.routes.ts
import { Routes } from "@angular/router";
export const routes: Routes = [
{ path: "", component: HomeComponent },
{ path: "users/:id", component: UserProfileComponent },
{
path: "admin",
loadChildren: () => import("./admin/admin.routes").then((m) => m.ADMIN_ROUTES),
},
];
Durch das Lazy Loading von Funktionsbereichen bleibt das initiale Bundle klein, und Guards ermöglichen es, die Navigation zu blockieren, bis eine bestimmte Bedingung, wie etwa die Authentifizierung, erfüllt ist.
Formulare und HTTP
Angular bietet zwei Ansätze für Formulare. Reactive forms definieren das Modell und die Validatoren in der Klasse, was gut skalierbar und testbar ist. Template-driven forms nutzen Direktiven wie ngModel und sind für einfache Anwendungsfälle schneller umzusetzen.
// signup.component.ts
form = new FormGroup({
email: new FormControl("", [Validators.required, Validators.email]),
password: new FormControl("", [Validators.required, Validators.minLength(8)]),
});
submit() {
if (this.form.invalid) return;
this.http.post("/api/signup", this.form.getRawValue()).subscribe();
}
HttpClient gibt Observables zurück, sodass Anfragen mithilfe von RxJS-Operatoren für Retries, Stornierungen und die Fehlerbehandlung kombiniert werden können.
Best Practices
- Bevorzuge für neuen Code standalone components und signals.
- Halte components auf die Präsentation fokussiert und lagere die Logik in services aus.
- Nutze reactive forms für alles, was Validierungen oder dynamische Felder benötigt.
- Nutze Lazy-Loading für Feature-Bereiche, um das initiale Bundle klein zu halten.
- Verwende
OnPushchange detection oder signals, um unnötige Checks zu begrenzen. - Tracke Items in
@formit einem stabilen, eindeutigen Wert. - Melde dich von langlebigen observables ab oder nutze die async pipe und
toSignal.
Häufige Fehler
- Business-Logik in Komponenten statt in Services zu platzieren.
- Manuelles Abonnieren (Subscribe) ohne anschließendes Unsubscribe, was zu Memory Leaks führt.
- Auf RxJS zurückzugreifen, wenn ein Signal einfacher wäre.
- Den Wert eines Signals direkt zu mutieren, anstatt
setoderupdatezu verwenden. - Ein einziges riesiges NgModule zu bauen, anstatt auf lazy-loaded Feature-Routes zu setzen.
- Angular wie React zu behandeln und gegen die Struktur des Frameworks zu arbeiten.
Wie geht es weiter?
Angular belohnt Geduld mit einer Struktur, die über Jahre hinweg Bestand hat. Vertiefe deine TypeScript-Kenntnisse, verstehe die Unterschiede zu React und Vue und lerne das Node.js-Tooling kennen, das hinter der CLI steckt. Baue anschließend ein Feature mit Routing, einem Service und einem reaktiven Formular, um zu sehen, wie die einzelnen Teile ineinandergreifen.