Skip to main content Skills Marketplace Découvrez et explorez les compétences IA créées par la communauté.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Copier le promptAfficher les détails du prompt Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
npx skills add https://github.com/TheBushidoCollective/han --skill angular-rxjs-patternsLa commande reste sur une seule ligne. Faites défiler horizontalement pour la vérifier avant de la copier.
Vous préférez une copie locale ? Téléchargez les fichiers actuellement disponibles dans SkillsMP.
Télécharger Zip Téléchargement... Métiers associés SOC
Basé sur la classification professionnelle SOC
name angular-rxjs-patterns user-invocable false description Use when handling async operations in Angular applications with observables, operators, and subjects. allowed-tools ["Bash","Read"]
Angular RxJS Patterns
Master RxJS in Angular for handling async operations, data streams,
and reactive programming patterns.
Observable Creation
Basic Observable Creation
import { Observable , of , from , interval, fromEvent } from 'rxjs' ;
const numbers$ = of (1 , 2 , 3 , 4 , 5 );
const fromArray$ = from ([1 , 2 , 3 ]);
const fromPromise$ = ( ( ));
timer$ = ( );
clicks$ = ( , );
custom$ = ( {
subscriber. ( );
subscriber. ( );
subscriber. ();
});
from
fetch
'/api/data'
const
interval
1000
const
fromEvent
document
'click'
const
new
Observable
subscriber =>
next
1
next
2
complete
HttpClient Observables import { HttpClient } from '@angular/common/http' ;
import { Injectable , inject } from '@angular/core' ;
import { Observable } from 'rxjs' ;
@Injectable ({
providedIn : 'root'
})
export class DataService {
private readonly http = inject (HttpClient );
getData (): Observable <Data []> {
return this .http .get <Data []>('/api/data' );
}
getItem (id : string ): Observable <Data > {
return this .http .get <Data >(`/api/data/${id} ` );
}
createItem (data : Data ): Observable <Data > {
return this .http .post <Data >('/api/data' , data);
}
updateItem (id : string , data : Data ): Observable <Data > {
return this .http .put <Data >(`/api/data/${id} ` , data);
}
deleteItem (id : string ): Observable <void > {
return this .http .delete <void >(`/api/data/${id} ` );
}
}
Common Operators
Transformation Operators import { map, pluck, switchMap, mergeMap, concatMap } from 'rxjs/operators' ;
import { of } from 'rxjs' ;
const numbers$ = of (1 , 2 , 3 ).pipe (
map (n => n * 2 )
);
const users$ = of (
{ name : 'John' , age : 30 },
{ name : 'Jane' , age : 25 }
).pipe (
map (user => user.name )
);
searchControl.valueChanges .pipe (
switchMap (term => this .searchService .search (term))
).subscribe (results => {
this .results = results;
});
const ids$ = of (1 , 2 , 3 );
ids$.pipe (
mergeMap (id => this .getUser (id))
).subscribe ();
ids$.pipe (
concatMap (id => this .getUser (id))
).subscribe ();
Filtering Operators import { filter, take, takeUntil, takeWhile, distinctUntilChanged } from 'rxjs/operators' ;
of (1 , 2 , 3 , 4 , 5 ).pipe (
filter (n => n % 2 === 0 )
);
interval (1000 ).pipe (
take (5 )
);
const destroy$ = new Subject ();
source$.pipe (
takeUntil (destroy$)
).subscribe ();
of (1 , 1 , 2 , 2 , 3 , 3 ).pipe (
distinctUntilChanged ()
);
Combination Operators import { combineLatest, merge, concat, forkJoin, zip } from 'rxjs' ;
import { startWith } from 'rxjs/operators' ;
combineLatest ([
this .user$ ,
this .settings$
]).pipe (
map (([user, settings] ) => ({ user, settings }))
).subscribe ();
merge (
this .clicks$ ,
this .hovers$
).subscribe ();
concat (
this .loadUser$ ,
this .loadSettings$
).subscribe ();
forkJoin ({
user : this .getUser (),
posts : this .getPosts (),
comments : this .getComments ()
}).subscribe (({ user, posts, comments } ) => {
});
zip (
of (1 , 2 , 3 ),
of ('a' , 'b' , 'c' )
).pipe (
map (([num, letter] ) => `${num} ${letter} ` )
);
Utility Operators import { tap, delay, debounceTime, throttleTime, distinctUntilChanged } from 'rxjs/operators' ;
source$.pipe (
tap (value => console .log ('Value:' , value)),
map (value => value * 2 )
);
of (1 , 2 , 3 ).pipe (
delay (1000 )
);
searchControl.valueChanges .pipe (
debounceTime (300 )
);
clicks$.pipe (
throttleTime (1000 )
);
input$.pipe (
distinctUntilChanged ()
);
Error Handling
catchError - Handle Errors import { catchError } from 'rxjs/operators' ;
import { of , EMPTY , throwError } from 'rxjs' ;
this .http .get ('/api/data' ).pipe (
catchError (error => {
console .error ('Error:' , error);
return of ([]);
})
);
source$.pipe (
catchError (() => EMPTY )
);
source$.pipe (
catchError (error => {
console .error ('Error:' , error);
return throwError (() => new Error ('Custom error' ));
})
);
source$.pipe (
catchError (error => {
if (error.status === 404 ) {
return of (null );
}
return throwError (() => error);
})
);
retry and retryWhen import { retry, retryWhen, delay, take } from 'rxjs/operators' ;
this .http .get ('/api/data' ).pipe (
retry (3 )
);
this .http .get ('/api/data' ).pipe (
retryWhen (errors =>
errors.pipe (
delay (1000 ),
take (3 )
)
)
);
this .http .get ('/api/data' ).pipe (
retryWhen (errors =>
errors.pipe (
mergeMap ((error, index ) => {
if (index >= 3 ) {
return throwError (() => error);
}
const delayMs = Math .pow (2 , index) * 1000 ;
return of (error).pipe (delay (delayMs));
})
)
)
);
Subscription Management
takeUntilDestroyed (Preferred) Use takeUntilDestroyed() from @angular/core/rxjs-interop — no ngOnDestroy
needed, and no manual Subject<void> to manage:
import { Component , inject } from '@angular/core' ;
import { takeUntilDestroyed } from '@angular/core/rxjs-interop' ;
@Component ({
selector : 'app-my-component' ,
standalone : true
})
export class MyComponent {
private readonly dataService = inject (DataService );
constructor ( ) {
this .dataService .data$ .pipe (
takeUntilDestroyed ()
).subscribe (data => {
this .data = data;
});
this .dataService .other$ .pipe (
takeUntilDestroyed ()
).subscribe (other => {
this .other = other;
});
}
}
DestroyRef for Manual Subscriptions When subscribing imperatively outside the constructor (e.g. on user action),
use DestroyRef directly:
import { Component , inject } from '@angular/core' ;
import { DestroyRef } from '@angular/core' ;
@Component ({
selector : 'app-my-component' ,
standalone : true
})
export class MyComponent {
readonly #destroyRef = inject (DestroyRef );
startPolling ( ) {
const sub = interval (5000 ).subscribe (() => this .poll ());
this .#destroyRef.onDestroy (() => sub.unsubscribe ());
}
}
Async Pipe (No Manual Unsubscribe) import { Component , inject } from '@angular/core' ;
import { AsyncPipe } from '@angular/common' ;
import { Observable } from 'rxjs' ;
@Component ({
selector : 'app-user-list' ,
standalone : true ,
imports : [AsyncPipe ],
template : `
@if (users$ | async; as users) {
@for (user of users; track user.id) {
<div>{{ user.name }}</div>
}
}
@if (loading$ | async) {
<div>Loading...</div>
}
@if (error$ | async; as error) {
<div>Error: {{ error }}</div>
}
`
})
export class UserListComponent {
private readonly userService = inject (UserService );
users$ : Observable <User []> = this .userService .getUsers ();
loading$ : Observable <boolean > = this .userService .loading$ ;
error$ : Observable <string | null > = this .userService .error$ ;
}
Subjects
Subject - Multicast import { Subject } from 'rxjs' ;
const subject = new Subject <number >();
subject.subscribe (val => console .log ('A:' , val));
subject.subscribe (val => console .log ('B:' , val));
subject.next (1 );
subject.next (2 );
BehaviorSubject - Current Value import { BehaviorSubject } from 'rxjs' ;
const subject = new BehaviorSubject <number >(0 );
subject.subscribe (val => console .log ('A:' , val));
subject.next (1 );
subject.next (2 );
subject.subscribe (val => console .log ('B:' , val));
@Injectable ({
providedIn : 'root'
})
export class StateService {
private stateSubject = new BehaviorSubject <State >(initialState);
state$ = this .stateSubject .asObservable ();
updateState (newState : State ) {
this .stateSubject .next (newState);
}
get currentState (): State {
return this .stateSubject .value ;
}
}
ReplaySubject - Buffer Values import { ReplaySubject } from 'rxjs' ;
const subject = new ReplaySubject <number >(2 );
subject.next (1 );
subject.next (2 );
subject.next (3 );
subject.subscribe (val => console .log ('A:' , val));
subject.next (4 );
subject.subscribe (val => console .log ('B:' , val));
AsyncSubject - Last Value on Complete import { AsyncSubject } from 'rxjs' ;
const subject = new AsyncSubject <number >();
subject.subscribe (val => console .log ('A:' , val));
subject.next (1 );
subject.next (2 );
subject.next (3 );
subject.complete ();
Hot vs Cold Observables
Cold Observable - Unicast
const cold$ = interval (1000 );
cold$.subscribe (val => console .log ('A:' , val));
setTimeout (() => {
cold$.subscribe (val => console .log ('B:' , val));
}, 2000 );
Hot Observable - Multicast import { Subject , interval } from 'rxjs' ;
import { share, shareReplay } from 'rxjs/operators' ;
const subject = new Subject ();
const source$ = interval (1000 );
source$.subscribe (subject);
subject.subscribe (val => console .log ('A:' , val));
setTimeout (() => {
subject.subscribe (val => console .log ('B:' , val));
}, 2000 );
const shared$ = interval (1000 ).pipe (share ());
shared$.subscribe (val => console .log ('A:' , val));
setTimeout (() => {
shared$.subscribe (val => console .log ('B:' , val));
}, 2000 );
const cached$ = this .http .get ('/api/data' ).pipe (
shareReplay (1 )
);
cached$.subscribe ();
cached$.subscribe ();
RxJS in Services
Data Service with State Use signals for synchronous state. Use observables for async HTTP operations,
converting them to signals with toSignal() when needed in templates:
import { Injectable , inject, signal, computed } from '@angular/core' ;
import { HttpClient } from '@angular/common/http' ;
import { Observable , catchError, finalize, tap } from 'rxjs' ;
import { of } from 'rxjs' ;
@Injectable ({
providedIn : 'root'
})
export class UserService {
private readonly http = inject (HttpClient );
readonly users = signal<User []>([]);
readonly loading = signal (false );
readonly error = signal<string | null >(null );
readonly hasUsers = computed (() => this .users ().length > 0 );
loadUsers (): void {
this .loading .set (true );
this .error .set (null );
this .http .get <User []>('/api/users' ).pipe (
tap (users => this .users .set (users)),
catchError (err => {
this .error .set (err.message );
return of ([]);
}),
finalize (() => this .loading .set (false ))
).subscribe ();
}
getUser (id : string ): Observable <User > {
return this .http .get <User >(`/api/users/${id} ` );
}
}
Template with signals (no async pipe needed for signal state):
@Component ({
selector : 'app-user-list' ,
standalone : true ,
template : `
@if (userService.loading()) {
<div>Loading...</div>
}
@if (userService.error(); as error) {
<div>Error: {{ error }}</div>
}
@for (user of userService.users(); track user.id) {
<div>{{ user.name }}</div>
}
`
})
export class UserListComponent {
protected readonly userService = inject (UserService );
constructor ( ) {
this .userService .loadUsers ();
}
}
Search Service with Debounce import { Injectable , inject } from '@angular/core' ;
import { HttpClient } from '@angular/common/http' ;
import { Observable , Subject , of } from 'rxjs' ;
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators' ;
@Injectable ({
providedIn : 'root'
})
export class SearchService {
private readonly http = inject (HttpClient );
private readonly searchTerms = new Subject <string >();
readonly results$ : Observable <SearchResult []> = this .searchTerms .pipe (
debounceTime (300 ),
distinctUntilChanged (),
switchMap (term => this .search (term))
);
search (term : string ): Observable <SearchResult []> {
if (!term.trim ()) {
return of ([]);
}
return this .http .get <SearchResult []>(`/api/search?q=${term} ` );
}
setSearchTerm (term : string ): void {
this .searchTerms .next (term);
}
}
Testing RxJS
Testing Observables import { TestBed } from '@angular/core/testing' ;
import { HttpClientTestingModule , HttpTestingController } from '@angular/common/http/testing' ;
describe ('UserService' , () => {
let service : UserService ;
let httpMock : HttpTestingController ;
beforeEach (() => {
TestBed .configureTestingModule ({
imports : [HttpClientTestingModule ],
providers : [UserService ]
});
service = TestBed .inject (UserService );
httpMock = TestBed .inject (HttpTestingController );
});
afterEach (() => {
httpMock.verify ();
});
it ('should fetch users' , () => {
const mockUsers = [{ id : 1 , name : 'John' }];
service.getUsers ().subscribe (users => {
expect (users).toEqual (mockUsers);
});
const req = httpMock.expectOne ('/api/users' );
expect (req.request .method ).toBe ('GET' );
req.flush (mockUsers);
});
});
Testing with Marble Diagrams import { TestScheduler } from 'rxjs/testing' ;
describe ('Marble tests' , () => {
let scheduler : TestScheduler ;
beforeEach (() => {
scheduler = new TestScheduler ((actual, expected ) => {
expect (actual).toEqual (expected);
});
});
it ('should debounce' , () => {
scheduler.run (({ cold, expectObservable } ) => {
const source$ = cold ('-a-b-c|' );
const expected = '-----c|' ;
const result$ = source$.pipe (debounceTime (20 ));
expectObservable (result$).toBe (expected);
});
});
});
When to Use This Skill Use angular-rxjs-patterns when building modern, production-ready
applications that require:
Complex async data flows
Real-time updates and streaming data
Efficient HTTP request management
Form input handling with debouncing
State management with observables
Error handling and retry logic
Combining multiple async sources
Memory-safe subscription management
RxJS Best Practices in Angular
Use takeUntilDestroyed() - Automatic cleanup without ngOnDestroy
Use signals for state - BehaviorSubject → signal() for synchronous state
Use async pipe for observables in templates - Automatic subscription management
Use @if/@for control flow - Replaces *ngIf/*ngFor
Use inject() - Cleaner than constructor injection
shareReplay for caching - Avoid duplicate HTTP requests
debounceTime for inputs - Reduce API calls
switchMap for cancellation - Cancel old requests
catchError for errors - Always handle errors
Test observables properly - Use marble diagrams
Common RxJS Mistakes
Not unsubscribing - Memory leaks
Nested subscriptions - Callback hell
Not using operators - Imperative instead of declarative
Subscribing in services - Return observables instead
Not handling errors - Silent failures
Using Subject incorrectly - Prefer BehaviorSubject for state
Not using shareReplay - Duplicate HTTP requests
Forgetting to complete subjects - Memory leaks
Using subscribe in templates - Use async pipe
Not understanding hot vs cold - Unexpected behavior
Resources