Skip to content

Commit b346425

Browse files
added new operators
1 parent 2785629 commit b346425

File tree

8 files changed

+985
-8
lines changed

8 files changed

+985
-8
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
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.
44

5+
## 🌐 Live Site
6+
7+
You can explore the full guide as a searchable, categorized site at [RxJS Angular interview guide](https://ankitsharma-007.github.io/rxjs-angular-interview-guide/).
8+
59
## 🚀 What’s Inside?
610

711
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:
@@ -35,6 +39,4 @@ And more as we grow!
3539

3640
## 🤝 Contributions Welcome
3741

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-
---
42+
Have a great example, question, or operator to add? PRs are welcome! Let’s make this the go-to resource for RxJS interview prep in the Angular world.

docs/Operators/Filtering/find.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
The `find` operator searches through the sequence of values emitted by a source Observable. It takes a **predicate function** (a function that returns `true` or `false`) as an argument.
2+
3+
`find` will:
4+
5+
1. Check each value emitted by the source against the predicate function.
6+
2. If it finds a value for which the predicate returns `true`:
7+
- It emits that **single value**.
8+
- It immediately **completes** the output Observable (it stops listening to the source).
9+
3. If the source Observable completes _without_ emitting any value that satisfies the predicate function:
10+
- `find` emits `undefined`.
11+
- It then completes.
12+
13+
## Analogy
14+
15+
Imagine you're watching items pass by on a **conveyor belt** (the source Observable). You're looking for a specific item, say, the **first red ball**.
16+
17+
- You start watching (`subscribe`).
18+
- Items go by: blue square, green triangle... (`find` checks each with your predicate `item => item.color === 'red'`).
19+
- A red ball appears! (`find` predicate returns `true`).
20+
- You **grab that red ball** (emit the value).
21+
- You **walk away** because you found what you needed (complete the output Observable). You don't care about any other items that might come later on the belt.
22+
- If the belt stops (`source completes`) before you see _any_ red balls, you walk away empty-handed (emit `undefined`).
23+
24+
## Key Points
25+
26+
- **Emits At Most One Value:** You'll only ever get the _first_ matching item or `undefined`.
27+
- **Completes Early:** As soon as a match is found, the operator completes. This can be efficient if you only need the first occurrence.
28+
- **Predicate Function:** The core logic lives in the function you provide to test each value.
29+
- **vs `filter`:** Don't confuse `find` with `filter`. `filter` lets _all_ values that match the predicate pass through, while `find` only lets the _first_ one through and then stops.
30+
31+
## Real-World Example: Finding the First Admin User in a Stream
32+
33+
Suppose you have a stream of user objects being emitted (perhaps from a WebSocket or paginated API results). You want to find the very first user object that has administrative privileges and then stop processing.
34+
35+
### Code Snippet
36+
37+
**1. Mock User Service (Emits Users One by One)**
38+
39+
```typescript
40+
import { Injectable } from "@angular/core";
41+
import { Observable, from, timer } from "rxjs";
42+
import { concatMap, delay, tap } from "rxjs/operators"; // Use concatMap for sequential emission with delay
43+
44+
export interface User {
45+
id: number;
46+
name: string;
47+
isAdmin: boolean;
48+
}
49+
50+
@Injectable({
51+
providedIn: "root",
52+
})
53+
export class UserStreamService {
54+
getUsers(): Observable<User> {
55+
const users: User[] = [
56+
{ id: 1, name: "Alice (User)", isAdmin: false },
57+
{ id: 2, name: "Bob (User)", isAdmin: false },
58+
{ id: 3, name: "Charlie (Admin)", isAdmin: true }, // The one we want!
59+
{ id: 4, name: "Diana (User)", isAdmin: false },
60+
{ id: 5, name: "Eve (Admin)", isAdmin: true }, // `find` won't reach this one
61+
];
62+
63+
console.log("UserStreamService: Starting user emission...");
64+
65+
// Emit users one by one with a small delay between them
66+
return from(users).pipe(
67+
concatMap((user) =>
68+
timer(500).pipe(
69+
// Wait 500ms before emitting next user
70+
tap(() => console.log(` -> Emitting user: ${user.name}`)),
71+
switchMap(() => of(user)) // Emit the user after the delay
72+
)
73+
)
74+
// This simpler version emits immediately, find still works:
75+
// return from(users).pipe(
76+
// tap(user => console.log(` -> Emitting user: ${user.name}`))
77+
// );
78+
);
79+
}
80+
}
81+
```
82+
83+
**2. Component Using `find`**
84+
85+
```typescript
86+
import {
87+
Component,
88+
inject,
89+
signal,
90+
ChangeDetectionStrategy,
91+
OnInit,
92+
DestroyRef,
93+
} from "@angular/core";
94+
import { CommonModule } from "@angular/common"; // For @if and json pipe
95+
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
96+
import { UserStreamService, User } from "./user-stream.service"; // Adjust path
97+
import { find, tap } from "rxjs/operators";
98+
99+
@Component({
100+
selector: "app-find-admin",
101+
standalone: true,
102+
imports: [CommonModule],
103+
template: `
104+
<div>
105+
<h4>Find Operator Example</h4>
106+
<p>Searching for the first admin user in the stream...</p>
107+
108+
@if (foundAdmin()) {
109+
<div class="result found">
110+
<strong>First Admin Found:</strong>
111+
<pre>{{ foundAdmin() | json }}</pre>
112+
</div>
113+
} @else if (searchComplete()) {
114+
<p class="result not-found">
115+
No admin user found before the stream completed.
116+
</p>
117+
} @else {
118+
<p class="result searching">Searching...</p>
119+
}
120+
</div>
121+
`,
122+
// No 'styles' section
123+
changeDetection: ChangeDetectionStrategy.OnPush,
124+
})
125+
export class FindAdminComponent implements OnInit {
126+
private userStreamService = inject(UserStreamService);
127+
private destroyRef = inject(DestroyRef);
128+
129+
// --- State Signals ---
130+
foundAdmin = signal<User | undefined>(undefined); // Result can be User or undefined
131+
searchComplete = signal<boolean>(false); // Track if the find operation finished
132+
133+
ngOnInit(): void {
134+
console.log("FindAdminComponent: Subscribing to find the first admin...");
135+
136+
this.userStreamService
137+
.getUsers()
138+
.pipe(
139+
tap((user) =>
140+
console.log(`Checking user: ${user.name}, isAdmin: ${user.isAdmin}`)
141+
),
142+
143+
// --- Apply the find operator ---
144+
// Predicate checks the isAdmin property
145+
find((user) => user.isAdmin === true),
146+
// --------------------------------
147+
148+
takeUntilDestroyed(this.destroyRef) // Standard cleanup
149+
)
150+
.subscribe({
151+
next: (adminUser) => {
152+
// This 'next' block runs AT MOST ONCE.
153+
// 'adminUser' will be the first user where isAdmin is true, OR undefined.
154+
if (adminUser) {
155+
console.log("SUCCESS: First admin found ->", adminUser);
156+
this.foundAdmin.set(adminUser);
157+
} else {
158+
// This case happens if the source stream completes BEFORE an admin is found.
159+
console.log(
160+
"INFO: Stream completed without finding an admin user."
161+
);
162+
}
163+
this.searchComplete.set(true); // Mark search as finished
164+
},
165+
error: (err) => {
166+
console.error("Error during user stream processing:", err);
167+
this.searchComplete.set(true); // Mark as finished on error too
168+
},
169+
complete: () => {
170+
// This 'complete' runs immediately after 'find' emits its value (or undefined).
171+
// It does NOT wait for the source stream ('getUsers') to necessarily finish
172+
// if an admin was found early.
173+
console.log("Find operation stream completed.");
174+
// Ensure completion state is set, e.g., if source was empty.
175+
this.searchComplete.set(true);
176+
},
177+
});
178+
}
179+
}
180+
```
181+
182+
**Explanation:**
183+
184+
1. `UserStreamService` provides an Observable `getUsers()` that emits user objects sequentially with a delay.
185+
2. `FindAdminComponent` subscribes to this stream in `ngOnInit`.
186+
3. **`find(user => user.isAdmin === true)`**: This is the core. For each user emitted by `getUsers()`:
187+
- The predicate `user => user.isAdmin === true` is evaluated.
188+
- It checks Alice (false), Bob (false).
189+
- It checks Charlie (true!). The predicate returns `true`.
190+
- `find` immediately emits the Charlie `User` object.
191+
- `find` immediately completes its output stream. It unsubscribes from the `getUsers()` source; Diana and Eve will likely not even be processed by the `tap` or emitted by the source in this specific component subscription because `find` stopped listening early.
192+
4. The `subscribe` block receives the Charlie object in its `next` handler. The `foundAdmin` signal is updated, and the UI displays the result. The `searchComplete` signal is set.
193+
5. The `complete` handler runs immediately after `next`, logging that the `find` operation is done.
194+
195+
If you were to change the `users` array in the service so no user has `isAdmin: true`, the `getUsers` stream would emit all users and then complete. `find` would never find a match, so it would emit `undefined` when its source completes. The `next` handler would receive `undefined`, the UI would show the "not found" message, and `complete` would run.

docs/Operators/RxJS-operators.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ Think of operators as tools in a workshop for working with your asynchronous dat
7070
- **Purpose:** Miscellaneous operators useful for debugging, controlling timing, or other side effects.
7171
- **Examples:**
7272

73-
- `tap(value => console.log(value))`: Perform side effects (like logging) for each emission without modifying the stream itself. (Formerly known as `do`).
74-
- `delay(1000)`: Delays the emission of each item by a specified time.
75-
- `timeout(5000)`: Emits an error if the source Observable doesn't emit a value within a specified time.
76-
- `finalize(() => console.log('Stream finished'))`: Executes a callback function when the source Observable completes or errors. Good for cleanup logic.
73+
- [`tap`](../Operators/Utility/tap.md): Perform side effects (like logging) for each emission without modifying the stream itself. (Formerly known as `do`).
74+
- [`delay`](../Operators/Utility/delay.md): Delays the emission of each item by a specified time.
75+
- [`timeout`](../Operators/Utility/timeout.md): Emits an error if the source Observable doesn't emit a value within a specified time.
76+
- [`finalize`](../Operators/Utility/finalize.md): Executes a callback function when the source Observable completes or errors. Good for cleanup logic.
7777
- `toArray()`: Collects all source emissions into a single array and emits that array when the source completes.
7878

7979
## Multicasting Operators

0 commit comments

Comments
 (0)