Im sich entwickelnden Bereich des Angular State Management bieten Angular Signals einen einfacheren und intuitiveren Weg zur Verwaltung des Zustands im Vergleich zu traditionellen Lösungen wie Observables und RxJS. In diesem erweiterten Beitrag werden wir erkunden, wie Sie Angular Signals in einen Zustandsspeicher konvertieren können und das Konzept mit einem praktischen Beispiel veranschaulichen, das Signals mit Diensten und Observables integriert.
Wir werden durch die Verwaltung von Blog-Beiträgen als Zustand mithilfe von Signals gehen und sie über Observables zur Nutzung in einer Komponente verfügbar machen. Darüber hinaus werden wir die Funktionalität für bessere Leistung und Code-Klarheit verbessern.
Die Grundlagen: Konvertierung von Signals zu einem Zustandsspeicher
Beginnen wir mit der Konvertierung von Angular Signals in einen Zustandsspeicher, der ein Array von Blog-Beiträgen halten und verwalten kann. Der Schlüssel hier ist die Verwendung von Signals für den reaktiven Zustand und Observable, um den Zustand für Komponenten, die mit dem RxJS-Muster vertraut sind, bereitzustellen.
Im Folgenden sehen Sie ein Beispiel, wie die inject()-Funktion von Angular verwendet wird, um den Dienst zu erhalten und den Zustandsspeicher mithilfe eines Signals zu verwalten.
Erstes Beispiel:
import { inject, signal, WritableSignal, toObservable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { BlogPostDto, DefaultService } from './services/default.service'; // Angenommen, DTO und Dienst sind importiert
export class BlogService {
private blogService: DefaultService = inject(DefaultService);
// Schritt 1: Erklären Sie das WritableSignal als Zustandsspeicher für BlogPostDto[]
private blogStores: WritableSignal = signal([]);
// Schritt 2: Stellen Sie das Signal als Observable für die externe Nutzung bereit
blogs$: Observable;
constructor() {
// Konvertieren Sie das Signal in ein Observable
this.blogs$ = toObservable(this.blogStores.asReadonly());
// Rufen Sie nicht fetchBlogs() hier auf; es wird später explizit aufgerufen
}
// Schritt 3: Erstellen Sie eine Methode, um einen bestimmten Blog nach seinem Slug abzurufen
blogBySlug$ = (slug: string) => this.blogs$.pipe(
map(blogs => blogs.find((blog) => blog.slug === slug))
);
// Blogs Lazy abrufen, wenn sie explizit angefordert werden
fetchBlogs(): void {
this.blogService.getBlogs().subscribe({
next: (blogs: BlogPostDto[]) => {
this.blogStores.set(blogs);
}
});
}
}
Verbesserung des Codes:
Wir können einige Verbesserungen vornehmen, um den Code in Bezug auf Lesbarkeit, Leistung und Reaktivität zu optimieren.
1. Lazy Laden von Blogs:
Statt Blogs sofort im Konstruktor abzurufen, rufen wir fetchBlogs() nur dann auf, wenn es nötig ist. Dadurch werden unnötige API-Aufrufe vermieden, wenn die Daten nicht sofort verwendet werden.
2. Fehlerbehandlung:
Es ist wichtig, die Fehlerbehandlung im getBlogs()-Abonnement einzuschließen, um stille Fehler zu vermeiden, wenn die API fehlschlägt.
3. Reaktive Blogsuchen:
Anstatt die Slug-Suche über map zu leiten, können wir Signals effektiver nutzen, indem wir eine reaktive Suche direkt im Speicher erstellen, die sich aktualisiert, wenn neue Blog-Daten abgerufen werden.
Erweitertes Beispiel:
import { inject, signal, WritableSignal, toObservable, computed } from '@angular/core';
import { Observable, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { BlogPostDto, DefaultService } from './services/default.service';
export class BlogService {
private blogService: DefaultService = inject(DefaultService);
// Zustandssignal für Blogs erklären
private blogStores: WritableSignal = signal([]);
// Signal als Observable bereitstellen
blogs$: Observable;
constructor() {
// Konvertieren Sie das Signal in ein Observable zur externen Bereitstellung
this.blogs$ = toObservable(this.blogStores.asReadonly());
}
// Methode zum Abrufen von Blogs mit Fehlerbehandlung
fetchBlogs(): void {
this.blogService.getBlogs()
.pipe(
catchError(error => {
console.error('Fehler beim Abrufen der Blogs:', error);
return of([]); // Ein leeres Array im Fehlerfall zurückgeben
})
)
.subscribe({
next: (blogs: BlogPostDto[]) => {
this.blogStores.set(blogs); // Zustand mit neuen Blogs aktualisieren
}
});
}
// Reaktive Suche mit einem Signal, um einen Blog nach Slug zu finden
blogBySlug(slug: string): BlogPostDto | undefined {
const blogSignal = computed(() => this.blogStores().find(blog => blog.slug === slug));
return blogSignal(); // Gibt sofort den Blog zurück, falls gefunden, oder undefined, falls nicht
}
}
Erklärung der wichtigsten Änderungen:
1. Lazy Blog-Abruf:
In dieser Version wird fetchBlogs() explizit bei Bedarf aufgerufen, anstatt es im Konstruktor auszulösen. Dadurch wird der Dienst effizienter.
this.fetchBlogs(); // Wird explizit aufgerufen, nicht im Konstruktor
2. Fehlerbehandlung:
Wir haben einen catchError-Operator hinzugefügt, um API-Fehler zu behandeln, indem wir sie protokollieren und ein leeres Array zurückgeben, um zu verhindern, dass die App abstürzt.
.pipe(catchError(error => {
console.error('Fehler beim Abrufen der Blogs:', error);
return of([]); // Rückfall im Fehlerfall
}))
3. Reaktive Blogsuchen:
Durch die Verwendung der computed()-Funktion von Signals erstellen wir ein reaktives Signal, das direkt die Abhängigkeit vom slug verfolgt. Auf diese Weise wird, wenn die Blog-Liste aktualisiert wird, das Signal für den spezifischen Blog automatisch neu berechnet.
const blogSignal = computed(() => this.blogStores().find(blog => blog.slug === slug));
return blogSignal();
Anwendungsfall: Blogs in Komponenten bereitstellen
Jetzt, da wir unseren Zustandsspeicher eingerichtet haben, sehen wir, wie wir ihn in einer Angular-Komponente nutzen können.
Blog-Listenkomponente:
@Component({
selector: 'app-blog-list',
template: `
<div *ngFor="let blog of blogs$ | async">
<h2>{{ blog.title }}</h2>
<p>{{ blog.excerpt }}</p>
</div>
`
})
export class BlogListComponent implements OnInit {
blogs$: Observable;
constructor(private blogService: BlogService) {}
ngOnInit(): void {
// Rufen Sie explizit fetchBlogs auf, wenn die Komponente initialisiert wird
this.blogService.fetchBlogs();
// Abonnieren Sie den Blog-Zustandsspeicher
this.blogs$ = this.blogService.blogs$;
}
}
Blog-Detailkomponente:
@Component({
selector: 'app-blog-detail',
template: `
<h1>{{ blog?.title }}</h1>
<p>{{ blog?.content }}</p>
`
})
export class BlogDetailComponent implements OnInit {
blog: BlogPostDto | undefined;
constructor(private blogService: BlogService, private route: ActivatedRoute) {}
ngOnInit(): void {
const slug = this.route.snapshot.paramMap.get('slug')!;
this.blog = this.blogService.blogBySlug(slug);
}
}
Fazit
Durch die Konvertierung von Angular Signals in einen Zustandsspeicher profitieren wir von den Vorteilen sowohl einer feinkörnigen Reaktivität als auch der Kompatibilität mit RxJS-basierten Observable-Streams. Dieser Ansatz führt zu effizienterem State Management, weniger unnötigen Aktualisierungen und einem lesbareren Code. In dem erweiterten Beispiel haben wir auch Lazy Laden, Fehlerbehandlung und reaktive Signale eingeführt, um die Leistung und Benutzererfahrung zu optimieren.
Angular Signals sind eine vielversprechende Ergänzung zu den reaktiven Fähigkeiten von Angular und bieten eine einfachere, aber leistungsstarke Alternative zu komplexeren RxJS-Mustern. Experimentieren Sie mit ihnen in Ihren Angular-Projekten und erkunden Sie, wie sie Ihre Lösungen für das State Management optimieren können!
