1@Component({
2 template: \`
3 <input #searchInput placeholder="Search for a samurai...">
4 @for (result of searchResults$ | async; track result.id) {
5 <div>{{ result.name }}</div>
6 }
7 \`
8})
9export class SearchComponent implements AfterViewInit {
10 @ViewChild('searchInput') searchInput!: ElementRef;
11 searchResults$!: Observable<Samurai[]>;
12
13 private http = inject(HttpClient);
14
15 ngAfterViewInit(): void {
16 this.searchResults$ = fromEvent<InputEvent>(
17 this.searchInput.nativeElement,
18 'input'
19 ).pipe(
20 map(event => (event.target as HTMLInputElement).value),
21 debounceTime(300), // Wait 300ms
22 distinctUntilChanged(), // Ignore the same values
23 filter(query => query.length >= 2), // Min 2 characters
24 switchMap(query => // Cancel previous query
25 this.http.get<Samurai[]>(\`/api/search?q=\${query}\`).pipe(
26 catchError(() => of([])) // Error handling
27 )
28 )
29 );
30 }
31}1@Injectable({ providedIn: 'root' })
2export class StatusService {
3 private http = inject(HttpClient);
4
5 getStatusWithPolling(): Observable<Status> {
6 return interval(5000).pipe( // Every 5 seconds
7 startWith(0), // Immediately on start
8 switchMap(() =>
9 this.http.get<Status>('/api/status').pipe(
10 retry({
11 count: 3,
12 delay: 1000 // Retry 3 times every second
13 }),
14 catchError(error => {
15 console.error('Status check failed:', error);
16 return of({ status: 'unknown', error: true });
17 })
18 )
19 ),
20 shareReplay(1) // Share result between subscribers
21 );
22 }
23}1@Injectable({ providedIn: 'root' })
2export class CachedDataService {
3 private http = inject(HttpClient);
4 private cache$ = new Map<string, Observable<any>>();
5
6 getData<T>(url: string): Observable<T> {
7 if (!this.cache$.has(url)) {
8 this.cache$.set(url,
9 this.http.get<T>(url).pipe(
10 shareReplay({ bufferSize: 1, refCount: true }),
11 tap({
12 error: () => this.cache$.delete(url)
13 })
14 )
15 );
16 }
17 return this.cache$.get(url)!;
18 }
19
20 invalidateCache(url: string): void {
21 this.cache$.delete(url);
22 }
23}1getSamuraiWithWeapons(id: number): Observable<SamuraiWithWeapons> {
2 return this.http.get<Samurai>(\`/api/samurai/\${id}\`).pipe(
3 switchMap(samurai =>
4 forkJoin({
5 weapons: this.http.get<Weapon[]>(\`/api/samurai/\${id}/weapons\`),
6 skills: this.http.get<Skill[]>(\`/api/samurai/\${id}/skills\`)
7 }).pipe(
8 map(({ weapons, skills }) => ({
9 ...samurai,
10 weapons,
11 skills
12 }))
13 )
14 )
15 );
16}| Use Case | Signals | RxJS | |----------|---------|------| | Component state | ✅ | ❌ | | Simple derived calculations | ✅ computed() | ❌ | | HTTP requests | ❌ | ✅ | | Debounce/throttle | ❌ | ✅ | | Multiple transformations | ❌ | ✅ | | Combining streams | ❌ | ✅ |
1// Integration - the best of both worlds
2@Component({...})
3export class HybridComponent {
4 private http = inject(HttpClient);
5
6 // Signal for UI state
7 isLoading = signal(false);
8 selectedId = signal<number | null>(null);
9
10 // RxJS for HTTP
11 samurai$ = toObservable(this.selectedId).pipe(
12 filter(id => id !== null),
13 tap(() => this.isLoading.set(true)),
14 switchMap(id => this.http.get<Samurai>(\`/api/samurai/\${id}\`)),
15 tap(() => this.isLoading.set(false))
16 );
17
18 // Observable → Signal
19 samurai = toSignal(this.samurai$);
20}