RxJS dla Angular Developer - Kompletny Przewodnik Rekrutacyjny 2026
Jeśli aplikujesz na stanowisko Angular Developer, RxJS nie jest opcjonalny - to absolutny fundament. Angular opiera cały swój ekosystem na reaktywności: HTTP Client zwraca Observable, Router emituje eventy jako strumienie, Reactive Forms to Observable pod maską. Kandydaci, którzy nie rozumieją RxJS, odpadają na pierwszych pytaniach technicznych.
W tym przewodniku przejdziemy przez wszystko, co musisz wiedzieć o RxJS na rozmowę Angular - od podstaw Observable, przez kluczowe operatory, po zaawansowane wzorce zarządzania stanem. Każda sekcja zawiera konkretne pytania z odpowiedziami, które usłyszysz na rozmowie.
Dlaczego RxJS jest krytyczny dla Angular
Zanim zagłębimy się w szczegóły, musisz zrozumieć dlaczego RxJS i Angular są nierozłączne:
| Obszar Angular | Użycie RxJS |
|---|---|
| HttpClient | Wszystkie requesty zwracają Observable |
| Router | Events, params, queryParams jako Observable |
| Reactive Forms | valueChanges, statusChanges |
| @Output() | EventEmitter to Subject |
| AsyncPipe | Natywna obsługa Observable w szablonach |
| State Management | NgRx, NGXS oparte na RxJS |
Jeśli przyjdziesz na rozmowę Angular mówiąc "znam trochę RxJS", to jak powiedzenie "znam trochę JavaScript" na rozmowę frontend. RxJS to nie dodatek - to sposób myślenia o danych w Angular.
Observable - Podstawa wszystkiego
Odpowiedź w 30 sekund
Kiedy rekruter zapyta "Czym jest Observable?":
Observable to strumień danych, który może emitować wiele wartości w czasie. W przeciwieństwie do Promise, który zwraca jedną wartość i kończy, Observable może emitować ciągle - jak zdarzenia użytkownika czy WebSocket. Observable jest leniwy - nie wykonuje się dopóki ktoś nie zasubskrybuje. I co kluczowe - można go anulować przez unsubscribe, co jest fundamentalne dla zarządzania pamięcią w Angular.
Observable vs Promise - co odpowiedzieć
To pytanie pada na każdej rozmowie Angular. Przygotuj się na tabelę:
| Cecha | Observable | Promise |
|---|---|---|
| Wartości | 0, 1 lub wiele | Dokładnie 1 |
| Ewaluacja | Lazy (po subscribe) | Eager (od razu) |
| Anulowanie | Tak (unsubscribe) | Nie |
| Operatory | 100+ operatorów RxJS | then/catch/finally |
| W Angular | HttpClient, Router, Forms | Rzadko używane |
// Observable - może emitować wiele wartości, można anulować
import { interval } from 'rxjs';
import { take } from 'rxjs/operators';
const observable$ = interval(1000).pipe(take(5));
const subscription = observable$.subscribe(
value => console.log('Tick:', value)
);
// Anuluj po 2.5 sekundach - zostanie tylko 0, 1, 2
setTimeout(() => subscription.unsubscribe(), 2500);
// Promise - jedna wartość, nie można anulować
const promise = fetch('/api/users');
// promise się wykona niezależnie od tego czy używasz wyniku
Rekruterzy często pytają: "Kiedy użyłbyś Promise zamiast Observable w Angular?"
Odpowiedź: Prawie nigdy. Angular HttpClient zwraca Observable. Jedyny przypadek to integracja z zewnętrznymi bibliotekami bazującymi na Promise (np. stare SDK). Wtedy używasz from(promise) do konwersji na Observable.
Operatory - serce RxJS
Znajomość operatorów odróżnia początkującego od doświadczonego Angular developera. Nie musisz znać wszystkich 100+, ale te poniżej to absolutne minimum.
map, filter, tap - podstawy
import { of } from 'rxjs';
import { map, filter, tap } from 'rxjs/operators';
// Dane użytkowników z API
of({ id: 1, name: 'Jan', age: 25 }, { id: 2, name: 'Anna', age: 17 })
.pipe(
tap(user => console.log('Przed filtrem:', user)), // Debug bez modyfikacji
filter(user => user.age >= 18), // Tylko pełnoletni
map(user => user.name.toUpperCase()) // Transformuj na wielkie litery
)
.subscribe(name => console.log('Wynik:', name));
// Output: Wynik: JAN
Rekruter może zapytać: "Jaka jest różnica między map a tap?"
map transformuje wartość - zwraca nową wartość która idzie dalej w strumieniu. tap to side effect - wykonuje operację (logging, debug) ale nie zmienia wartości. Zawsze zwraca to co dostał.
switchMap vs mergeMap vs concatMap
To jedno z najczęstszych pytań na rozmowach Angular. Każdy z tych operatorów mapuje wartość na nowy Observable, ale różnią się zachowaniem:
import { fromEvent, interval } from 'rxjs';
import { switchMap, mergeMap, concatMap, take } from 'rxjs/operators';
const clicks$ = fromEvent(document, 'click');
// SWITCHMAP - anuluje poprzednią subskrypcję
// Idealne do: wyszukiwania, HTTP gdzie liczy się ostatni wynik
clicks$.pipe(
switchMap(() => interval(1000).pipe(take(3)))
).subscribe(x => console.log('switchMap:', x));
// Kliknięcie anuluje poprzedni interval i zaczyna nowy
// MERGEMAP - równoległe wykonanie, bez anulowania
// Idealne do: zapisywania wielu elementów, operacji gdzie potrzebujesz wszystkich wyników
clicks$.pipe(
mergeMap(() => interval(1000).pipe(take(3)))
).subscribe(x => console.log('mergeMap:', x));
// Każde kliknięcie dodaje nowy interval, wszystkie działają równolegle
// CONCATMAP - kolejka, jeden po drugim
// Idealne do: operacji które muszą być sekwencyjne
clicks$.pipe(
concatMap(() => interval(1000).pipe(take(3)))
).subscribe(x => console.log('concatMap:', x));
// Czeka aż poprzedni interval się skończy zanim zacznie następny
Praktyczna reguła: W 90% przypadków w Angular używasz switchMap - dla HTTP requests, wyszukiwania, nawigacji. mergeMap gdy naprawdę potrzebujesz równoległości. concatMap gdy kolejność jest krytyczna.
debounceTime i distinctUntilChanged
Ten duet pojawia się w każdej aplikacji Angular z wyszukiwaniem:
import { fromEvent } from 'rxjs';
import { debounceTime, distinctUntilChanged, map, switchMap } from 'rxjs/operators';
@Component({
template: `<input #searchInput type="text" placeholder="Szukaj...">`
})
export class SearchComponent implements AfterViewInit {
@ViewChild('searchInput') searchInput: ElementRef;
ngAfterViewInit() {
fromEvent(this.searchInput.nativeElement, 'input').pipe(
map((event: Event) => (event.target as HTMLInputElement).value),
debounceTime(300), // Czekaj 300ms po ostatnim znaku
distinctUntilChanged(), // Ignoruj jeśli wartość się nie zmieniła
switchMap(term => this.searchService.search(term))
).subscribe(results => {
this.results = results;
});
}
}
Rekruter może zapytać: "Po co distinctUntilChanged?"
Wyobraź sobie: użytkownik wpisuje "ang", potem usuwa "g" i znów wpisuje "g". Wartość wraca do "ang". Bez distinctUntilChanged wysłałbyś dwa identyczne requesty. Z nim - tylko jeden.
Subjects - emitowanie wartości
Subjects to Observable które możesz kontrolować z zewnątrz - ręcznie emitować wartości. W Angular są kluczowe do komunikacji między komponentami i zarządzania stanem.
Rodzaje Subjects
import { Subject, BehaviorSubject, ReplaySubject, AsyncSubject } from 'rxjs';
// SUBJECT - podstawowy, nie ma wartości początkowej
const subject = new Subject<number>();
subject.subscribe(x => console.log('Sub A:', x));
subject.next(1); // Sub A: 1
subject.next(2); // Sub A: 2
// BEHAVIORSUBJECT - przechowuje ostatnią wartość
// Idealne do: current user, app settings, selected item
const behavior = new BehaviorSubject<string>('początkowa wartość');
behavior.subscribe(x => console.log('Behavior A:', x)); // Od razu: początkowa wartość
behavior.next('nowa wartość');
behavior.subscribe(x => console.log('Behavior B:', x)); // Od razu: nowa wartość
// REPLAYSUBJECT - przechowuje N ostatnich wartości
// Idealne do: cache, historia akcji
const replay = new ReplaySubject<number>(3); // Pamiętaj 3 ostatnie
replay.next(1);
replay.next(2);
replay.next(3);
replay.next(4);
replay.subscribe(x => console.log('Replay:', x)); // Od razu: 2, 3, 4
// ASYNCSUBJECT - emituje tylko ostatnią wartość i tylko po complete
const async = new AsyncSubject<string>();
async.next('a');
async.next('b');
async.next('c');
async.subscribe(x => console.log('Async:', x)); // Nic jeszcze...
async.complete(); // Teraz: c
BehaviorSubject w serwisach Angular
To najpopularniejszy pattern do state management w Angular:
@Injectable({ providedIn: 'root' })
export class UserService {
// Private BehaviorSubject - nie można modyfikować z zewnątrz
private currentUserSubject = new BehaviorSubject<User | null>(null);
// Public Observable - komponenty mogą subskrybować
currentUser$ = this.currentUserSubject.asObservable();
// Getter dla synchronicznego dostępu
get currentUser(): User | null {
return this.currentUserSubject.getValue();
}
login(credentials: Credentials): Observable<User> {
return this.http.post<User>('/api/login', credentials).pipe(
tap(user => this.currentUserSubject.next(user))
);
}
logout(): void {
this.currentUserSubject.next(null);
}
}
// W komponencie
@Component({
template: `
<div *ngIf="currentUser$ | async as user">
Zalogowany jako: {{ user.name }}
</div>
`
})
export class NavbarComponent {
currentUser$ = this.userService.currentUser$;
constructor(private userService: UserService) {}
}
Rekruter może zapytać: "Dlaczego asObservable()?"
Enkapsulacja. Nie chcesz, żeby komponenty mogły robić userService.currentUser$.next(...). Tylko serwis kontroluje kiedy i jak zmienia się stan. To podstawowa zasada dobrej architektury.
HTTP i RxJS w Angular
HttpClient Angular zwraca Observable - musisz rozumieć jak to obsługiwać.
Podstawowe żądania
@Injectable({ providedIn: 'root' })
export class ApiService {
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users');
}
createUser(user: CreateUserDto): Observable<User> {
return this.http.post<User>('/api/users', user);
}
updateUser(id: number, data: Partial<User>): Observable<User> {
return this.http.put<User>(`/api/users/${id}`, data);
}
deleteUser(id: number): Observable<void> {
return this.http.delete<void>(`/api/users/${id}`);
}
}
Obsługa błędów
import { catchError, retry, retryWhen, delay, take } from 'rxjs/operators';
import { throwError, of, timer } from 'rxjs';
// Podstawowa obsługa błędów
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
catchError(error => {
console.error('Błąd pobierania użytkowników:', error);
// Opcja 1: Zwróć pustą tablicę jako fallback
return of([]);
// Opcja 2: Przekaż błąd dalej z lepszym opisem
// return throwError(() => new Error('Nie udało się pobrać użytkowników'));
})
);
}
// Retry z exponential backoff
getUsersWithRetry(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
retry({
count: 3,
delay: (error, retryCount) => {
console.log(`Retry ${retryCount}...`);
return timer(Math.pow(2, retryCount) * 1000); // 2s, 4s, 8s
}
}),
catchError(error => {
console.error('Błąd po 3 próbach:', error);
return of([]);
})
);
}
Łączenie wielu żądań
import { forkJoin, combineLatest } from 'rxjs';
import { switchMap, map } from 'rxjs/operators';
// FORKJOIN - czeka na wszystkie i zwraca razem
// Używaj gdy: potrzebujesz wszystkich danych jednocześnie, żadne nie zmienia się w czasie
loadDashboardData(): Observable<DashboardData> {
return forkJoin({
users: this.http.get<User[]>('/api/users'),
posts: this.http.get<Post[]>('/api/posts'),
stats: this.http.get<Stats>('/api/stats')
});
}
// COMBINELATEST - reaguje na każdą zmianę
// Używaj gdy: dane mogą się zmieniać (np. z WebSocket)
liveData$ = combineLatest({
users: this.usersService.users$,
filters: this.filtersService.filters$
}).pipe(
map(({ users, filters }) => this.applyFilters(users, filters))
);
// Sekwencyjne żądania - jedno zależy od drugiego
getUserWithPosts(userId: number): Observable<UserWithPosts> {
return this.http.get<User>(`/api/users/${userId}`).pipe(
switchMap(user =>
this.http.get<Post[]>(`/api/users/${userId}/posts`).pipe(
map(posts => ({ ...user, posts }))
)
)
);
}
Zarządzanie subskrypcjami
Wycieki pamięci to najpowszechniejszy błąd z RxJS w Angular. Rekruterzy zawsze pytają jak im zapobiegać.
Async Pipe - preferowane rozwiązanie
@Component({
template: `
<!-- Async pipe automatycznie subskrybuje i unsubscribe -->
<ul>
<li *ngFor="let user of users$ | async">{{ user.name }}</li>
</ul>
<!-- Z ngIf dla null check -->
<div *ngIf="currentUser$ | async as user">
Witaj, {{ user.name }}!
</div>
<!-- Wiele Observable - unikaj wielu async pipe -->
<ng-container *ngIf="{ users: users$ | async, loading: loading$ | async } as vm">
<div *ngIf="vm.loading">Ładowanie...</div>
<ul *ngIf="!vm.loading">
<li *ngFor="let user of vm.users">{{ user.name }}</li>
</ul>
</ng-container>
`
})
export class UsersComponent {
users$ = this.usersService.getUsers();
currentUser$ = this.authService.currentUser$;
loading$ = this.usersService.loading$;
}
takeUntilDestroyed (Angular 16+)
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({...})
export class ModernComponent {
private destroyRef = inject(DestroyRef);
ngOnInit() {
this.someService.data$.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe(data => {
this.doSomething(data);
});
}
}
takeUntil pattern (przed Angular 16)
@Component({...})
export class ClassicComponent implements OnDestroy {
private destroy$ = new Subject<void>();
ngOnInit() {
this.someService.data$.pipe(
takeUntil(this.destroy$)
).subscribe(data => {
this.doSomething(data);
});
// Możesz mieć wiele subskrypcji z tym samym takeUntil
this.anotherService.events$.pipe(
takeUntil(this.destroy$)
).subscribe(event => {
this.handleEvent(event);
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
Kiedy który pattern?
| Scenariusz | Rozwiązanie |
|---|---|
| Wyświetlanie danych w szablonie |
async pipe |
| Side effects w komponencie |
takeUntilDestroyed lub takeUntil
|
| Jednorazowy HTTP request | Nie wymaga (kończy się sam) |
| Interval, WebSocket, ciągłe strumienie | Zawsze unsubscribe! |
Reactive Forms z RxJS
Angular Reactive Forms to showcase możliwości RxJS. Każdy FormControl eksponuje Observable:
@Component({
template: `
<form [formGroup]="form">
<input formControlName="email">
<div *ngIf="emailErrors$ | async as errors">
<span *ngIf="errors.required">Email wymagany</span>
<span *ngIf="errors.email">Nieprawidłowy format</span>
<span *ngIf="errors.taken">Email zajęty</span>
</div>
</form>
`
})
export class RegistrationComponent implements OnInit {
form = new FormGroup({
email: new FormControl('', [Validators.required, Validators.email])
});
emailErrors$: Observable<any>;
ngOnInit() {
// Reaktywna walidacja z async validator
this.emailErrors$ = this.form.get('email')!.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(email =>
email ? this.checkEmailAvailable(email) : of(null)
),
map(serverError => ({
...this.form.get('email')!.errors,
...serverError
}))
);
}
private checkEmailAvailable(email: string): Observable<{taken: true} | null> {
return this.http.get<boolean>(`/api/check-email?email=${email}`).pipe(
map(isTaken => isTaken ? { taken: true } : null),
catchError(() => of(null))
);
}
}
Praktyczne wzorce dla rozmowy
Cache HTTP response
@Injectable({ providedIn: 'root' })
export class CachedApiService {
private cache = new Map<string, Observable<any>>();
getData(id: string): Observable<Data> {
if (!this.cache.has(id)) {
this.cache.set(
id,
this.http.get<Data>(`/api/data/${id}`).pipe(
shareReplay(1) // Cache ostatniej wartości
)
);
}
return this.cache.get(id)!;
}
invalidateCache(id: string): void {
this.cache.delete(id);
}
}
Loading state
@Injectable({ providedIn: 'root' })
export class UsersService {
private loadingSubject = new BehaviorSubject<boolean>(false);
loading$ = this.loadingSubject.asObservable();
getUsers(): Observable<User[]> {
this.loadingSubject.next(true);
return this.http.get<User[]>('/api/users').pipe(
finalize(() => this.loadingSubject.next(false))
);
}
}
// W komponencie
@Component({
template: `
<div *ngIf="loading$ | async">Ładowanie...</div>
<ul *ngIf="!(loading$ | async)">
<li *ngFor="let user of users$ | async">{{ user.name }}</li>
</ul>
`
})
export class UsersComponent {
users$ = this.usersService.getUsers();
loading$ = this.usersService.loading$;
}
Polling API
@Injectable({ providedIn: 'root' })
export class StatusService {
private pollingEnabled$ = new BehaviorSubject<boolean>(true);
status$ = this.pollingEnabled$.pipe(
switchMap(enabled =>
enabled
? interval(5000).pipe(
startWith(0),
switchMap(() => this.http.get<Status>('/api/status')),
retry(3),
catchError(() => of({ status: 'unknown' }))
)
: EMPTY
),
shareReplay(1)
);
stopPolling(): void {
this.pollingEnabled$.next(false);
}
startPolling(): void {
this.pollingEnabled$.next(true);
}
}
Czego rekruterzy szukają
Po latach przeprowadzania rozmów na Angular Developer, wiem że rekruterzy zwracają uwagę na:
Rozumienie lazy evaluation - kandydaci często myślą, że Observable wykonuje się jak Promise. Musisz wiedzieć, że nic się nie dzieje bez subscribe (lub async pipe).
Wybór właściwego operatora - switchMap vs mergeMap to nie tylko teoria. Rekruterzy dają scenariusze: "Masz autocomplete, co użyjesz? A gdyby to był upload wielu plików?".
Zarządzanie subskrypcjami - jeśli nie wspomnisz o unsubscribe, takeUntil lub async pipe, to czerwona flaga. Wycieki pamięci w produkcji to poważny problem.
Praktyczne doświadczenie - opowiedz o konkretnych przypadkach: "W projekcie X użyłem shareReplay do cache'owania konfiguracji, co zmniejszyło liczbę requestów o 80%".
Praktyka przed rozmową
Zanim pójdziesz na rozmowę, przećwicz te zadania:
Zbuduj komponent wyszukiwania z debounce, który pokazuje wyniki z API i obsługuje błędy z komunikatem dla użytkownika.
Stwórz serwis zarządzający stanem koszyka zakupowego z BehaviorSubject, metodami add/remove/clear i Observable sumy.
Zaimplementuj polling endpoint ze statusem serwera, który zatrzymuje się gdy użytkownik przechodzi na inną zakładkę (visibility API + RxJS).
Napisz interceptor HTTP który dodaje token z serwisu AuthService i automatycznie odświeża token gdy wygaśnie (401 response).
Te zadania pokrywają 90% pytań praktycznych na rozmowach Angular.
Zobacz też
- Kompletny Przewodnik - Rozmowa Frontend Developer - pełny przewodnik przygotowania do rozmowy frontend
- Angular vs React - Porównanie dla Programistów 2026 - porównanie frameworków
- TypeScript dla Początkujących - TypeScript jest kluczowy dla Angular
Chcesz więcej pytań z RxJS?
To tylko wycinek z 40 pytań rekrutacyjnych z RxJS, które przygotowaliśmy. Pełny zestaw fiszek pokrywa:
- Wszystkie typy Subjects i kiedy ich używać
- 30+ operatorów z przykładami użycia
- Zaawansowane wzorce: multicasting, hot/cold Observable
- Error handling i retry strategies
- Integracja z Angular: HTTP, Forms, Router
- Testing Observable z marble diagrams
Sprawdź pełny zestaw fiszek RxJS
Możesz też wypróbować bezpłatny podgląd pytań z RxJS, żeby zobaczyć jakość naszych materiałów.
Artykuł przygotowany przez zespół Flipcards na podstawie rzeczywistych doświadczeń z rozmów rekrutacyjnych na stanowiska Angular Developer.
Chcesz więcej pytań rekrutacyjnych?
To tylko jeden temat z naszego kompletnego przewodnika po rozmowach rekrutacyjnych. Uzyskaj dostęp do 800+ pytań z 13 technologii.
