Skip to content

Commit 85b27f3

Browse files
added new operators
1 parent b346425 commit 85b27f3

File tree

3 files changed

+255
-1
lines changed

3 files changed

+255
-1
lines changed

docs/async-pipe.md

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
Think of the `async` pipe as a smart assistant for handling asynchronous data right where you display it (in the HTML template). It does several important things automatically:
2+
3+
1. **Subscribes:** When the component loads, the `async` pipe automatically subscribes to the Observable (or Promise) you provide it.
4+
2. **Unwraps Values:** When the Observable emits a new value, the `async` pipe "unwraps" that value and makes it available for binding in your template.
5+
3. **Triggers Change Detection:** It automatically tells Angular to check the component for changes whenever a new value arrives, ensuring your view updates.
6+
4. **Unsubscribes Automatically:** This is a huge benefit! When the component is destroyed, the `async` pipe automatically unsubscribes from the Observable, preventing potential memory leaks. You don't need manual unsubscription logic (like `takeUntilDestroyed` or `.unsubscribe()`) _for the subscription managed by the pipe itself_.
7+
5. **Handles Null/Undefined Initially:** Before the Observable emits its first value, the `async` pipe typically returns `null`, which you can handle gracefully in your template (often using `@if` or `*ngIf`).
8+
9+
## Why Use the `async` Pipe?
10+
11+
- **Less Boilerplate Code:** Significantly reduces the amount of code you need to write in your component's TypeScript file. You often don't need to manually subscribe, store the emitted value in a component property/signal, or handle unsubscription _just_ for displaying the data.
12+
- **Automatic Memory Management:** The automatic unsubscription is the killer feature, making your components cleaner and less prone to memory leaks.
13+
- **Improved Readability:** Keeps the template declarative. The template shows _what_ data stream it's bound to, and the pipe handles the _how_.
14+
15+
## Real-World Example: Displaying User Data Fetched via HttpClient
16+
17+
Fetching data from an API is a prime use case. Let's fetch user data and display it using the `async` pipe, avoiding manual subscription in the component for display purposes.
18+
19+
**Code Snippet:**
20+
21+
**1. User Service**
22+
23+
```typescript
24+
import { Injectable, inject } from "@angular/core";
25+
import { HttpClient } from "@angular/common/http";
26+
import { Observable } from "rxjs";
27+
import { shareReplay, tap } from "rxjs/operators";
28+
29+
export interface UserProfile {
30+
id: number;
31+
name: string;
32+
username: string;
33+
email: string;
34+
}
35+
36+
@Injectable({
37+
providedIn: "root",
38+
})
39+
export class UserService {
40+
private http = inject(HttpClient);
41+
private userUrl = "https://jsonplaceholder.typicode.com/users/";
42+
43+
// Cache for user profiles to avoid repeated requests for the same ID
44+
private userCache: { [key: number]: Observable<UserProfile> } = {};
45+
46+
getUser(id: number): Observable<UserProfile> {
47+
// Check cache first
48+
if (!this.userCache[id]) {
49+
console.log(`UserService: Fetching user ${id} from API...`);
50+
this.userCache[id] = this.http
51+
.get<UserProfile>(`${this.userUrl}${id}`)
52+
.pipe(
53+
tap(() =>
54+
console.log(`UserService: API call for user ${id} completed.`)
55+
),
56+
// Share & replay the single result, keep active while subscribed
57+
shareReplay({ bufferSize: 1, refCount: true })
58+
);
59+
} else {
60+
console.log(`UserService: Returning cached observable for user ${id}.`);
61+
}
62+
return this.userCache[id];
63+
}
64+
}
65+
```
66+
67+
**2. User Display Component**
68+
69+
```typescript
70+
import {
71+
Component,
72+
inject,
73+
signal,
74+
ChangeDetectionStrategy,
75+
Input,
76+
OnInit,
77+
} from "@angular/core";
78+
import { CommonModule } from "@angular/common"; // Needed for async pipe, @if, json pipe
79+
import { UserService, UserProfile } from "./user.service"; // Adjust path
80+
import { Observable, EMPTY } from "rxjs"; // Import Observable and EMPTY
81+
82+
@Component({
83+
selector: "app-user-display",
84+
standalone: true,
85+
imports: [CommonModule], // Make sure CommonModule is imported
86+
template: `
87+
<div class="user-card">
88+
<h4>User Profile (ID: {{ userId }})</h4>
89+
90+
<!-- Use the async pipe here -->
91+
@if (user$ | async; as user) {
92+
<!-- 'user' now holds the emitted UserProfile object -->
93+
<div>
94+
<p><strong>Name:</strong> {{ user.name }}</p>
95+
<p><strong>Username:</strong> {{ user.username }}</p>
96+
<p><strong>Email:</strong> {{ user.email }}</p>
97+
</div>
98+
<!-- Optional: Show raw data -->
99+
<!-- <details>
100+
<summary>Raw Data</summary>
101+
<pre>{{ user | json }}</pre>
102+
</details> -->
103+
} @else {
104+
<!-- This shows before the observable emits -->
105+
<p>Loading user data...</p>
106+
}
107+
<!-- Note: Error handling needs separate logic or wrapping the source -->
108+
</div>
109+
`,
110+
// No 'styles' section
111+
changeDetection: ChangeDetectionStrategy.OnPush, // Good practice with async pipe/observables
112+
})
113+
export class UserDisplayComponent implements OnInit {
114+
private userService = inject(UserService);
115+
116+
@Input({ required: true }) userId!: number; // Get user ID from parent
117+
118+
// Expose the Observable directly to the template
119+
user$: Observable<UserProfile> = EMPTY; // Initialize with EMPTY or handle null later
120+
121+
ngOnInit() {
122+
// Assign the observable in ngOnInit (or wherever appropriate)
123+
// NO .subscribe() here for the template binding!
124+
this.user$ = this.userService.getUser(this.userId);
125+
console.log(
126+
`UserDisplayComponent (ID: ${this.userId}): Assigned observable to user$`
127+
);
128+
}
129+
130+
// --- Compare with manual subscription (for illustration) ---
131+
// // Manual Approach (requires more code + manual unsubscription handling):
132+
// private destroyRef = inject(DestroyRef);
133+
// userSignal = signal<UserProfile | null>(null);
134+
// loading = signal<boolean>(false);
135+
136+
// ngOnInitManual() {
137+
// this.loading.set(true);
138+
// this.userService.getUser(this.userId)
139+
// .pipe(
140+
// takeUntilDestroyed(this.destroyRef) // Need manual unsubscribe handling
141+
// )
142+
// .subscribe({
143+
// next: (user) => {
144+
// this.userSignal.set(user); // Store in component state
145+
// this.loading.set(false);
146+
// },
147+
// error: (err) => {
148+
// console.error(err);
149+
// this.loading.set(false);
150+
// // Handle error state...
151+
// }
152+
// });
153+
// }
154+
// // Then in template you'd bind to userSignal() and loading()
155+
}
156+
```
157+
158+
**3. Parent Component (Using the User Display Component)**
159+
160+
```typescript
161+
import { Component } from "@angular/core";
162+
import { UserDisplayComponent } from "./user-display.component"; // Adjust path
163+
164+
@Component({
165+
selector: "app-root",
166+
standalone: true,
167+
imports: [UserDisplayComponent],
168+
template: `
169+
<h1>Async Pipe Demo</h1>
170+
<app-user-display [userId]="1"></app-user-display>
171+
<hr />
172+
<app-user-display [userId]="2"></app-user-display>
173+
<hr />
174+
<!-- This will use the cached observable -->
175+
<app-user-display [userId]="1"></app-user-display>
176+
`,
177+
})
178+
export class AppComponent {}
179+
```
180+
181+
**Explanation:**
182+
183+
1. `UserService` provides a `getUser(id)` method that returns an `Observable<UserProfile>`. It includes caching and `shareReplay` for efficiency.
184+
2. `UserDisplayComponent` gets a `userId` via `@Input`.
185+
3. In `ngOnInit`, it calls `userService.getUser(this.userId)` and assigns the **returned Observable directly** to the public component property `user$`. **Crucially, there is no `.subscribe()` call here.**
186+
4. In the template:
187+
- `@if (user$ | async; as user)`: This is the core line.
188+
- `user$ | async`: The `async` pipe subscribes to the `user$` observable. Initially, it returns `null`.
189+
- `as user`: If/when the `user$` observable emits a value, that value (the `UserProfile` object) is assigned to a local template variable named `user`.
190+
- The `@if` block only renders its content when `user$ | async` produces a "truthy" value (i.e., after the user profile has been emitted).
191+
- Inside the `@if` block, we can directly access properties of the resolved `user` object (e.g., `user.name`, `user.email`).
192+
- The `@else` block handles the initial state, showing "Loading user data..." until the `async` pipe receives the first emission.
193+
5. When the `UserDisplayComponent` is destroyed (e.g., navigated away from), the `async` pipe automatically cleans up its subscription to `user$`.
194+
195+
Compare this component's TypeScript code to the commented-out `ngOnInitManual` example. The `async` pipe version is much cleaner and less error-prone for simply displaying the data.
196+
197+
## Error Handling
198+
199+
The basic `async` pipe doesn't inherently handle errors from the Observable. If the `getUser` observable throws an error, the `async` pipe subscription will break. Proper error handling often involves using `catchError` within the Observable pipe _before_ it reaches the `async` pipe (e.g., catching the error and returning `of(null)` or `EMPTY`) or wrapping the component in an Error Boundary mechanism if appropriate.

