Skip to content

Commit 75d5220

Browse files
AnkitAnkit
authored andcommitted
initial commit
1 parent 9128012 commit 75d5220

33 files changed

+3753
-2
lines changed

.github/workflows/ci.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: ci
2+
on:
3+
push:
4+
branches:
5+
- main
6+
permissions:
7+
contents: write
8+
jobs:
9+
deploy:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- name: Configure Git Credentials
14+
run: |
15+
git config user.name github-actions[bot]
16+
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
17+
- uses: actions/setup-python@v5
18+
with:
19+
python-version: 3.x
20+
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
21+
- uses: actions/cache@v4
22+
with:
23+
key: mkdocs-material-${{ env.cache_id }}
24+
path: .cache
25+
restore-keys: |
26+
mkdocs-material-
27+
- run: pip install mkdocs-material
28+
- run: mkdocs gh-deploy --force

README.md

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,42 @@
1-
# rxjs-angular-interview-guide
2-
A comprehensive, open-source RxJS interview guide tailored for Angular developers. Includes real-world questions, detailed answers, and code examples to help you ace your next interview.
1+
# RxJS Interview Guide for Angular Developers
2+
3+
Welcome to the **RxJS Interview Guide for Angular Developers** — a curated, open-source resource designed to help you **master RxJS operators** and **crack interviews with confidence**. Whether you're brushing up for a job interview or deepening your understanding of reactive programming in Angular, this guide has you covered.
4+
5+
## 🚀 What’s Inside?
6+
7+
This guide focuses on **frequently used RxJS operators in real-world Angular applications**, categorized for easy access and faster learning. For each operator, you'll find:
8+
9+
-**Clear explanation** of what the operator does
10+
- 🔁 **Practical usage** in Angular contexts
11+
- 💡 **Code snippets** with real examples
12+
- 🧠 **Interview-style questions and answers**
13+
-**Tips, gotchas, and best practices**
14+
15+
## 📚 Categories Covered
16+
17+
Operators are grouped into the following sections:
18+
19+
- **Creation Operators** (e.g., `of`, `from`, `interval`, `timer`)
20+
- **Transformation Operators** (e.g., `map`, `switchMap`, `mergeMap`, `concatMap`, `exhaustMap`)
21+
- **Filtering Operators** (e.g., `filter`, `take`, `debounceTime`, `distinctUntilChanged`)
22+
- **Combination Operators** (e.g., `combineLatest`, `forkJoin`, `zip`, `withLatestFrom`)
23+
- **Utility Operators** (e.g., `tap`, `delay`, `finalize`)
24+
- **Error Handling Operators** (e.g., `catchError`, `retry`, `retryWhen`)
25+
- **Multicasting Operators** (e.g., `share`, `shareReplay`, `publishReplay`)
26+
27+
And more as we grow!
28+
29+
## 🎯 Who Is This For?
30+
31+
- Angular developers preparing for **technical interviews**
32+
- Engineers looking to **refresh or deepen RxJS knowledge**
33+
- Mentors and interviewers creating **technical assessments**
34+
- Anyone exploring **RxJS in Angular** in a structured, hands-on way
35+
36+
## 🤝 Contributions Welcome
37+
38+
Have a great example, question, or operator to add? PRs are open! Let’s make this the go-to resource for RxJS interview prep in the Angular world.
39+
40+
---
41+
42+
Let me know if you want badges, table of contents, or starter files (`CONTRIBUTING.md`, etc.) too!
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# combineLatest
2+
3+
The `combineLatest()` operator is used when you have multiple streams of data (Observables) and you want to combine their latest values into a single stream.
4+
5+
Think of it like this: You're waiting for several pieces of information to arrive. `combineLatest()` waits until it has received _at least one_ piece of information from _every_ source it's watching. Once it has that initial set, it gives you an array containing the latest value from each source.
6+
7+
After that initial combination, _any time_ any _one_ of the sources sends out a new piece of information, `combineLatest()` immediately sends out a _new_ array containing that new value along with the most recent values it remembers from all the _other_ sources.
8+
9+
## Key Characteristics
10+
11+
1. **Waits for Initial Values:** It won't emit anything until _all_ input Observables have emitted at least one value.
12+
2. **Emits Latest Combination:** Once initialized, it emits an array containing the most recent value from each input Observable.
13+
3. **Reacts to Any Input:** As soon as _any_ of the input Observables emits a new value, `combineLatest` emits a new array with the updated combination.
14+
15+
## Real-World Analogy
16+
17+
Imagine a dashboard displaying:
18+
19+
- The current stock price for "Company A".
20+
- The current user's selected currency (e.g., USD, EUR, INR).
21+
22+
You need _both_ the latest price _and_ the selected currency to display the price correctly formatted.
23+
24+
- `combineLatest()` would wait until it receives the first stock price update AND the first currency selection.
25+
- Once it has both, it emits `[latestPrice, latestCurrency]`.
26+
- If the stock price updates, it immediately emits `[newPrice, latestCurrency]`.
27+
- If the user changes the currency, it immediately emits `[latestPrice, newCurrency]`.
28+
29+
## Angular Example: Combining Route Parameter and User Settings
30+
31+
Let's say you have a component that displays product details. The product ID comes from the route URL, and you also have a user setting for whether to show prices in bold.
32+
33+
```typescript
34+
import { Component, OnInit, inject } from "@angular/core";
35+
import { ActivatedRoute } from "@angular/router";
36+
import { combineLatest, Observable, timer } from "rxjs";
37+
import {
38+
map,
39+
switchMap,
40+
distinctUntilChanged,
41+
filter,
42+
delay,
43+
} from "rxjs/operators";
44+
import { Injectable } from "@angular/core";
45+
import { of } from "rxjs";
46+
47+
@Injectable({ providedIn: "root" })
48+
export class ProductService {
49+
getProduct(id: string): Observable<any> {
50+
console.log(`API: Fetching product ${id}`);
51+
return of({
52+
id: id,
53+
name: `Product ${id}`,
54+
price: Math.random() * 100,
55+
}).pipe(delay(300));
56+
}
57+
}
58+
59+
@Injectable({ providedIn: "root" })
60+
export class UserSettingsService {
61+
getSettings(): Observable<{ boldPrice: boolean }> {
62+
return timer(0, 5000).pipe(map((i) => ({ boldPrice: i % 2 === 0 })));
63+
}
64+
}
65+
66+
@Component({
67+
selector: "app-product-detail",
68+
template: `
69+
<div *ngIf="data$ | async as data">
70+
<h2>{{ data.product.name }}</h2>
71+
<p [style.fontWeight]="data.settings.boldPrice ? 'bold' : 'normal'">
72+
Price: {{ data.product.price | currency }}
73+
</p>
74+
</div>
75+
<p *ngIf="!(data$ | async)">Loading product details...</p>
76+
`,
77+
})
78+
export class ProductDetailComponent implements OnInit {
79+
private route = inject(ActivatedRoute);
80+
private productService = inject(ProductService);
81+
private userSettingsService = inject(UserSettingsService);
82+
83+
data$!: Observable<{ product: any; settings: { boldPrice: boolean } }>;
84+
85+
ngOnInit() {
86+
const productId$ = this.route.paramMap.pipe(
87+
map((params) => params.get("productId")),
88+
filter((id): id is string => !!id),
89+
distinctUntilChanged()
90+
);
91+
92+
const settings$ = this.userSettingsService.getSettings();
93+
94+
this.data$ = combineLatest([productId$, settings$]).pipe(
95+
switchMap(([id, settings]) =>
96+
this.productService
97+
.getProduct(id)
98+
.pipe(map((product) => ({ product, settings })))
99+
)
100+
);
101+
}
102+
}
103+
```
104+
105+
**In this example:**
106+
107+
1. We get the `productId` from the route parameters. `distinctUntilChanged` prevents unnecessary work if the route emits the same ID multiple times.
108+
2. We get the `settings` from a service.
109+
3. `combineLatest` waits for both the _first_ valid `productId` and the _first_ `settings` emission.
110+
4. When both are available, or when _either_ the `productId` changes _or_ the `settings` update, `combineLatest` emits `[latestId, latestSettings]`.
111+
5. `switchMap` takes this latest combination. It uses the `latestId` to fetch the corresponding product data. If the `productId` changes while a previous product fetch is still in progress, `switchMap` cancels the old fetch and starts a new one for the new ID.
112+
6. Finally, we `map` the result to create an object `{ product, settings }` that's easy to use in the template with the `async` pipe. The template automatically updates whenever `data$` emits a new object, whether due to a product change or a setting change.
113+
114+
## Summary
115+
116+
use `combineLatest()` when you need to react to changes from multiple independent sources and always need the _most recent_ value from each of them to perform a calculation or update the UI.
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# forkJoin
2+
3+
`forkJoin()` is used when you have a group of Observables (often representing asynchronous operations like API calls) and you want to wait until **all** of them have **completed** before you get the results.
4+
5+
Think of it like starting several independent tasks (e.g., downloading multiple files, making several API requests). `forkJoin` waits patiently until every single one of those tasks signals "I'm finished!". Once the last task completes, `forkJoin` emits a _single_ value, which is an array containing the _very last value_ emitted by each of the input Observables, in the same order you provided them.
6+
7+
## Key Characteristics
8+
9+
1. **Waits for Completion:** It doesn't emit anything until _every_ input Observable finishes (completes).
10+
2. **Parallel Execution:** It subscribes to all input Observables immediately, allowing them to run in parallel.
11+
3. **Single Emission:** It emits only _one_ value (or an error).
12+
4. **Array of Last Values:** The emitted value is an array containing the _last_ value from each input Observable.
13+
5. **Error Behavior:** If _any_ of the input Observables error out, `forkJoin` immediately errors out as well. It will _not_ wait for the other Observables to complete and will _not_ emit the array of results.
14+
15+
## Real-World Analogy
16+
17+
Imagine you're ordering dinner from three different places via delivery apps:
18+
19+
- App 1: Pizza
20+
- App 2: Salad
21+
- App 3: Drinks
22+
23+
You want to start eating only when _everything_ has arrived. `forkJoin` is like waiting by the door. It doesn't matter if the pizza arrives first, or the drinks. You only care about the moment the _last_ delivery person arrives. At that exact moment, `forkJoin` gives you the complete meal: `[Pizza, Salad, Drinks]`.
24+
25+
However, if any single order fails (e.g., the pizza place cancels), `forkJoin` immediately tells you there's a problem ("Error: Pizza order cancelled!") and you don't get the combined results.
26+
27+
**Handling Errors within `forkJoin`:**
28+
29+
Because `forkJoin` fails completely if any input stream errors, you often want to handle potential errors _within_ each input stream _before_ they reach `forkJoin`. You can use the `catchError` operator for this, typically returning a fallback value (like `null`, `undefined`, or an empty object/array) so that the stream still _completes_ successfully.
30+
31+
```typescript
32+
import { forkJoin, of, timer, throwError } from "rxjs";
33+
import { delay, catchError } from "rxjs/operators";
34+
35+
const successful$ = of("Success Data").pipe(delay(500));
36+
37+
// Simulate an API call that fails
38+
const failing$ = timer(1500).pipe(
39+
delay(100), // Add small delay just for simulation
40+
map(() => {
41+
throw new Error("Network Error");
42+
}) // Simulate error
43+
);
44+
45+
// --- Without error handling inside ---
46+
// forkJoin([successful$, failing$]).subscribe({
47+
// next: results => console.log('This will not run'),
48+
// error: err => console.error('forkJoin failed because one stream errored:', err.message) // This will run
49+
// });
50+
51+
// --- With error handling inside the failing stream ---
52+
console.log("\nStarting forkJoin with internal error handling...");
53+
const failingHandled$ = failing$.pipe(
54+
catchError((error) => {
55+
console.warn(`Caught error in stream: ${error.message}. Returning null.`);
56+
// Return an Observable that emits a fallback value and COMPLETES
57+
return of(null);
58+
})
59+
);
60+
61+
forkJoin([successful$, failingHandled$]).subscribe({
62+
next: (results) => {
63+
// This will run after ~1.6 seconds
64+
console.log("forkJoin completed with results:", results); // results: ['Success Data', null]
65+
},
66+
error: (err) => {
67+
console.error("This should not run if errors are handled internally:", err);
68+
},
69+
});
70+
71+
/*
72+
Expected Output:
73+
Starting forkJoin with internal error handling...
74+
(after ~1.6 seconds)
75+
Caught error in stream: Network Error. Returning null.
76+
forkJoin completed with results: [ 'Success Data', null ]
77+
*/
78+
```
79+
80+
## Angular Example: Loading Initial Page Data
81+
82+
`forkJoin` is perfect for loading all the essential data a component needs before displaying anything.
83+
84+
```typescript
85+
import { Component, OnInit } from "@angular/core";
86+
import { HttpClient } from "@angular/common/http";
87+
import { forkJoin, of } from "rxjs";
88+
import { catchError } from "rxjs/operators";
89+
90+
interface UserProfile {
91+
name: string;
92+
email: string;
93+
}
94+
interface UserPreferences {
95+
theme: string;
96+
language: string;
97+
}
98+
interface InitialNotifications {
99+
count: number;
100+
messages: string[];
101+
}
102+
103+
@Component({
104+
selector: "app-profile-page",
105+
template: `
106+
<div *ngIf="!isLoading && !errorMsg">
107+
<h2>Profile: {{ profile?.name }}</h2>
108+
<p>Email: {{ profile?.email }}</p>
109+
<p>Theme: {{ preferences?.theme }}</p>
110+
<p>Notifications: {{ notifications?.count }}</p>
111+
</div>
112+
<div *ngIf="isLoading">Loading profile data...</div>
113+
<div *ngIf="errorMsg" style="color: red;">{{ errorMsg }}</div>
114+
`,
115+
})
116+
export class ProfilePageComponent implements OnInit {
117+
isLoading = true;
118+
errorMsg: string | null = null;
119+
120+
profile: UserProfile | null = null;
121+
preferences: UserPreferences | null = null;
122+
notifications: InitialNotifications | null = null;
123+
124+
constructor(private http: HttpClient) {}
125+
126+
ngOnInit() {
127+
this.loadData();
128+
}
129+
130+
loadData() {
131+
this.isLoading = true;
132+
this.errorMsg = null;
133+
134+
// Define the API calls - HttpClient observables complete automatically
135+
const profile$ = this.http.get<UserProfile>("/api/profile").pipe(
136+
catchError((err) => {
137+
console.error("Failed to load Profile", err);
138+
// Return fallback and let forkJoin continue
139+
return of(null);
140+
})
141+
);
142+
143+
const preferences$ = this.http
144+
.get<UserPreferences>("/api/preferences")
145+
.pipe(
146+
catchError((err) => {
147+
console.error("Failed to load Preferences", err);
148+
// Return fallback and let forkJoin continue
149+
return of(null);
150+
})
151+
);
152+
153+
const notifications$ = this.http
154+
.get<InitialNotifications>("/api/notifications")
155+
.pipe(
156+
catchError((err) => {
157+
console.error("Failed to load Notifications", err);
158+
// Return fallback and let forkJoin continue
159+
return of({ count: 0, messages: [] }); // Example fallback
160+
})
161+
);
162+
163+
// Use forkJoin to wait for all requests
164+
forkJoin([profile$, preferences$, notifications$]).subscribe(
165+
([profileResult, preferencesResult, notificationsResult]) => {
166+
// This block runs when all API calls have completed (successfully or with handled errors)
167+
console.log("All data received:", {
168+
profileResult,
169+
preferencesResult,
170+
notificationsResult,
171+
});
172+
173+
// Check if essential data is missing
174+
if (!profileResult || !preferencesResult) {
175+
this.errorMsg =
176+
"Could not load essential profile data. Please try again later.";
177+
} else {
178+
this.profile = profileResult;
179+
this.preferences = preferencesResult;
180+
this.notifications = notificationsResult; // Notifications might be optional or have a fallback
181+
}
182+
183+
this.isLoading = false;
184+
},
185+
(err) => {
186+
// This error handler is less likely to be hit if catchError is used inside,
187+
// but good practice to have for unexpected issues.
188+
console.error("Unexpected error in forkJoin:", err);
189+
this.errorMsg = "An unexpected error occurred while loading data.";
190+
this.isLoading = false;
191+
}
192+
);
193+
}
194+
}
195+
```
196+
197+
In this example, the component makes three API calls. The `forkJoin` ensures that the loading indicator stays active until _all three_ requests are finished. By using `catchError` inside each request, we prevent one failed request from stopping the others, and we can handle missing data appropriately in the `next` callback of `forkJoin`.
198+
199+
## Summary
200+
201+
use `forkJoin` when you need to run several asynchronous operations (that eventually complete) in parallel and only want to proceed once you have the final result from _all_ of them. Remember its strict error handling behavior and use `catchError` internally if necessary.

0 commit comments

Comments
 (0)