docs/promise-vs-observable.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
Let's break down the key differences between Observables (from RxJS) and Promises (native JavaScript). While both deal with asynchronous operations, they have fundamental differences in how they work and what they're capable of.
2+
3+
Here's a comparison table and explanations:
4+
5+
| Feature | Promise | Observable |
6+
| :------------------ | :-------------------------------------------- | :--------------------------------------------------- |
7+
| **Values Emitted** | **One** value (or one rejection) | **Multiple** values over time (or error/complete) |
8+
| **Execution** | **Eager**: Starts immediately upon creation | **Lazy**: Starts only when subscribed to |
9+
| **Cancellable?** | **No** (standard Promises aren't cancellable) | **Yes** (via Unsubscription) |
10+
| **Operators** | Limited (`.then()`, `.catch()`, `.finally()`) | **Rich set** of operators (map, filter, retry, etc.) |
11+
| **Use Cases** | Single async events (HTTP requests, timers) | Streams of events, complex async flows, state |
12+
| **Primary Library** | Native JavaScript ES6+ | RxJS library (often used with Angular) |
13+
14+
## Explanation of Differences
15+
16+
1. **Single vs. Multiple Values:**
17+
18+
- **Promise:** Designed to handle a single asynchronous event that will eventually succeed (resolve with a value) or fail (reject with an error). Once a Promise settles (resolves or rejects), it's done. It will never emit another value.
19+
- _Analogy:_ Ordering a specific package online. You wait, and eventually, you get _that one package_ (resolve) or a notification it couldn't be delivered (reject). The transaction is then over.
20+
- **Observable:** Represents a stream or sequence of values arriving over time. It can emit zero, one, or multiple values. It can also signal an error or completion.
21+
- _Analogy:_ Subscribing to a newsletter or a YouTube channel. You might receive _multiple emails/videos_ over days or weeks (multiple `next` emissions). You could also get an error notification, or the channel might eventually stop publishing (complete).
22+
23+
2. **Eager vs. Lazy Execution:**
24+
25+
- **Promise:** A Promise starts executing its asynchronous operation the moment it is _created_. Calling `.then()` doesn't trigger the operation; it just registers what to do _when_ the already-running operation finishes.
26+
- _Example:_ `const myPromise = new Promise(/* executor starts now */);`
27+
- **Observable:** An Observable is lazy. The code inside it (e.g., the function making an HTTP call) doesn't run until someone actually _subscribes_ to it using `.subscribe()`. Each subscription typically triggers a new, independent execution. (Operators like `shareReplay` modify this to share executions).
28+
- _Example:_ `const myObservable = new Observable(/* code here runs only on subscribe */); myObservable.subscribe(); // Execution starts now.`
29+
30+
3. **Cancellable:**
31+
32+
- **Promise:** Standard Promises don't have a built-in `.cancel()` method. Once you create a Promise and its operation starts, there's no standard way to tell it, "Stop what you're doing, I don't need the result anymore." (Though browser APIs like `AbortController` can sometimes be used to cancel the _underlying operation_, like an `fetch` request, which then causes the Promise to reject).
33+
- **Observable:** Observables are cancellable via their `Subscription` object. When you `subscribe()`, you get back a `Subscription`. Calling `subscription.unsubscribe()` signals to the Observable that the subscriber is no longer interested. This often triggers cleanup logic within the Observable (like clearing intervals or cancelling HTTP requests via `takeUntilDestroyed` or similar mechanisms) and stops further emissions to that subscriber.
34+
35+
4. **Operators:**
36+
- **Promise:** Has basic chaining with `.then()` (for success), `.catch()` (for error), and `.finally()` (for cleanup). You can also use `Promise.all()`, `Promise.race()`, etc., for combining promises.
37+
- **Observable:** Comes with the vast RxJS library of operators (`map`, `filter`, `reduce`, `retry`, `retryWhen`, `debounceTime`, `switchMap`, `mergeMap`, `combineLatest`, `withLatestFrom`, `shareReplay`, `timeout`, `find`, `delay`, `tap`, etc.). These operators allow for powerful manipulation, combination, and control of asynchronous data streams in a declarative way.
38+
39+
## When to Use Which (General Guidelines)
40+
41+
**Use Promises when:**
42+
43+
- You're dealing with a single, one-off asynchronous operation (like a typical HTTP GET or POST request where you expect one response).
44+
- You're working with native browser APIs or libraries that return Promises.
45+
- The complexity doesn't warrant pulling in the full RxJS library.
46+
47+
**Use Observables when:**
48+
49+
- You need to handle streams of data arriving over time (WebSocket messages, user input events like keystrokes or mouse movements, repeated interval emissions).
50+
- You need the advanced transformation, combination, or control capabilities provided by RxJS operators (retrying, debouncing, throttling, complex filtering/mapping, etc.).
51+
- You need the ability to cancel asynchronous operations cleanly.
52+
- You are working heavily within the Angular framework, which uses Observables extensively (e.g., `HttpClient`, `Router` events, `EventEmitter`).
53+
54+
In modern Angular, while you _can_ use Promises, Observables are generally preferred for asynchronous operations, especially when dealing with framework features or requiring more complex stream manipulation, due to their power, flexibility, and cancellable nature.

mkdocs.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ nav:
1616
- "Observer": observer.md
1717
- "Cold Observables": cold-observables.md
1818
- "Hot Observables": hot-observables.md
19-
19+
- "Promise Vs Observable": promise-vs-observable.md
20+
- "Async pipe": async-pipe.md
2021
- "Operators":
2122
- "RxJS operators": Operators/RxJS-operators.md
2223
- "Combination":

0 commit comments

Comments
 (0)