├── .github └── workflows │ └── ci.yml ├── LICENSE ├── README.md ├── docs ├── Operators │ ├── Combination │ │ ├── combineLatest.md │ │ ├── forkJoin.md │ │ ├── withLatestFrom.md │ │ └── zip.md │ ├── Creation │ │ ├── empty-never.md │ │ ├── from.md │ │ ├── interval.md │ │ ├── of.md │ │ └── timer.md │ ├── Error │ │ ├── catchError.md │ │ ├── retry.md │ │ └── retryWhen.md │ ├── Filtering │ │ ├── debounceTime.md │ │ ├── distinctUntilChanged.md │ │ ├── filter.md │ │ ├── find.md │ │ ├── first.md │ │ ├── last.md │ │ ├── skip.md │ │ ├── take.md │ │ └── takeUntil.md │ ├── Multicasting │ │ ├── share.md │ │ └── shareReplay.md │ ├── RxJS-operators.md │ ├── Subjects │ │ ├── behaviorSubject.md │ │ ├── replaySubject.md │ │ ├── subject-behaviorSubject-replaySubject.md │ │ └── subject.md │ ├── Transformation │ │ ├── concatMap.md │ │ ├── exhaustMap.md │ │ ├── map.md │ │ ├── mergeMap.md │ │ ├── switchMap-mergeMap-concatMap.md │ │ └── switchMap.md │ └── Utility │ │ ├── delay.md │ │ ├── finalize.md │ │ ├── tap.md │ │ └── timeout.md ├── assets │ ├── Rx_Logo.png │ └── favicon.ico ├── async-pipe.md ├── cold-observables.md ├── hot-observables.md ├── index.md ├── observable.md ├── observer.md ├── promise-vs-observable.md └── stylesheets │ └── extra.css └── mkdocs.yml /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 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 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Ankit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 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 | ## 🌐 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 | 9 | ## 🚀 What’s Inside? 10 | 11 | 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: 12 | 13 | - ✅ **Clear explanation** of what the operator does 14 | - 🔁 **Practical usage** in Angular contexts 15 | - 💡 **Code snippets** with real examples 16 | - 🧠 **Interview-style questions and answers** 17 | - ✨ **Tips, gotchas, and best practices** 18 | 19 | ## 📚 Categories Covered 20 | 21 | Operators are grouped into the following sections: 22 | 23 | - **Creation Operators** (e.g., `of`, `from`, `interval`, `timer`) 24 | - **Transformation Operators** (e.g., `map`, `switchMap`, `mergeMap`, `concatMap`, `exhaustMap`) 25 | - **Filtering Operators** (e.g., `filter`, `take`, `debounceTime`, `distinctUntilChanged`) 26 | - **Combination Operators** (e.g., `combineLatest`, `forkJoin`, `zip`, `withLatestFrom`) 27 | - **Utility Operators** (e.g., `tap`, `delay`, `finalize`) 28 | - **Error Handling Operators** (e.g., `catchError`, `retry`, `retryWhen`) 29 | - **Multicasting Operators** (e.g., `share`, `shareReplay`, `publishReplay`) 30 | 31 | And more as we grow! 32 | 33 | ## 🎯 Who Is This For? 34 | 35 | - Angular developers preparing for **technical interviews** 36 | - Engineers looking to **refresh or deepen RxJS knowledge** 37 | - Mentors and interviewers creating **technical assessments** 38 | - Anyone exploring **RxJS in Angular** in a structured, hands-on way 39 | 40 | ## 🤝 Contributions Welcome 41 | 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. 43 | -------------------------------------------------------------------------------- /docs/Operators/Combination/combineLatest.md: -------------------------------------------------------------------------------- 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 { 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 |
70 |

{{ data.product.name }}

71 |

72 | Price: {{ data.product.price | currency }} 73 |

74 |
75 |

Loading product details...

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. 117 | -------------------------------------------------------------------------------- /docs/Operators/Combination/forkJoin.md: -------------------------------------------------------------------------------- 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 |
107 |

Profile: {{ profile?.name }}

108 |

Email: {{ profile?.email }}

109 |

Theme: {{ preferences?.theme }}

110 |

Notifications: {{ notifications?.count }}

111 |
112 |
Loading profile data...
113 |
{{ errorMsg }}
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("/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("/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("/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. 202 | -------------------------------------------------------------------------------- /docs/Operators/Combination/withLatestFrom.md: -------------------------------------------------------------------------------- 1 | Think of `withLatestFrom()` as an operator that lets one stream (the "source") peek at the most recent value from one or more other streams whenever the source stream emits something. 2 | 3 | - **Source Stream:** This is the main Observable you attach `withLatestFrom()` to. 4 | - **Other Streams:** These are the Observables you pass _into_ `withLatestFrom()`. 5 | - **How it works:** When the **source** stream emits a value, `withLatestFrom()` looks at the **other** streams and grabs their _latest_ emitted value. It then combines the value from the source stream and the latest values from the other streams into an array. 6 | - **Important:** It only emits when the **source** stream emits. If the other streams emit values but the source stream hasn't emitted since, `withLatestFrom()` does nothing. Also, it won't emit anything until _all_ the provided streams (source and others) have emitted at least one value. 7 | 8 | ## Real-World Example: Search with Filters 9 | 10 | Imagine you have a search page for products. There's: 11 | 12 | 1. A search input field where the user types their query. 13 | 2. A dropdown menu to select a category filter (e.g., "Electronics", "Clothing", "Home Goods"). 14 | 15 | You want to make an API call to fetch products whenever the user types in the search box (after a little pause, using `debounceTime`), but you need _both_ the search term _and_ the currently selected category filter to make the correct API request. 16 | 17 | - The search term changes frequently. This will be our **source** stream (after debouncing). 18 | - The category filter changes less often, maybe only when the user explicitly selects a new option. This will be our **other** stream. 19 | 20 | We want to trigger the search using the _latest_ filter value _at the moment_ the (debounced) search term is ready. `withLatestFrom()` is perfect for this. 21 | 22 | ## Code Snippet 23 | 24 | Let's see how this looks in an Angular component: 25 | 26 | ```typescript 27 | import { Component, inject, DestroyRef, OnInit } from "@angular/core"; 28 | import { CommonModule } from "@angular/common"; 29 | import { ReactiveFormsModule, FormControl } from "@angular/forms"; 30 | import { 31 | Subject, 32 | debounceTime, 33 | distinctUntilChanged, 34 | withLatestFrom, 35 | takeUntilDestroyed, 36 | startWith, 37 | } from "rxjs"; 38 | 39 | @Component({ 40 | selector: "app-product-search", 41 | standalone: true, 42 | imports: [CommonModule, ReactiveFormsModule], 43 | template: ` 44 |
45 | 46 | 47 |
48 |
49 | 50 | 56 |
57 | 58 |
59 | Searching for: "{{ searchResults.term }}" in category: "{{ 60 | searchResults.category 61 | }}" 62 |
63 | `, 64 | }) 65 | export class ProductSearchComponent implements OnInit { 66 | // --- Dependencies --- 67 | private destroyRef = inject(DestroyRef); // For automatic unsubscription 68 | 69 | // --- Form Controls --- 70 | searchTermControl = new FormControl(""); 71 | categoryFilterControl = new FormControl("all"); // Default category 72 | 73 | // --- Component State --- 74 | searchResults: { term: string; category: string } | null = null; 75 | 76 | ngOnInit(): void { 77 | // --- Observables --- 78 | // Source: Search term, debounced 79 | const searchTerm$ = this.searchTermControl.valueChanges.pipe( 80 | debounceTime(400), // Wait for 400ms pause in typing 81 | distinctUntilChanged(), // Only emit if the value actually changed 82 | startWith(this.searchTermControl.value || "") // Emit initial value immediately 83 | ); 84 | 85 | // Other: Category filter 86 | const categoryFilter$ = this.categoryFilterControl.valueChanges.pipe( 87 | startWith(this.categoryFilterControl.value || "all") // Emit initial value immediately 88 | ); 89 | 90 | // --- Combining with withLatestFrom --- 91 | searchTerm$ 92 | .pipe( 93 | withLatestFrom(categoryFilter$), // Combine search term with the LATEST category 94 | takeUntilDestroyed(this.destroyRef) // Auto-unsubscribe when component is destroyed 95 | ) 96 | .subscribe(([term, category]) => { 97 | // This block runs ONLY when searchTerm$ emits (after debounce) 98 | // It gets the emitted 'term' and the 'latest' value from categoryFilter$ 99 | 100 | // Ensure we have non-null values (FormControl can emit null) 101 | const validTerm = term ?? ""; 102 | const validCategory = category ?? "all"; 103 | 104 | console.log( 105 | `API Call Needed: Search for "${validTerm}" with filter "${validCategory}"` 106 | ); 107 | 108 | // In a real app, you'd call your API service here: 109 | // this.productService.search(validTerm, validCategory).subscribe(...) 110 | 111 | // Update component state for display (example) 112 | this.searchResults = { term: validTerm, category: validCategory }; 113 | }); 114 | } 115 | } 116 | ``` 117 | 118 | **Explanation of the Code:** 119 | 120 | 1. **`searchTermControl` / `categoryFilterControl`:** We use Angular's `FormControl` to manage the input and select elements. 121 | 2. **`searchTerm$`:** We get an Observable of the search term's changes using `valueChanges`. We apply: 122 | - `debounceTime(400)`: To wait until the user stops typing for 400ms before considering the term stable. 123 | - `distinctUntilChanged()`: To avoid triggering searches if the debounced term is the same as the last one. 124 | - `startWith()`: To ensure the stream has an initial value so `withLatestFrom` can emit right away if the category also has a value. This makes the initial state work correctly. 125 | 3. **`categoryFilter$`:** We get an Observable of the category changes using `valueChanges`. We also use `startWith()` here for the initial value. 126 | 4. **`withLatestFrom(categoryFilter$)`:** We pipe the `searchTerm$` (our source). When `searchTerm$` emits a value (after debouncing), `withLatestFrom` looks at `categoryFilter$` and gets its _most recently emitted value_. 127 | 5. **`subscribe(([term, category]) => ...)`:** The result is an array `[sourceValue, latestOtherValue]`. We destructure this into `term` and `category`. This callback function is executed _only_ when the debounced search term changes. Inside, we have exactly what we need: the current search term and the _latest_ selected category at that moment. 128 | 6. **`takeUntilDestroyed(this.destroyRef)`:** This is the modern Angular way to handle unsubscriptions. When the `ProductSearchComponent` is destroyed, this operator automatically completes the Observable stream, preventing memory leaks without manual cleanup. 129 | 130 | So, `withLatestFrom()` is incredibly useful when an action (like searching) depends on the latest state of other configuration or filter inputs at the exact moment the action is triggered. 131 | -------------------------------------------------------------------------------- /docs/Operators/Combination/zip.md: -------------------------------------------------------------------------------- 1 | The `zip()` operator in RxJS is a **combination operator**. Its job is to combine multiple source Observables by waiting for each observable to emit a value at the **same index**, and then it emits an array containing those values paired together. 2 | 3 | ## Analogy: The Zipper 4 | 5 | Think of a clothing zipper. It has two sides (or more, if you imagine a multi-way zipper!). To close the zipper, teeth from _both_ sides must align at the same position. `zip()` works the same way: 6 | 7 | 1. It subscribes to all the source Observables you provide. 8 | 2. It waits until the **first** value arrives from **every** source. It then emits these first values together in an array: `[firstValueA, firstValueB, ...]`. 9 | 3. Then, it waits until the **second** value arrives from **every** source. It emits these second values together: `[secondValueA, secondValueB, ...]`. 10 | 4. It continues this process, index by index (0, 1, 2,...). 11 | 5. **Crucially:** If one source Observable completes _before_ another, `zip()` will stop emitting new pairs as soon as it runs out of values from the shorter source to pair with. It needs a value from _all_ sources for a given index to emit. 12 | 13 | ## Why Use `zip()`? 14 | 15 | You use `zip()` when you have multiple streams and you need to combine their values based on their **emission order or index**. You specifically want the 1st item from stream A paired with the 1st from stream B, the 2nd with the 2nd, and so on. 16 | 17 | ## Real-World Example: Pairing Related Sequential Data 18 | 19 | Imagine you have two real-time data feeds: 20 | 21 | 1. `sensorA$` emits temperature readings every second. 22 | 2. `sensorB$` emits humidity readings every second, perfectly synchronized with sensor A. 23 | 24 | You want to process these readings as pairs (temperature and humidity for the _same_ timestamp/interval). `zip` is perfect for this. 25 | 26 | Another scenario (less common for APIs, more for UI events or other streams): Suppose you want to pair every user click with a corresponding item from another list that gets populated sequentially. The first click pairs with the first item, the second click with the second, etc. 27 | 28 | ## Code Snippet Example 29 | 30 | Let's create a simple Angular component example using `zip`. We'll zip together values from two simple streams: one emitting letters ('A', 'B', 'C') quickly, and another emitting numbers (10, 20, 30, 40) more slowly. 31 | 32 | ```typescript 33 | import { Component, DestroyRef, inject, OnInit, signal } from "@angular/core"; 34 | import { zip, interval, of } from "rxjs"; 35 | import { map, take } from "rxjs/operators"; 36 | import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; 37 | 38 | @Component({ 39 | selector: "app-zip-example", 40 | standalone: true, 41 | template: ` 42 |

RxJS zip() Example

43 |

Combining letters and numbers based on index:

44 |
    45 |
  • {{ pair | json }}
  • 46 |
47 |

48 | Note: The number stream had '40', but the letter stream completed after 49 | 'C', so zip stopped. 50 |

51 | `, 52 | styles: [ 53 | ` 54 | li { 55 | font-family: monospace; 56 | } 57 | `, 58 | ], 59 | }) 60 | export class ZipExampleComponent implements OnInit { 61 | private destroyRef = inject(DestroyRef); 62 | 63 | zippedResult = signal>([]); // Signal to hold the result 64 | 65 | ngOnInit() { 66 | // Source 1: Emits 'A', 'B', 'C' one after another immediately 67 | const letters$ = of("A", "B", "C"); 68 | 69 | // Source 2: Emits 10, 20, 30, 40 every 500ms 70 | const numbers$ = interval(500).pipe( 71 | map((i) => (i + 1) * 10), // Map index 0, 1, 2, 3 to 10, 20, 30, 40 72 | take(4) // Only take the first 4 values 73 | ); 74 | 75 | // Zip them together 76 | zip(letters$, numbers$) 77 | .pipe( 78 | // zip emits arrays like [string, number] 79 | takeUntilDestroyed(this.destroyRef) // Auto-unsubscribe 80 | ) 81 | .subscribe({ 82 | next: (value) => { 83 | // Update the signal with the latest pair 84 | // NOTE: For signals, it's often better to collect all results 85 | // if the stream completes quickly, or update progressively. 86 | // Here we'll just append for demonstration. 87 | this.zippedResult.update((current) => [...current, value]); 88 | console.log("Zipped value:", value); 89 | }, 90 | complete: () => { 91 | console.log("Zip completed because the letters$ stream finished."); 92 | }, 93 | error: (err) => { 94 | console.error("Zip error:", err); 95 | }, 96 | }); 97 | } 98 | } 99 | ``` 100 | 101 | **Explanation of the Code:** 102 | 103 | 1. `letters$` emits 'A', 'B', 'C' and then completes. 104 | 2. `numbers$` starts emitting 10 (at 500ms), 20 (at 1000ms), 30 (at 1500ms), 40 (at 2000ms). 105 | 3. `zip` waits: 106 | - It gets 'A' immediately, but waits for `numbers$` to emit. 107 | - At 500ms, `numbers$` emits 10. `zip` now has the first value from both ('A', 10) -> Emits `['A', 10]`. 108 | - It gets 'B' immediately, waits for `numbers$`. 109 | - At 1000ms, `numbers$` emits 20. `zip` has the second value from both ('B', 20) -> Emits `['B', 20]`. 110 | - It gets 'C' immediately, waits for `numbers$`. 111 | - At 1500ms, `numbers$` emits 30. `zip` has the third value from both ('C', 30) -> Emits `['C', 30]`. 112 | 4. `letters$` has now completed. Even though `numbers$` emits 40 at 2000ms, `zip` cannot find a corresponding 4th value from `letters$`, so it stops and completes. 113 | 114 | ## `zip()` vs. Other Combination Operators 115 | 116 | - **`combineLatest`**: Emits an array of the _latest_ values from each source whenever _any_ source emits. Doesn't care about index, just the most recent value from all participants. 117 | - **`forkJoin`**: Waits for _all_ source observables to _complete_, then emits a single array containing the _last_ value emitted by each source. Useful for running parallel one-off tasks (like multiple HTTP requests) and getting all results together at the end. 118 | 119 | Use `zip()` specifically when the _order_ and _pairing_ by index (1st with 1st, 2nd with 2nd, etc.) is what you need. 120 | -------------------------------------------------------------------------------- /docs/Operators/Creation/empty-never.md: -------------------------------------------------------------------------------- 1 | # Empty Vs Never 2 | 3 | Let's clarify the difference between the RxJS constants `EMPTY` and `NEVER`. Both are simple, pre-defined Observable constants, but they represent very different stream behaviors, primarily concerning completion. 4 | 5 | Think of it like this: Both represent a stream that will _never give you any data_ (no `next` emissions). The difference lies in whether they tell you they are finished or just stay silent forever. 6 | 7 | 1. **`EMPTY`** 8 | 9 | - **What it does:** Represents an Observable that emits **zero** items. 10 | - **Key Behavior:** As soon as you subscribe to it, it immediately sends a **`complete`** notification. 11 | - **Analogy:** It's like a function that returns immediately without doing anything (`return;`), or reading an empty file. It quickly signals "I have nothing to give you, and I'm done." 12 | - **Use Case:** Useful when you need an Observable that does nothing but signal successful completion right away. This is often helpful in conditional logic within higher-order mapping operators (like `switchMap`, `mergeMap`, `concatMap`). For example, if a condition isn't met, you might return `EMPTY` instead of making an API call, indicating that the operation for that specific trigger completed successfully without producing a value. 13 | 14 | 2. **`NEVER`** 15 | - **What it does:** Represents an Observable that emits **zero** items. 16 | - **Key Behavior:** It **never** sends a `complete` notification and **never** sends an `error` notification. It remains silent indefinitely after subscription. 17 | - **Analogy:** It's like a process that hangs forever without producing output or terminating, or a phone line that just keeps ringing and ringing without ever being answered or going to an error state. It signals "I have nothing for you right now, and I might _never_ have anything, and I'm certainly not finished." 18 | - **Use Case:** Represents a stream that simply never emits or terminates. This can be useful in testing scenarios or when you want to keep a combination operator (like `race` or `combineLatest`) alive, even if one of its potential sources will never produce a relevant value or complete. It effectively keeps that "slot" open indefinitely without signalling completion or error. It can also be used intentionally to prevent parts of an Observable chain from completing. 19 | 20 | ## Direct Comparison 21 | 22 | | Feature | `EMPTY` | `NEVER` | 23 | | :------------------- | :-------------------------- | :--------------------- | 24 | | **`next` emissions** | 0 | 0 | 25 | | **`complete`** | Yes (immediately) | No (never) | 26 | | **`error`** | No (by default) | No (never) | 27 | | **Terminates?** | Yes (completes immediately) | No (runs indefinitely) | 28 | 29 | ## Code Snippet Demonstration 30 | 31 | ```typescript 32 | import { Component, OnInit, OnDestroy } from "@angular/core"; 33 | import { EMPTY, NEVER, Subscription } from "rxjs"; 34 | 35 | @Component({ 36 | selector: "app-empty-never-demo", 37 | template: ` 38 |

EMPTY vs NEVER Demo

39 |

40 | Check the console log at ${new Date().toLocaleTimeString("en-IN", { 41 | timeZone: "Asia/Kolkata", 42 | })} (IST). 43 |

44 |

EMPTY Status: {{ emptyStatus }}

45 |

NEVER Status: {{ neverStatus }}

46 | `, 47 | }) 48 | export class EmptyNeverDemoComponent implements OnInit, OnDestroy { 49 | emptyStatus = "Subscribing..."; 50 | neverStatus = "Subscribing..."; 51 | 52 | private emptySub: Subscription | undefined; 53 | private neverSub: Subscription | undefined; 54 | 55 | ngOnInit(): void { 56 | console.log(`--- Subscribing to EMPTY ---`); 57 | this.emptySub = EMPTY.subscribe({ 58 | next: () => { 59 | console.log("EMPTY: next (This will not be logged)"); 60 | this.emptyStatus = "Got next (unexpected)"; 61 | }, 62 | error: (err) => { 63 | console.error("EMPTY: error", err); 64 | this.emptyStatus = `Error: ${err}`; 65 | }, 66 | complete: () => { 67 | // This is called immediately! 68 | console.log( 69 | `EMPTY: complete! (Called immediately) at ${new Date().toLocaleTimeString()}` 70 | ); 71 | this.emptyStatus = "Completed immediately"; 72 | }, 73 | }); 74 | // The line below will likely log 'Completed immediately' because EMPTY completes synchronously 75 | console.log( 76 | `Current EMPTY status after sync subscribe: ${this.emptyStatus}` 77 | ); 78 | 79 | console.log(`\n--- Subscribing to NEVER ---`); 80 | this.neverSub = NEVER.subscribe({ 81 | next: () => { 82 | console.log("NEVER: next (This will not be logged)"); 83 | this.neverStatus = "Got next (unexpected)"; 84 | }, 85 | error: (err) => { 86 | console.error("NEVER: error (This will not be logged)"); 87 | this.neverStatus = `Error: ${err}`; 88 | }, 89 | complete: () => { 90 | // This is never called! 91 | console.log("NEVER: complete! (This will never be logged)"); 92 | this.neverStatus = "Completed (unexpected)"; 93 | }, 94 | }); 95 | // NEVER does nothing, so status remains 'Subscribing...' 96 | console.log( 97 | `Current NEVER status after sync subscribe: ${this.neverStatus}` 98 | ); 99 | 100 | // Set a timeout just to show NEVER doesn't complete on its own 101 | setTimeout(() => { 102 | if (this.neverStatus === "Subscribing...") { 103 | console.log( 104 | "\nAfter 2 seconds, NEVER still hasn't emitted or completed." 105 | ); 106 | this.neverStatus = "Still running after 2s (as expected)"; 107 | } 108 | }, 2000); 109 | } 110 | 111 | ngOnDestroy(): void { 112 | console.log("\n--- Component Destroying ---"); 113 | if (this.emptySub && !this.emptySub.closed) { 114 | // This check is usually false as EMPTY completes immediately 115 | console.log("Unsubscribing from EMPTY (already closed likely)."); 116 | this.emptySub.unsubscribe(); 117 | } else { 118 | console.log("EMPTY subscription was already closed."); 119 | } 120 | 121 | if (this.neverSub && !this.neverSub.closed) { 122 | // This is important for NEVER if the component is destroyed 123 | console.log("Unsubscribing from NEVER."); 124 | this.neverSub.unsubscribe(); 125 | } 126 | } 127 | } 128 | ``` 129 | 130 | ## Summary 131 | 132 | Choose `EMPTY` when you need an Observable that does nothing but signals successful completion instantly. 133 | 134 | Choose `NEVER` when you need an Observable that does nothing and _never_ signals completion or error. 135 | -------------------------------------------------------------------------------- /docs/Operators/Creation/from.md: -------------------------------------------------------------------------------- 1 | The `from()` operator is another **creation operator**, but its main purpose is to **convert** various other types of objects and data structures into an Observable. It's versatile and can handle things like: 2 | 3 | - Arrays (or array-like objects like `NodeList`, `arguments`) 4 | - Iterables (like `Map`, `Set`, or strings) 5 | - Promises 6 | - Other Observables (though this is less common as you usually just use the Observable directly) 7 | 8 | When given an array or iterable, `from()` emits each item from that collection one by one, in order, and then completes. When given a Promise, it waits for the Promise to resolve, emits the resolved value as its single `next` notification, and then completes. If the Promise rejects, `from()` emits an error notification. 9 | 10 | ## Key Characteristics 11 | 12 | - **Conversion:** Its primary role is converting something _else_ into an Observable. 13 | - **Single Argument:** It takes only _one_ argument – the object to convert. 14 | - **Emission Behavior:** 15 | - For arrays/iterables: Emits items synchronously, one by one. 16 | - For Promises: Emits the resolved value asynchronously when the promise settles. 17 | - **Completes:** It completes after emitting all items (from iterables) or the resolved value (from promises). 18 | 19 | ## Difference from `of()` 20 | 21 | This is a common point of confusion: 22 | 23 | - `of([1, 2, 3])`: Emits the **entire array `[1, 2, 3]` as a single item**. 24 | - `from([1, 2, 3])`: Emits **`1`**, then **`2`**, then **`3`** as three separate items. 25 | 26 | ## Real-World Example Scenarios 27 | 28 | 1. **Processing Array Items:** You might fetch configuration data which arrives as a plain array, but you want to use RxJS operators (`map`, `filter`, etc.) to process _each item_ in the array within a stream. 29 | 2. **Integrating Promises:** You're working within an Angular/RxJS codebase, but need to interact with a browser API or a third-party JavaScript library that returns a `Promise`. `from()` lets you easily bring that promise-based result into your RxJS workflow. 30 | 31 | ## Code Snippet 1 (Using `from()` with an Array) 32 | 33 | Let's say you have an array of user IDs and you want to create an Observable stream that emits each ID individually. 34 | 35 | ```typescript 36 | import { Component, OnInit } from "@angular/core"; 37 | import { from, Observable } from "rxjs"; // Import 'from' 38 | import { map } from "rxjs/operators"; 39 | 40 | @Component({ 41 | selector: "app-user-id-processor", 42 | template: ` 43 |

Processing User IDs:

44 |
    45 |
  • 46 | {{ processedId }} 47 |
  • 48 |
49 | `, 50 | }) 51 | export class UserIdProcessorComponent implements OnInit { 52 | userIds: string[] = ["user-001", "user-007", "user-101"]; 53 | processedUserIds$: Observable | undefined; 54 | 55 | ngOnInit(): void { 56 | console.log("Component initializing..."); 57 | 58 | // Convert the userIds array into an Observable stream 59 | const userIdStream$ = from(this.userIds); 60 | 61 | // Example: Use RxJS operators on the stream from the array 62 | this.processedUserIds$ = userIdStream$.pipe( 63 | map((id) => `Processed: ${id.toUpperCase()}`) // Apply an operator to each emitted ID 64 | ); 65 | 66 | console.log( 67 | "Observable created from array. Subscribing manually for demonstration..." 68 | ); 69 | 70 | this.processedUserIds$.subscribe({ 71 | next: (value) => { 72 | // Called for each ID ('Processed: USER-001', 'Processed: USER-007', etc.) 73 | console.log("Received processed ID:", value); 74 | }, 75 | error: (err) => { 76 | console.error("Error:", err); // Won't happen here 77 | }, 78 | complete: () => { 79 | // Called after the last ID is processed and emitted 80 | console.log("User ID stream complete!"); 81 | }, 82 | }); 83 | console.log("Subscription processing for array finished (synchronously)."); 84 | } 85 | } 86 | ``` 87 | 88 | ## Code Snippet 2 (Using `from()` with a Promise) 89 | 90 | Imagine you need to use the browser's `Workspace` API (which returns a Promise) to get some data and integrate it into your component's Observable-based logic. 91 | 92 | ```typescript 93 | import { Component, OnInit } from "@angular/core"; 94 | import { from, Observable } from "rxjs"; // Import 'from' 95 | import { switchMap, catchError, tap } from "rxjs/operators"; 96 | 97 | @Component({ 98 | selector: "app-promise-integrator", 99 | template: ` 100 |

Data from Promise:

101 |
102 | Data fetched: {{ fetchedData | json }} 103 |
104 |
Error: {{ errorMessage }}
105 | `, 106 | }) 107 | export class PromiseIntegratorComponent implements OnInit { 108 | data$: Observable | undefined; 109 | errorMessage: string = ""; 110 | 111 | ngOnInit(): void { 112 | console.log("Component initializing..."); 113 | 114 | // 1. Create a Promise (e.g., using fetch) 115 | const dataPromise = fetch("https://api.example.com/data") // Example API 116 | .then((response) => { 117 | if (!response.ok) { 118 | throw new Error(`HTTP error! status: ${response.status}`); 119 | } 120 | return response.json(); // This also returns a Promise 121 | }); 122 | 123 | console.log("Promise created. Converting to Observable using from()..."); 124 | 125 | // 2. Convert the Promise to an Observable using from() 126 | const promiseAsObservable$ = from(dataPromise); 127 | 128 | // 3. Use the Observable in your RxJS pipeline 129 | this.data$ = promiseAsObservable$.pipe( 130 | tap((data) => 131 | console.log("Data received from promise via Observable:", data) 132 | ), 133 | catchError((error) => { 134 | // Handle potential errors from the promise (fetch failure, JSON parsing error) 135 | console.error("Error emitted from promise Observable:", error); 136 | this.errorMessage = error.message || "Failed to fetch data"; 137 | return from([]); // Return an empty observable to prevent killing the main stream 138 | // Or: return throwError(() => new Error('Custom error message')); 139 | }) 140 | ); 141 | 142 | console.log( 143 | "Subscribing to promise-based Observable (will resolve asynchronously)..." 144 | ); 145 | // AsyncPipe in the template will handle the subscription here. 146 | // Manual subscription for logging completion: 147 | this.data$.subscribe({ 148 | next: () => { 149 | /* Handled by tap above / AsyncPipe */ 150 | }, 151 | error: () => { 152 | /* Handled by catchError above */ 153 | }, 154 | complete: () => { 155 | // Called only if the promise resolves successfully and catchError doesn't replace the stream 156 | if (!this.errorMessage) { 157 | console.log("Promise-based Observable stream complete!"); 158 | } 159 | }, 160 | }); 161 | } 162 | } 163 | ``` 164 | 165 | **Explanation:** 166 | 167 | - **Array Example:** `from(this.userIds)` takes the array and emits each string element individually, allowing operators like `map` to work on each one. 168 | - **Promise Example:** `from(dataPromise)` takes the promise returned by `Workspace().then(...)`. It waits (asynchronously) for the promise to resolve. If it resolves successfully, the resolved JSON data is emitted as the `next` value. If the promise rejects (e.g., network error), `from()` emits an `error` notification, which we handle with `catchError`. The stream completes after the single value (or error) is emitted. 169 | 170 | ## Summary 171 | 172 | `from()` is your go-to operator when you need to turn an array, iterable, or Promise into an Observable stream, typically to process its contents individually or integrate it seamlessly into your existing RxJS pipelines. 173 | -------------------------------------------------------------------------------- /docs/Operators/Creation/interval.md: -------------------------------------------------------------------------------- 1 | `interval()` is an RxJS **creation operator** that generates an Observable which emits sequential numbers (0, 1, 2, 3, and so on) at a specified, regular time interval (in milliseconds). 2 | 3 | Think of it as setting up a metronome that ticks indefinitely, emitting the tick count each time. 4 | 5 | ## Key Characteristics 6 | 7 | - **Sequential Numbers:** Emits `0`, then `1`, then `2`, ... 8 | - **Timed Emissions:** You specify the delay between emissions (e.g., `interval(1000)` emits every 1 second). 9 | - **Asynchronous:** The emissions happen asynchronously based on the timer you set. 10 | - **Never Completes:** This is important! By default, `interval()` _never_ stops emitting on its own. It will run forever unless you explicitly unsubscribe or use another operator (like `take`) to limit it. 11 | - **Cold Observable:** Each subscription starts its own independent timer. If two parts of your code subscribe to `interval(1000)`, they will each get their own sequence starting from 0. 12 | 13 | ## Real-World Example Scenario 14 | 15 | A very common use case in web applications, including Angular, is **polling**. Imagine you need to check a server endpoint repeatedly to see if there's new data available, like: 16 | 17 | - Checking for new chat messages every 5 seconds. 18 | - Updating a dashboard with fresh statistics every 30 seconds. 19 | - Checking the status of a long-running background job every 10 seconds. 20 | 21 | `interval()` provides the timed trigger for making these checks. 22 | 23 | ## Code Snippet (Angular Component - Polling Example) 24 | 25 | Let's create a component that checks for hypothetical server status updates every 5 seconds (5000 milliseconds). We'll use `interval()` to trigger the check and `HttpClient` to make the request. We _must_ remember to clean up the interval when the component is destroyed. 26 | 27 | ```typescript 28 | import { Component, OnInit, OnDestroy } from "@angular/core"; 29 | import { HttpClient } from "@angular/common/http"; 30 | import { interval, Subscription, Observable } from "rxjs"; 31 | import { switchMap, startWith, catchError, tap } from "rxjs/operators"; 32 | 33 | @Component({ 34 | selector: "app-status-poller", 35 | template: ` 36 |

Server Status Check

37 |

Checking every 5 seconds...

38 |
39 | Last Status: {{ status | json }} 40 |
41 | Last Checked: {{ lastChecked | date : "mediumTime" }} 42 |
43 |
Error: {{ errorMessage }}
44 | `, 45 | }) 46 | export class StatusPollerComponent implements OnInit, OnDestroy { 47 | status: any = null; 48 | lastChecked: Date | null = null; 49 | errorMessage: string = ""; 50 | 51 | // To hold the subscription to the interval observable 52 | private pollingSubscription: Subscription | undefined; 53 | private readonly POLLING_INTERVAL_MS = 5000; // 5 seconds 54 | 55 | constructor(private http: HttpClient) {} 56 | 57 | ngOnInit(): void { 58 | console.log( 59 | `Starting status polling now (${new Date().toLocaleString()})...` 60 | ); 61 | 62 | // Create an observable that emits every 5 seconds 63 | const pollingInterval$ = interval(this.POLLING_INTERVAL_MS); 64 | 65 | this.pollingSubscription = pollingInterval$ 66 | .pipe( 67 | // Use startWith(0) to trigger the first check immediately, 68 | // instead of waiting for the first 5-second interval to pass. 69 | startWith(0), // Emit 0 immediately, then continue with interval 0, 1, 2... 70 | tap(() => 71 | console.log("Polling interval triggered... Fetching status.") 72 | ), 73 | // For each emission from the interval, switch to making an HTTP GET request 74 | switchMap(() => { 75 | // switchMap cancels previous pending HTTP requests if the interval fires again quickly 76 | return this.http.get("/api/server/status").pipe( 77 | // Replace with your actual API endpoint 78 | catchError((error) => { 79 | // Handle HTTP errors gracefully 80 | console.error("Error fetching status:", error); 81 | this.errorMessage = `Failed to fetch status (${ 82 | error.statusText || "Unknown Error" 83 | })`; 84 | this.status = null; // Clear previous status on error 85 | // Return an empty observable or throwError to stop polling if needed 86 | // For this example, we'll let polling continue 87 | return []; // Don't emit anything on error, effectively skipping this interval's update 88 | }) 89 | ); 90 | }) 91 | ) 92 | .subscribe({ 93 | next: (statusData) => { 94 | // Successfully got status data from the API 95 | console.log("Status received:", statusData); 96 | this.status = statusData; 97 | this.lastChecked = new Date(); // Record the time of the successful check 98 | this.errorMessage = ""; // Clear any previous error message 99 | }, 100 | error: (err) => { 101 | // This would typically catch errors NOT handled by the inner catchError, 102 | // or if catchError re-throws an error. 103 | console.error("Polling stream error:", err); 104 | this.errorMessage = "Polling mechanism failed."; 105 | }, 106 | // Note: No 'complete' handler needed as interval() doesn't complete. 107 | }); 108 | } 109 | 110 | ngOnDestroy(): void { 111 | // VERY IMPORTANT: Unsubscribe when the component is destroyed! 112 | // Otherwise, the interval and polling will continue running in the background indefinitely, 113 | // causing memory leaks and unnecessary network requests. 114 | if (this.pollingSubscription) { 115 | this.pollingSubscription.unsubscribe(); 116 | console.log("Polling stopped and unsubscribed."); 117 | } 118 | } 119 | } 120 | ``` 121 | 122 | **Explanation:** 123 | 124 | 1. **`interval(5000)`**: Creates the basic timer emitting `0, 1, 2,...` every 5 seconds. 125 | 2. **`startWith(0)`**: We often want to check _immediately_ when the component loads, not wait for the first interval. `startWith(0)` makes it emit `0` right away. 126 | 3. **`switchMap(...)`**: This is key for polling. When the interval emits, `switchMap` subscribes to the inner Observable (the `http.get()` call). If the interval emits _again_ before the HTTP request finishes, `switchMap` automatically _cancels_ the previous pending HTTP request and starts a new one. This prevents outdated responses or unnecessary concurrent requests for simple polling. 127 | 4. **`catchError(...)`**: Inside `switchMap`, we catch errors from the `http.get()` call specifically, allowing us to handle API errors without stopping the entire polling `interval`. 128 | 5. **`subscribe(...)`**: We process the successful status updates received from the API. 129 | 6. **`ngOnDestroy()` / `unsubscribe()`**: This is **critical**. We store the subscription returned by `.subscribe()` and call `.unsubscribe()` on it when the component is destroyed. This stops the `interval` timer and prevents memory leaks. 130 | 131 | ## Summary 132 | 133 | `interval()` is a fundamental tool for creating streams based on timed intervals, frequently used for polling or triggering periodic actions, but always requiring careful handling of unsubscription to avoid issues. 134 | -------------------------------------------------------------------------------- /docs/Operators/Creation/of.md: -------------------------------------------------------------------------------- 1 | The `of()` operator is a **creation operator**. Its job is simple: it creates an Observable that takes a sequence of arguments you provide, emits each of those arguments one after the other in the order you provided them, and then immediately **completes**. 2 | 3 | Think of it as a way to turn a fixed set of known values into an Observable stream. 4 | 5 | ## Key Characteristics 6 | 7 | - **Synchronous:** It emits all its values immediately and synchronously when you subscribe. 8 | - **Ordered:** It emits the values in the exact order they are passed as arguments. 9 | - **Completes:** After emitting the last value, it sends a completion notification. 10 | - **Takes Multiple Arguments:** You list the values you want to emit directly as arguments to `of()`. 11 | 12 | ## Real-World Example Scenario 13 | 14 | Imagine you have a component in your Angular application that needs to display a list of predefined, static options, like user roles available for selection, default chart types, or initial filter categories. These values are known upfront, they don't need to be fetched from an API right now, but maybe other parts of your application expect to work with Observables for consistency. `of()` is perfect for creating an Observable stream from this static list. 15 | 16 | **Example:** Let's say you want to display a list of predefined priority levels ('Low', 'Medium', 'High', 'Critical') in a dropdown or as filter options. 17 | 18 | ## Code Snippet (Angular Component) 19 | 20 | ```typescript 21 | import { Component, OnInit } from "@angular/core"; 22 | import { Observable, of } from "rxjs"; // Import 'of' 23 | 24 | @Component({ 25 | selector: "app-priority-options", 26 | template: ` 27 |

Available Priorities:

28 |
    29 |
  • {{ priority }}
  • 30 |
31 | `, 32 | }) 33 | export class PriorityOptionsComponent implements OnInit { 34 | // Declare an Observable property to hold the stream of priorities 35 | priorities$: Observable | undefined; 36 | 37 | ngOnInit(): void { 38 | console.log("Component initializing..."); 39 | 40 | // Use of() to create an Observable from a fixed list of strings 41 | this.priorities$ = of("Low", "Medium", "High", "Critical"); 42 | 43 | console.log( 44 | "Observable created with of(). Subscribing manually for demonstration..." 45 | ); 46 | 47 | // Manual subscription (often handled by AsyncPipe in templates as shown above) 48 | this.priorities$.subscribe({ 49 | next: (value) => { 50 | // This will be called for each value ('Low', 'Medium', 'High', 'Critical') 51 | console.log("Received priority:", value); 52 | }, 53 | error: (err) => { 54 | // This won't be called in this case because of() doesn't error 55 | console.error("Error:", err); 56 | }, 57 | complete: () => { 58 | // This will be called immediately after the last value ('Critical') is emitted 59 | console.log("Priority stream complete!"); 60 | }, 61 | }); 62 | 63 | console.log("Subscription processing finished (synchronously)."); 64 | } 65 | } 66 | ``` 67 | 68 | **Explanation:** 69 | 70 | 1. **`import { of } from 'rxjs';`**: We import the `of` operator. 71 | 2. **`this.priorities$ = of('Low', 'Medium', 'High', 'Critical');`**: We call `of()` with our list of priority strings as arguments. This immediately creates an Observable (`this.priorities$`). 72 | 3. **Subscription Behavior:** 73 | - When `subscribe()` is called (either manually or via the `async` pipe), the Observable created by `of()` _synchronously_ emits 'Low', then 'Medium', then 'High', then 'Critical'. 74 | - The `next` handler in our manual subscription logs each of these values. 75 | - Immediately after emitting 'Critical', the Observable sends the `complete` notification, and our `complete` handler logs "Priority stream complete!". 76 | - The `error` handler is never called because `of()` successfully emits its predefined values. 77 | 4. **`async` pipe:** In the template (`*ngFor="let priority of priorities$ | async"`), Angular's `async` pipe subscribes to `priorities$`, receives each value ('Low', 'Medium', etc.), updates the list, and automatically unsubscribes and handles completion/errors when the component is destroyed. 78 | 79 | ## Summary 80 | 81 | `of()` is a straightforward way to create an Observable when you have a fixed number of values readily available and you want them emitted sequentially as part of a stream. 82 | -------------------------------------------------------------------------------- /docs/Operators/Creation/timer.md: -------------------------------------------------------------------------------- 1 | `timer()` is an RxJS **creation operator** that creates an Observable which emits values after a specified delay, and can optionally continue emitting values at regular intervals thereafter. 2 | 3 | It behaves differently based on the arguments you provide: 4 | 5 | 1. **`timer(dueTime)`:** 6 | 7 | - Waits for the specified `dueTime` (in milliseconds, or a `Date`). 8 | - Emits a single value: `0`. 9 | - Immediately **completes**. 10 | 11 | 2. **`timer(initialDelay, period)`:** 12 | - Waits for the specified `initialDelay` (in milliseconds). 13 | - Emits the first value: `0`. 14 | - Then, waits for `period` milliseconds. 15 | - Emits the next value: `1`. 16 | - Continues emitting sequential numbers (`2`, `3`, ...) every `period` milliseconds. 17 | - This form **never completes** on its own (just like `interval`). 18 | 19 | ## Key Characteristics 20 | 21 | - **Asynchronous:** Emissions happen after specified delays. 22 | - **Completion:** 23 | - Completes after one emission (`0`) if only `dueTime` is provided. 24 | - Never completes if `period` is also provided. 25 | - **Initial Delay:** The second form allows a specific delay before the _first_ emission, which is different from `interval` (where the first emission occurs after the _first period_). 26 | - **Cold Observable:** Each subscription starts its own independent timer. 27 | 28 | ## Difference from `interval()` 29 | 30 | - `interval(1000)`: Waits 1000ms, emits `0`, waits 1000ms, emits `1`, ... 31 | - `timer(1000)`: Waits 1000ms, emits `0`, completes. 32 | - `timer(0, 1000)`: Waits 0ms (emits `0` immediately), waits 1000ms, emits `1`, waits 1000ms, emits `2`, ... (Starts immediately, then intervals). 33 | - `timer(5000, 1000)`: Waits 5000ms, emits `0`, waits 1000ms, emits `1`, waits 1000ms, emits `2`, ... (Initial delay before starting intervals). 34 | 35 | ## Real-World Example Scenarios 36 | 37 | 1. **`timer(dueTime)` Scenario: Delayed Action / Welcome Message** 38 | 39 | - Imagine you want to show a "Need help?" tooltip or a welcome message in your Angular app, but only _after_ the user has been on the page for, say, 3 seconds, giving them time to look around first. You only want this message to appear once. 40 | 41 | 2. **`timer(initialDelay, period)` Scenario: Delayed Polling** 42 | - Similar to the `interval` polling example, but maybe you don't want to start checking the server status _immediately_ on component load. Perhaps you want to wait 5 seconds for initial setup/rendering to finish, and _then_ start checking every 10 seconds. 43 | 44 | **Code Snippet 1 (Using `timer(dueTime)` - Delayed Message):** 45 | 46 | ```typescript 47 | import { Component, OnInit } from "@angular/core"; 48 | import { timer, Subscription } from "rxjs"; 49 | 50 | @Component({ 51 | selector: "app-delayed-message", 52 | template: ` 53 |

Welcome!

54 |
55 | Looks like you've been here a few seconds. Need any help? 56 |
57 | `, 58 | }) 59 | export class DelayedMessageComponent implements OnInit { 60 | showHelpMessage = false; 61 | private timerSubscription: Subscription | undefined; 62 | 63 | ngOnInit(): void { 64 | const messageDelay = 3000; // 3 seconds 65 | console.log( 66 | `Component initialized at ${new Date().toLocaleTimeString()}. Setting timer for ${messageDelay}ms.` 67 | ); 68 | 69 | // Create an observable that emits 0 after 3 seconds, then completes. 70 | const delayTimer$ = timer(messageDelay); 71 | 72 | this.timerSubscription = delayTimer$.subscribe({ 73 | next: (value) => { 74 | // This will be called once with value 0 after 3 seconds 75 | console.log( 76 | `Timer emitted ${value} at ${new Date().toLocaleTimeString()}. Showing message.` 77 | ); 78 | this.showHelpMessage = true; 79 | }, 80 | complete: () => { 81 | // This will be called immediately after the 'next' emission 82 | console.log("Delay timer completed."); 83 | // Since it completes, explicit unsubscription in ngOnDestroy for *this specific timer* 84 | // isn't strictly necessary for leak prevention, but is still good practice 85 | // if the component could be destroyed *before* the timer fires. 86 | }, 87 | }); 88 | } 89 | 90 | // Good practice to include, especially if combining with other subscriptions 91 | ngOnDestroy(): void { 92 | if (this.timerSubscription) { 93 | this.timerSubscription.unsubscribe(); 94 | console.log( 95 | "Delayed message timer unsubscribed (if it was still running)." 96 | ); 97 | } 98 | } 99 | } 100 | ``` 101 | 102 | **Code Snippet 2 (Using `timer(initialDelay, period)` - Delayed Polling):** 103 | 104 | Let's adapt the polling example to wait 5 seconds initially, then poll every 10 seconds. 105 | 106 | ```typescript 107 | import { Component, OnInit, OnDestroy } from "@angular/core"; 108 | import { HttpClient } from "@angular/common/http"; 109 | import { timer, Subscription, Observable } from "rxjs"; 110 | import { switchMap, catchError, tap } from "rxjs/operators"; 111 | 112 | @Component({ 113 | selector: "app-delayed-poller", 114 | template: ` 115 |

Delayed Server Status Check

116 |

Waiting 5s initially, then checking every 10s...

117 |
118 | Last Status: {{ status | json }} 119 |
120 | Last Checked: {{ lastChecked | date : "mediumTime" }} 121 |
122 |
Error: {{ errorMessage }}
123 | `, 124 | }) 125 | export class DelayedPollerComponent implements OnInit, OnDestroy { 126 | status: any = null; 127 | lastChecked: Date | null = null; 128 | errorMessage: string = ""; 129 | 130 | private pollingSubscription: Subscription | undefined; 131 | private readonly INITIAL_DELAY_MS = 5000; // 5 seconds 132 | private readonly POLLING_PERIOD_MS = 10000; // 10 seconds 133 | 134 | constructor(private http: HttpClient) {} 135 | 136 | ngOnInit(): void { 137 | console.log( 138 | `Starting delayed polling now (${new Date().toLocaleString()}). Initial delay: ${ 139 | this.INITIAL_DELAY_MS 140 | }ms, Period: ${this.POLLING_PERIOD_MS}ms.` 141 | ); 142 | 143 | // Create timer: waits 5s, emits 0, then emits 1, 2,... every 10s 144 | const pollingTimer$ = timer(this.INITIAL_DELAY_MS, this.POLLING_PERIOD_MS); 145 | 146 | this.pollingSubscription = pollingTimer$ 147 | .pipe( 148 | tap((count) => 149 | console.log(`Polling timer emitted ${count}. Fetching status.`) 150 | ), 151 | // Switch to HTTP request on each timer emission 152 | switchMap((count) => { 153 | return this.http.get("/api/server/status").pipe( 154 | // Use your actual endpoint 155 | catchError((error) => { 156 | console.error( 157 | `Error fetching status (emission ${count}):`, 158 | error 159 | ); 160 | this.errorMessage = `Failed to fetch status (${ 161 | error.statusText || "Unknown Error" 162 | })`; 163 | this.status = null; 164 | return []; // Continue polling even after error 165 | }) 166 | ); 167 | }) 168 | ) 169 | .subscribe({ 170 | next: (statusData) => { 171 | console.log("Status received:", statusData); 172 | this.status = statusData; 173 | this.lastChecked = new Date(); 174 | this.errorMessage = ""; 175 | }, 176 | error: (err) => { 177 | console.error("Polling stream error:", err); 178 | this.errorMessage = "Polling mechanism failed."; 179 | }, 180 | // No 'complete' handler here because this timer variant never completes 181 | }); 182 | } 183 | 184 | ngOnDestroy(): void { 185 | // VERY IMPORTANT for the timer(initialDelay, period) variant! 186 | if (this.pollingSubscription) { 187 | this.pollingSubscription.unsubscribe(); 188 | console.log("Delayed polling stopped and unsubscribed."); 189 | } 190 | } 191 | } 192 | ``` 193 | 194 | **Explanation:** 195 | 196 | - **Example 1:** `timer(3000)` waits 3 seconds, emits `0`, completes. Useful for one-off delayed actions. 197 | - **Example 2:** `timer(5000, 10000)` waits 5 seconds, emits `0`, then continues emitting `1, 2, ...` at 10-second intervals. This requires careful unsubscription in `ngOnDestroy` just like `interval`. 198 | 199 | ## Summary 200 | 201 | `timer()` provides more flexibility than `interval()` for controlling _when_ emissions start, offering both a one-shot delay (`timer(dueTime)`) and a recurring emission with an initial offset (`timer(initialDelay, period)`). Remember to unsubscribe from the recurring variant! 202 | -------------------------------------------------------------------------------- /docs/Operators/Error/catchError.md: -------------------------------------------------------------------------------- 1 | `catchError()` is an RxJS operator used for **graceful error handling** within an Observable stream. When an error occurs in the source Observable (or in any preceding operators in the `pipe`), `catchError` intercepts that error notification. It gives you a chance to: 2 | 3 | 1. **Analyze the error:** Log it, send it to a monitoring service, etc. 4 | 2. **Attempt recovery:** You might retry the operation (often using the `retry` operator before `catchError`). 5 | 3. **Provide a fallback value:** Return a default value or an empty state so the stream can continue gracefully instead of just terminating. 6 | 4. **Re-throw the error:** If you can't handle it, you can let the error propagate further down the chain to the subscriber's error handler. 7 | 8 | ## How `catchError` Works 9 | 10 | - You place `catchError` inside the `.pipe()` method. 11 | - It takes a function as an argument. This function receives the `error` object and optionally the `caught` Observable (the source stream that errored, useful for retrying). 12 | - **Crucially, this function MUST return a new Observable.** 13 | - If you return `of(someDefaultValue)` (e.g., `of([])`, `of(null)`), the outer stream will receive that default value in its `next` handler, and then it will _complete_ successfully (it won't hit the `error` handler of the subscription). 14 | - If you return `EMPTY` (from RxJS), the outer stream simply completes without emitting any further values. 15 | - If you `throw error` (or `throw new Error(...)`) inside the `catchError` function, the error is passed along to the `error` handler of your `subscribe` block (or the next `catchError` downstream). 16 | 17 | ## Real-World Example: Handling HTTP Request Errors 18 | 19 | This is the most common use case in Angular. Imagine fetching user data from an API. The API might fail for various reasons (server down, user not found, network issue). Instead of letting the error break your component, you want to catch it, show a message, and perhaps return `null` or an empty user object. 20 | 21 | ## Code Snippet Example 22 | 23 | Let's create a component that tries to fetch user data. We'll use `catchError` to handle potential `HttpClient` errors. 24 | 25 | ```typescript 26 | import { Component, DestroyRef, inject, OnInit, signal } from "@angular/core"; 27 | import { HttpClient, HttpErrorResponse } from "@angular/common/http"; 28 | import { Observable, of, EMPTY, throwError } from "rxjs"; // Import 'of', 'EMPTY', 'throwError' 29 | import { catchError, tap } from "rxjs/operators"; 30 | import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; 31 | 32 | interface User { 33 | id: number; 34 | name: string; 35 | email: string; 36 | } 37 | 38 | @Component({ 39 | selector: "app-user-profile", 40 | standalone: true, 41 | imports: [], // Add CommonModule if using *ngIf/*ngFor later 42 | template: ` 43 |

User Profile

44 |
Loading user data...
45 | 46 |
47 |

ID: {{ userData.id }}

48 |

Name: {{ userData.name }}

49 |

Email: {{ userData.email }}

50 |
51 | 52 |
Error: {{ errorMsg() }}
53 | 54 | 55 | `, 56 | }) 57 | export class UserProfileComponent implements OnInit { 58 | private http = inject(HttpClient); 59 | private destroyRef = inject(DestroyRef); 60 | 61 | // --- State Signals --- 62 | user = signal(null); 63 | loading = signal(false); 64 | errorMsg = signal(null); 65 | 66 | private userId = 1; // Example user ID 67 | 68 | ngOnInit() { 69 | this.loadUser(); 70 | } 71 | 72 | loadUser() { 73 | this.loading.set(true); 74 | this.errorMsg.set(null); // Clear previous errors 75 | this.user.set(null); // Clear previous user data 76 | 77 | this.fetchUserData(this.userId) 78 | .pipe( 79 | takeUntilDestroyed(this.destroyRef) // Auto-unsubscribe on destroy 80 | ) 81 | .subscribe({ 82 | next: (userData) => { 83 | this.user.set(userData); // Update user signal on success 84 | this.loading.set(false); 85 | console.log("User data loaded:", userData); 86 | }, 87 | error: (err) => { 88 | // This error handler is called ONLY if catchError re-throws the error 89 | this.loading.set(false); 90 | // Error is already set by catchError if we return a fallback 91 | // If catchError re-threw, we might set a generic message here 92 | if (!this.errorMsg()) { 93 | // Check if message wasn't set by catchError 94 | this.errorMsg.set("An unexpected error occurred downstream."); 95 | } 96 | console.error("Subscription Error Handler:", err); 97 | }, 98 | complete: () => { 99 | // Called when the stream finishes successfully 100 | // (including after catchError returns a fallback like 'of(null)') 101 | this.loading.set(false); // Ensure loading is off 102 | console.log("User data stream completed."); 103 | }, 104 | }); 105 | } 106 | 107 | private fetchUserData(id: number): Observable { 108 | // Use a non-existent URL to force an error for demonstration 109 | // const apiUrl = `/api/users/${id}`; // Real URL 110 | const apiUrl = `/api/non-existent-users/${id}`; // Fake URL for testing error 111 | 112 | return this.http.get(apiUrl).pipe( 113 | tap(() => console.log(`Attempting to fetch user ${id}`)), // Side effect logging 114 | 115 | // --- catchError Operator --- 116 | catchError((error: HttpErrorResponse) => { 117 | console.error("HTTP Error intercepted by catchError:", error); 118 | 119 | // ---- Strategy 1: Handle and return a fallback value ---- 120 | // Set user-friendly error message 121 | if (error.status === 404) { 122 | this.errorMsg.set(`User with ID ${id} not found.`); 123 | } else if (error.status === 0 || error.status >= 500) { 124 | this.errorMsg.set( 125 | "Server error or network issue. Please try again later." 126 | ); 127 | } else { 128 | this.errorMsg.set(`An error occurred: ${error.message}`); 129 | } 130 | this.loading.set(false); // Turn off loading indicator 131 | // Return null as a fallback. The 'next' handler of subscribe will receive null. 132 | return of(null); 133 | 134 | // ---- Strategy 2: Handle and return EMPTY (completes without value) ---- 135 | // this.errorMsg.set('Could not load user data.'); 136 | // this.loading.set(false); 137 | // return EMPTY; // Stream completes, 'next' handler is not called. 138 | 139 | // ---- Strategy 3: Log and re-throw the error ---- 140 | // this.errorMsg.set('Failed to load user data. Error propagated.'); // Set msg here or in subscribe error block 141 | // this.loading.set(false); 142 | // return throwError(() => new Error(`Failed fetching user ${id}: ${error.message}`)); // Propagate error to subscribe's error handler 143 | }) 144 | ); 145 | } 146 | } 147 | ``` 148 | 149 | **Explanation:** 150 | 151 | 1. `WorkspaceUserData` makes an HTTP GET request using `HttpClient`. 152 | 2. We `.pipe()` the result through `catchError`. 153 | 3. If the HTTP request fails (e.g., returns 404 Not Found), the `catchError` function executes. 154 | 4. Inside `catchError`: 155 | - We log the actual `HttpErrorResponse`. 156 | - We set a user-friendly error message in the `errorMsg` signal based on the error status. 157 | - We set `loading` to false. 158 | - **Crucially, we return `of(null)`.** This creates a _new_ observable that emits `null` once and then completes. 159 | 5. Because `catchError` returned `of(null)`, the original stream is considered "handled." The `subscribe` block's `next` handler receives `null`. The component updates the `user` signal to `null` and the `error` handler _is not_ executed. The stream completes normally. 160 | 6. The template uses `*ngIf` directives bound to the signals (`user()`, `errorMsg()`, `loading()`) to conditionally display the user data, the loading indicator, or the error message. 161 | 162 | If we had chosen Strategy 3 (using `throwError`), the error _would_ propagate to the `subscribe` block's `error` handler. 163 | 164 | `catchError` is essential for building robust Angular applications that can gracefully handle failures in asynchronous operations like API calls. 165 | -------------------------------------------------------------------------------- /docs/Operators/Filtering/debounceTime.md: -------------------------------------------------------------------------------- 1 | `debounceTime()` is a rate-limiting operator in RxJS. It helps control how often values are emitted from a source Observable, especially when the source emits values very rapidly. 2 | 3 | Think of it like this: `debounceTime()` waits for a **pause** in the emissions from the source. When the source emits a value, `debounceTime` starts a timer for a specified duration (let's say `X` milliseconds). 4 | 5 | - If the source emits _another_ value _before_ that `X` milliseconds timer runs out, the operator discards the previous value and restarts the timer for the _new_ value. 6 | - Only if the timer completes its full `X` milliseconds _without_ any new values arriving from the source, will `debounceTime` finally emit the _last_ value it received. 7 | 8 | In short, it only emits a value after a specific period of **silence** from the source Observable. 9 | 10 | ## Key Characteristics 11 | 12 | 1. **Requires Silence:** It waits for a specified duration (`dueTime`) where no new values are emitted by the source. 13 | 2. **Emits Last Value:** When the silence duration is met, it emits the _most recent_ value received from the source _before_ the silence began. 14 | 3. **Resets Timer:** Each new emission from the source before the `dueTime` expires resets the timer. Intermediate values are discarded. 15 | 4. **Rate Limiting:** Effectively limits the rate at which values pass through, based on pauses in activity. 16 | 17 | ## Real-World Analogy: Autocomplete Search Box 18 | 19 | This is the classic example! Imagine searching on a website. You type into the search box: 20 | 21 | - `L` -> (API call for "L"? No, too quick!) 22 | - `La` -> (API call for "La"? No, too quick!) 23 | - `Lap` -> (API call for "Lap"? No, too quick!) 24 | - `Lapt` -> (API call for "Lapt"? No, too quick!) 25 | - `Lapto` -> (API call for "Lapto"? No, too quick!) 26 | - `Laptop` -> (User pauses typing for 300ms...) -> **OK, NOW send API request for "Laptop"** 27 | 28 | You don't want to send an API request to your server for _every single letter_ the user types. That would be incredibly inefficient and costly. Instead, you use `debounceTime(300)`. The operator waits until the user pauses typing for 300 milliseconds. Only then does it take the _last_ value typed ("Laptop") and send it to the server for searching. If the user types quickly without pausing, all the intermediate values ("L", "La", "Lap", etc.) are ignored. 29 | 30 | ## Angular Example: Typeahead Search Input 31 | 32 | Let's refine the Angular search component using `debounceTime`. 33 | 34 | ```typescript 35 | import { Component, OnInit, inject } from "@angular/core"; 36 | import { FormControl, ReactiveFormsModule } from "@angular/forms"; // Need ReactiveFormsModule 37 | import { HttpClient } from "@angular/common/http"; // Assuming API call 38 | import { Observable, of } from "rxjs"; 39 | import { 40 | debounceTime, // <-- The operator we're focusing on 41 | distinctUntilChanged, // Prevent duplicates 42 | switchMap, // Handle async operations, cancel previous 43 | catchError, // Handle API errors 44 | tap, // For side-effects like loading indicators 45 | } from "rxjs/operators"; 46 | import { DestroyRef } from "@angular/core"; 47 | import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; 48 | import { NgFor, NgIf, AsyncPipe } from "@angular/common"; // Need CommonModule directives/pipes 49 | 50 | @Component({ 51 | selector: "app-efficient-search", 52 | standalone: true, 53 | imports: [ReactiveFormsModule, NgFor, NgIf, AsyncPipe], 54 | template: ` 55 | 60 | 61 |
Searching...
62 | 63 |
    64 |
  • {{ result }}
  • 65 |
  • 68 | No results found. 69 |
  • 70 |
71 | 72 |
{{ errorMsg }}
73 | `, 74 | }) 75 | export class EfficientSearchComponent implements OnInit { 76 | searchControl = new FormControl(""); 77 | results$: Observable; // Observable stream for results 78 | isLoading = false; 79 | errorMsg: string | null = null; 80 | 81 | // Use inject() for dependencies 82 | private http = inject(HttpClient); 83 | private destroyRef = inject(DestroyRef); 84 | 85 | ngOnInit() { 86 | this.results$ = this.searchControl.valueChanges.pipe( 87 | // 1. DEBOUNCE: Wait for 300ms pause after last keystroke 88 | debounceTime(300), 89 | 90 | // 2. DISTINCT: Only proceed if the text is different from the last debounced value 91 | distinctUntilChanged(), 92 | 93 | // 3. TAP (Side-effect): Show loading, clear errors before making the call 94 | tap((term) => { 95 | if (term && term.length > 0) { 96 | // Only show loading for actual searches 97 | this.isLoading = true; 98 | this.errorMsg = null; 99 | } else { 100 | this.isLoading = false; // Hide loading if input is cleared 101 | } 102 | console.log(`Debounced search term: "${term}"`); 103 | }), 104 | 105 | // 4. SWITCHMAP: Make the API call, cancel previous if new term arrives 106 | switchMap((term) => { 107 | if (!term || term.length < 1) { 108 | // If input is empty or too short, return empty array immediately 109 | return of([]); // 'of([])' returns an Observable 110 | } 111 | // Replace with your actual API search function 112 | return this.searchApi(term).pipe( 113 | catchError((err) => { 114 | console.error("API Search Error:", err); 115 | this.errorMsg = "Search failed. Please try again."; 116 | return of([]); // Return empty on error 117 | }) 118 | ); 119 | }), 120 | 121 | // 5. TAP (Side-effect): Hide loading after API call completes (success or handled error) 122 | tap(() => { 123 | this.isLoading = false; 124 | }), 125 | 126 | // 6. AUTOCLEANUP: Ensure subscription is managed 127 | takeUntilDestroyed(this.destroyRef) 128 | ); 129 | } 130 | 131 | // Dummy search API function 132 | private searchApi(term: string): Observable { 133 | console.log(`--- Making API call for: "${term}" ---`); 134 | // In a real app: return this.http.get(`/api/search?q=${term}`); 135 | const mockResults = term 136 | ? [`${term} - result 1`, `${term} - result 2`] 137 | : []; 138 | return of(mockResults).pipe(delay(500)); // Simulate network delay 139 | } 140 | } 141 | // Required import for delay in dummy API 142 | import { delay } from "rxjs/operators"; 143 | ``` 144 | 145 | **In this Angular Example:** 146 | 147 | 1. `debounceTime(300)` ensures that we don't react to every keystroke. The pipeline only continues after the user has paused typing for 300ms. 148 | 2. `distinctUntilChanged()` works well after `debounceTime` to prevent searching for the exact same term multiple times if the user pauses, types something, then deletes it back to the original term before pausing again. 149 | 3. `tap()` allows us to update the `isLoading` state before (`true`) and after (`false`) the API call logic initiated by `switchMap`. 150 | 4. `switchMap()` handles the asynchronous API call. Crucially, combined with `debounceTime`, it ensures that only the request for the _latest_ stable search term is executed, and any previous pending requests for older terms are cancelled. 151 | 5. `takeUntilDestroyed` handles unsubscription automatically. 152 | 153 | Using `debounceTime` here dramatically improves user experience and reduces unnecessary load on backend services. 154 | -------------------------------------------------------------------------------- /docs/Operators/Filtering/distinctUntilChanged.md: -------------------------------------------------------------------------------- 1 | # distinctUntilChanged 2 | 3 | `distinctUntilChanged()` is a straightforward yet very useful **filtering operator**. Its purpose is to prevent consecutive duplicate values from passing through an Observable stream. 4 | 5 | It works by remembering the most recent value it emitted. When a new value arrives from the source, `distinctUntilChanged()` compares this new value with the previously emitted value: 6 | 7 | - If the new value is **different** from the previous one, it allows the new value to pass through and remembers it as the new "previous" value. 8 | - If the new value is **the same** as the previous one, it filters out (discards) the new value. 9 | 10 | By default, it uses strict equality (`===`) for comparison. You can optionally provide your own comparison function if you need custom logic (e.g., comparing specific properties of objects). The very first value emitted by the source always passes through, as there's nothing previous to compare it against. 11 | 12 | ## Key Characteristics 13 | 14 | - **Filters Consecutive Duplicates:** Only emits a value if it's different from the immediately preceding emission. 15 | - **Comparison:** Uses `===` by default; accepts an optional custom comparator function. 16 | - **Stateful:** It needs to keep track of the last emitted value. 17 | - **Passes First Value:** The first emission always gets through. 18 | - **Passes Errors/Completion:** Doesn't interfere with error or completion notifications. 19 | 20 | ## Real-World Example: Optimizing User Input Handling 21 | 22 | Imagine you're building a feature with a search input field. As the user types, you want to react to their input, perhaps by making an API call to fetch search results. 23 | 24 | Now, input events can sometimes fire frequently, even if the actual text value hasn't changed (e.g., related to focus events or specific key presses that don't alter the text). Furthermore, if you use `debounceTime` to wait for pauses in typing, the user might pause, resume typing the _same characters_, and pause again, potentially emitting the same search term multiple times consecutively after debouncing. 25 | 26 | If fetching search results is an expensive operation (network request, database query), you absolutely want to avoid making redundant requests for the exact same search term back-to-back. `distinctUntilChanged()` is the perfect tool here. By placing it in your Observable pipe _after_ you've extracted the input value (and often after `debounceTime`), you ensure that your API call logic only executes when the search term the user has settled on _actually changes_ from the previous term you searched for. 27 | 28 | ## Code Snippet 29 | 30 | ```typescript 31 | import { Component, OnInit, OnDestroy } from "@angular/core"; 32 | import { FormControl, ReactiveFormsModule } from "@angular/forms"; 33 | import { Subscription } from "rxjs"; 34 | import { map, debounceTime, distinctUntilChanged, tap } from "rxjs/operators"; 35 | import { CommonModule } from "@angular/common"; 36 | 37 | @Component({ 38 | selector: "app-distinct-search-reactive", 39 | standalone: true, 40 | imports: [CommonModule, ReactiveFormsModule], 41 | template: ` 42 |

Distinct Search Input Demo (Reactive Forms)

43 |

Time: {{ currentTime }}

44 |

45 | Uses FormControl.valueChanges. Filters out consecutive duplicate search 46 | terms after debounce. Check console. 47 |

48 | 54 |
55 |
Search triggered for:
56 |
    57 |
  • 58 | {{ term }} 59 |
  • 60 |
61 |
62 | `, 63 | }) 64 | export class DistinctSearchReactiveComponent implements OnInit, OnDestroy { 65 | searchInputControl = new FormControl(""); 66 | searchLog: string[] = []; 67 | currentTime: string = new Date().toLocaleTimeString(); 68 | private inputSubscription: Subscription | undefined; 69 | 70 | ngOnInit(): void { 71 | this.inputSubscription = this.searchInputControl.valueChanges 72 | .pipe( 73 | tap((value) => { 74 | this.currentTime = new Date().toLocaleTimeString(); 75 | console.log(`[${this.currentTime}] Raw valueChange: "${value}"`); 76 | }), 77 | debounceTime(400), 78 | map((value) => (typeof value === "string" ? value.trim() : "")), 79 | tap((value) => { 80 | this.currentTime = new Date().toLocaleTimeString(); 81 | console.log(` [${this.currentTime}] Debounced: "${value}"`); 82 | }), 83 | distinctUntilChanged(), 84 | tap((value) => { 85 | this.currentTime = new Date().toLocaleTimeString(); 86 | console.log( 87 | ` [${this.currentTime}] Distinct: "${value}" -> Triggering Search!` 88 | ); 89 | }) 90 | ) 91 | .subscribe({ 92 | next: (searchTerm) => { 93 | const termStr = searchTerm ?? ""; 94 | this.searchLog.push( 95 | `[${new Date().toLocaleTimeString()}] "${termStr}"` 96 | ); 97 | if (this.searchLog.length > 10) this.searchLog.shift(); 98 | // API call placeholder 99 | }, 100 | error: (err) => console.error("Input stream error:", err), 101 | }); 102 | } 103 | 104 | ngOnDestroy(): void { 105 | this.inputSubscription?.unsubscribe(); 106 | console.log("Search input subscription stopped."); 107 | } 108 | } 109 | ``` 110 | 111 | ## Explanation 112 | 113 | 1. **`fromEvent(..., 'input')`**: Creates a stream of input events. 114 | 2. **`map(...)`**: Extracts the text value from each event. 115 | 3. **`debounceTime(400)`**: Waits for a 400ms pause in typing before passing the latest value. This helps prevent excessive processing during rapid typing. 116 | 4. **`distinctUntilChanged()`**: This is the crucial step. It receives the debounced value. It compares this value to the _last value that it allowed through_. If the current debounced value is identical to the previous one (e.g., user paused, typed the same letter again, paused), `distinctUntilChanged` filters it out. Only if the debounced value has actually changed since the last emission will it pass through. 117 | 5. **`tap(...)` after distinctUntilChanged**: The logging here only happens for values that are truly distinct _after_ debouncing. 118 | 6. **`subscribe({...})`**: The `next` handler, which would typically trigger the expensive search operation, is only called when `distinctUntilChanged` allows a value through, thus avoiding redundant searches for the same term. 119 | 120 | ## Summary 121 | 122 | `distinctUntilChanged()` is a simple but powerful operator for optimizing streams by ensuring that downstream operations only occur when a value _actually changes_ compared to its immediate predecessor, filtering out consecutive duplicates. 123 | -------------------------------------------------------------------------------- /docs/Operators/Filtering/filter.md: -------------------------------------------------------------------------------- 1 | # filter 2 | 3 | The `filter()` operator is, as the name suggests, a **filtering operator**. It looks at each value emitted by the source Observable and applies a condition check – called a **predicate function** – to it. 4 | 5 | - If the predicate function returns `true` for a value, `filter()` allows that value to pass through to the next operator or subscriber. 6 | - If the predicate function returns `false`, `filter()` simply discards that value, and it's never seen downstream. 7 | 8 | It works very much like the `Array.prototype.filter()` method in JavaScript, but operates on values emitted asynchronously over time by an Observable. 9 | 10 | ## Key Characteristics 11 | 12 | - **Conditional Emission:** Only emits values that satisfy the condition defined in the predicate function. 13 | - **Takes a Predicate Function:** You provide a function `filter(predicateFn)` where `predicateFn` takes the source value (and optionally its index) and returns `true` or `false`. 14 | - **Doesn't Modify Values:** It doesn't change the content of the values that pass through; it only decides _if_ they pass. 15 | - **Preserves Relative Order:** The values that do pass maintain their original relative order. 16 | - **Passes Through Errors/Completion:** If the source Observable errors or completes, `filter` passes those notifications along immediately. 17 | 18 | ## Real-World Example Scenario 19 | 20 | It's Tuesday afternoon here in Bengaluru (around 3:00 PM IST). Imagine you have a stream of incoming tasks or notifications in your Angular application. Each task object might have a `priority` property ('high', 'medium', 'low'). You might have different parts of your UI or different logic handlers interested only in tasks of a certain priority. 21 | 22 | **Scenario:** Let's say you want to display an urgent notification counter that only increments when a task with `'high'` priority arrives. You can use `filter()` to create a new stream containing only those high-priority tasks. 23 | 24 | ## Code Snippet (Angular Component - Filtering High-Priority Tasks) 25 | 26 | ```typescript 27 | import { Component, OnInit, OnDestroy } from "@angular/core"; 28 | import { Subject, Subscription } from "rxjs"; 29 | import { filter, tap } from "rxjs/operators"; // Import filter 30 | 31 | interface Task { 32 | id: number; 33 | description: string; 34 | priority: "high" | "medium" | "low"; 35 | } 36 | 37 | @Component({ 38 | selector: "app-task-filter-demo", 39 | template: ` 40 |

Task Filtering Demo

41 |

Simulating incoming tasks. Check console log and high priority list.

42 | 45 | 46 |
47 |
High Priority Tasks Only (Count: {{ highPriorityTaskCount }})
48 |
    49 |
  • 53 | ID: {{ task.id }} - {{ task.description }} 54 |
  • 55 |
56 |
57 |
58 |
All Tasks Log:
59 |
    60 |
  • 61 | ID: {{ task.id }} - {{ task.description }} (Priority: 62 | {{ task.priority }}) 63 |
  • 64 |
65 |
66 | `, 67 | }) 68 | export class TaskFilterDemoComponent implements OnInit, OnDestroy { 69 | highPriorityTaskCount = 0; 70 | highPriorityTasks: Task[] = []; 71 | allTasksLog: Task[] = []; 72 | 73 | // Use a Subject to simulate a stream of incoming tasks 74 | private taskSubject = new Subject(); 75 | private taskSubscription: Subscription | undefined; 76 | private taskIdCounter = 0; 77 | 78 | ngOnInit(): void { 79 | // Subscribe to the task stream 80 | this.taskSubscription = this.taskSubject 81 | .pipe( 82 | tap((task) => { 83 | // Log every task that comes in *before* filtering 84 | console.log( 85 | `[${new Date().toLocaleTimeString()}] Received Task: ID=${ 86 | task.id 87 | }, Prio=${task.priority}` 88 | ); 89 | this.allTasksLog.push(task); 90 | if (this.allTasksLog.length > 10) this.allTasksLog.shift(); // Keep log short 91 | }), 92 | // Apply the filter operator 93 | filter((task: Task) => { 94 | // This is the predicate function. 95 | // It returns true only if the task's priority is 'high'. 96 | const shouldPass = task.priority === "high"; 97 | console.log( 98 | ` Filtering Task ID ${task.id} (Prio: ${task.priority}). Should pass? ${shouldPass}` 99 | ); 100 | return shouldPass; 101 | }), 102 | // The rest of the pipe only sees tasks that passed the filter 103 | tap((highPrioTask) => { 104 | console.log(` -> Task ID ${highPrioTask.id} passed the filter!`); 105 | }) 106 | ) 107 | .subscribe({ 108 | next: (highPriorityTask: Task) => { 109 | // This 'next' handler only receives tasks where priority === 'high' 110 | this.highPriorityTaskCount++; 111 | this.highPriorityTasks.push(highPriorityTask); 112 | if (this.highPriorityTasks.length > 5) this.highPriorityTasks.shift(); // Keep list short 113 | }, 114 | error: (err) => console.error("Task stream error:", err), 115 | // complete: () => console.log('Task stream completed') // Only if subject completes 116 | }); 117 | } 118 | 119 | simulateIncomingTask(): void { 120 | this.taskIdCounter++; 121 | const priorities: Array<"high" | "medium" | "low"> = [ 122 | "low", 123 | "medium", 124 | "high", 125 | ]; 126 | const randomPriority = 127 | priorities[Math.floor(Math.random() * priorities.length)]; 128 | 129 | const newTask: Task = { 130 | id: this.taskIdCounter, 131 | description: `Simulated task number ${this.taskIdCounter}`, 132 | priority: randomPriority, 133 | }; 134 | console.log( 135 | `------------------\nSimulating: Pushing task ${newTask.id} with priority ${newTask.priority}` 136 | ); 137 | this.taskSubject.next(newTask); // Push the new task onto the stream 138 | } 139 | 140 | ngOnDestroy(): void { 141 | if (this.taskSubscription) { 142 | this.taskSubscription.unsubscribe(); 143 | } 144 | this.taskSubject.complete(); 145 | } 146 | } 147 | ``` 148 | 149 | **Explanation:** 150 | 151 | 1. **`Subject`**: We use a Subject to mimic an Observable stream where `Task` objects arrive over time (triggered by the button click). 152 | 2. **`tap(...)` (before filter)**: We use `tap` to log every task that enters the pipe, _before_ it hits the filter, so we can see everything that arrives. 153 | 3. **`filter((task: Task) => task.priority === 'high')`**: This is the core. 154 | - The `filter` operator receives each `Task` object emitted by the `taskSubject`. 155 | - The predicate function `(task: Task) => task.priority === 'high'` checks if the `priority` property of the task is strictly equal to `'high'`. 156 | - If it is `true`, the `task` object is passed further down the pipe. 157 | - If it is `false` (i.e., priority is 'medium' or 'low'), the `task` object is discarded by `filter`. 158 | 4. **`tap(...)` (after filter)**: We log again here to clearly see which tasks made it _through_ the filter. 159 | 5. **`subscribe({ next: ... })`**: The `next` handler will _only_ be executed for tasks that passed the filter (those with 'high' priority). We update the count and the list based on these filtered tasks. 160 | 161 | ## Summary 162 | 163 | `filter()` acts as a gatekeeper for your Observable streams, allowing only the data that meets your specific criteria to proceed, making it essential for selecting relevant information from potentially noisy streams. 164 | -------------------------------------------------------------------------------- /docs/Operators/Filtering/find.md: -------------------------------------------------------------------------------- 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 { 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 |
105 |

Find Operator Example

106 |

Searching for the first admin user in the stream...

107 | 108 | @if (foundAdmin()) { 109 |
110 | First Admin Found: 111 |
{{ foundAdmin() | json }}
112 |
113 | } @else if (searchComplete()) { 114 |

115 | No admin user found before the stream completed. 116 |

117 | } @else { 118 |

Searching...

119 | } 120 |
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(undefined); // Result can be User or undefined 131 | searchComplete = signal(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. 196 | -------------------------------------------------------------------------------- /docs/Operators/Filtering/first.md: -------------------------------------------------------------------------------- 1 | # first 2 | 3 | The `first()` operator is used to get **only the first value** emitted by a source Observable that meets an optional condition. After emitting that single value, it immediately **completes** the stream. 4 | 5 | It can be used in a few ways: 6 | 7 | 1. **`first()` (no arguments):** Emits the very first value from the source, then completes. If the source completes without emitting _any_ values, `first()` will emit an `EmptyError`. 8 | 2. **`first(predicateFn)`:** Emits the first value from the source for which the provided `predicateFn` function returns `true`, then completes. If the source completes before any value satisfies the predicate, `first()` will emit an `EmptyError`. 9 | 3. **`first(predicateFn, defaultValue)`:** Same as above, but if the source completes before any value satisfies the predicate, it emits the provided `defaultValue` instead of erroring, and then completes. 10 | 11 | ## Key Characteristics 12 | 13 | - **Selects First Value:** Emits only one value – the first that qualifies based on the arguments. 14 | - **Completes Stream:** Immediately completes after emitting the single value (or default value/error). 15 | - **Unsubscribes from Source:** Cleans up the source subscription upon completion or error. 16 | - **Potential Error:** Can throw `EmptyError` if no suitable value is found before the source completes (unless a `defaultValue` is supplied). 17 | 18 | ## Real-World Example Scenario 19 | 20 | Imagine your Angular application needs to load some initial configuration data when it starts. This data might be available via an Observable (perhaps from a service using `ReplaySubject(1)` or `BehaviorSubject`). You only care about getting that _first available configuration value_ to initialize your component, even if the source Observable might emit updates later. `first()` is ideal for grabbing that initial emission and then completing the stream cleanly. 21 | 22 | **Scenario:** Let's simulate a stream emitting user status updates ('pending', 'active', 'inactive'). We only want to know the _first_ time the user becomes 'active'. 23 | 24 | ## Code Snippet 25 | 26 | ```typescript 27 | import { Component, OnInit } from "@angular/core"; 28 | import { 29 | of, 30 | first, 31 | catchError, 32 | EMPTY, 33 | throwError, 34 | timer, 35 | map, 36 | concat, 37 | } from "rxjs"; 38 | import { EmptyError } from "rxjs"; 39 | 40 | @Component({ 41 | selector: "app-first-demo", 42 | standalone: true, 43 | imports: [], 44 | template: ` 45 |

First Operator Demo

46 |

Looking for the first 'active' status. Check console.

47 |

Result: {{ resultStatus }}

48 | `, 49 | }) 50 | export class FirstDemoComponent implements OnInit { 51 | resultStatus = "Waiting..."; 52 | 53 | ngOnInit(): void { 54 | const statusUpdates$ = concat( 55 | timer(500).pipe(map(() => "pending")), 56 | timer(1000).pipe(map(() => "pending")), 57 | timer(1500).pipe(map(() => "active")), // First 'active' here 58 | timer(2000).pipe(map(() => "pending")), 59 | timer(2500).pipe(map(() => "inactive")) 60 | ); 61 | 62 | console.log( 63 | `[${new Date().toLocaleTimeString()}] Subscribing to find first 'active' status...` 64 | ); 65 | 66 | statusUpdates$ 67 | .pipe( 68 | first((status) => status === "active"), 69 | catchError((error) => { 70 | if (error instanceof EmptyError) { 71 | console.warn( 72 | `[${new Date().toLocaleTimeString()}] No 'active' status found before stream completed.` 73 | ); 74 | this.resultStatus = "No active status found."; 75 | return EMPTY; 76 | } else { 77 | console.error( 78 | `[${new Date().toLocaleTimeString()}] Stream error:`, 79 | error 80 | ); 81 | this.resultStatus = `Error: ${error.message}`; 82 | return throwError(() => error); 83 | } 84 | }) 85 | ) 86 | .subscribe({ 87 | next: (activeStatus) => { 88 | console.log( 89 | `[${new Date().toLocaleTimeString()}] Found first active status: ${activeStatus}` 90 | ); 91 | this.resultStatus = `First active status: ${activeStatus}`; 92 | }, 93 | complete: () => { 94 | console.log( 95 | `[${new Date().toLocaleTimeString()}] Stream completed by first().` 96 | ); 97 | // Note: resultStatus might already be set by next or catchError 98 | if (this.resultStatus === "Waiting...") { 99 | this.resultStatus = 100 | "Stream completed (likely handled by catchError/default)"; 101 | } 102 | }, 103 | }); 104 | } 105 | } 106 | ``` 107 | 108 | **Explanation:** 109 | 110 | 1. **`statusUpdates$`**: We simulate a stream emitting different status strings over time using `concat` and `timer`. 111 | 2. **`first(status => status === 'active')`**: This operator listens to `statusUpdates$`. 112 | - It ignores 'pending'. 113 | - When 'active' arrives, `first()` emits 'active'. 114 | - Immediately after emitting 'active', it sends the `complete` signal and unsubscribes from `statusUpdates$`. The subsequent 'pending' and 'inactive' emissions are never processed by this subscription. 115 | 3. **`catchError(...)`**: This handles the `EmptyError` that `first()` would throw if the `statusUpdates$` completed _before_ emitting 'active'. In this example, 'active' is found, so this specific error path isn't taken. 116 | 4. **`subscribe({...})`**: 117 | - The `next` handler receives the single value 'active'. 118 | - The `complete` handler is called right after `next`, confirming the stream finished. 119 | 120 | ## Summary 121 | 122 | `first()` is used when you need exactly one value from the beginning of a stream (optionally matching a condition) and want the stream to complete immediately afterward. It's concise for getting initial values or the first occurrence of a specific event. Remember its potential to throw `EmptyError` if no qualifying value is emitted before the source completes. 123 | -------------------------------------------------------------------------------- /docs/Operators/Filtering/last.md: -------------------------------------------------------------------------------- 1 | # last 2 | 3 | The `last()` operator is used to get **only the very last value** emitted by a source Observable that satisfies an optional condition, but only _after_ the source Observable **completes**. 4 | 5 | It operates similarly to `first()` but focuses on the end of the stream: 6 | 7 | 1. **`last()` (no arguments):** Waits for the source Observable to complete. Once completed, it emits the single, last value that the source emitted. If the source completes without emitting _any_ values, `last()` will emit an `EmptyError`. 8 | 2. **`last(predicateFn)`:** Waits for the source Observable to complete. Once completed, it looks at all the values the source emitted and finds the last one for which the `predicateFn` returned `true`. It emits that single value. If no value satisfied the predicate before completion, `last()` emits an `EmptyError`. 9 | 3. **`last(predicateFn, defaultValue)`:** Same as above, but if no value satisfied the predicate before completion, it emits the `defaultValue` instead of erroring. 10 | 11 | ## Key Characteristics 12 | 13 | - **Waits for Completion:** Critically depends on the source Observable sending a `complete` notification before it can emit a value. It won't work on streams that never complete (like a raw `interval`). 14 | - **Selects Last Value:** Emits only one value – the last that qualifies based on the arguments, determined _after_ the source finishes. 15 | - **Completes Stream:** Completes itself immediately after emitting the single value (or default value/error). 16 | - **Potential Error:** Can throw `EmptyError` if the source completes without emitting suitable values (unless a `defaultValue` is supplied). 17 | 18 | ## Real-World Example Scenario 19 | 20 | Imagine you have an Observable representing a sequence of operations that must finish, like processing steps in a batch job. The stream might emit intermediate status updates, but you only care about the **final result or status message** that is emitted just before the entire process completes. 21 | 22 | **Scenario:** Let's simulate a stream emitting scores achieved during different phases of a game round. We only want to get the _final_ score achieved at the end of the round. 23 | 24 | ## Code Snippet 25 | 26 | ```typescript 27 | import { Component, OnInit } from "@angular/core"; 28 | import { of, last, catchError, EMPTY, throwError } from "rxjs"; 29 | import { EmptyError } from "rxjs"; 30 | 31 | @Component({ 32 | selector: "app-last-demo", 33 | standalone: true, 34 | imports: [], 35 | template: ` 36 |

Last Operator Demo

37 |

Getting the final score from a completed round. Check console.

38 |

Result: {{ finalScoreStatus }}

39 | `, 40 | }) 41 | export class LastDemoComponent implements OnInit { 42 | finalScoreStatus = "Waiting for round to complete..."; 43 | 44 | ngOnInit(): void { 45 | const roundScores$ = of(10, 50, 20, 100); // Finite, completes after 100 46 | 47 | console.log( 48 | `[${new Date().toLocaleTimeString()}] Subscribing to get final score...` 49 | ); 50 | 51 | roundScores$ 52 | .pipe( 53 | last(), // No predicate, just get the very last value 54 | catchError((error) => { 55 | if (error instanceof EmptyError) { 56 | console.warn( 57 | `[${new Date().toLocaleTimeString()}] Source completed without emitting values.` 58 | ); 59 | this.finalScoreStatus = "Round completed with no scores."; 60 | return EMPTY; 61 | } else { 62 | console.error( 63 | `[${new Date().toLocaleTimeString()}] Stream error:`, 64 | error 65 | ); 66 | this.finalScoreStatus = `Error: ${error.message}`; 67 | return throwError(() => error); 68 | } 69 | }) 70 | ) 71 | .subscribe({ 72 | next: (finalScore) => { 73 | console.log( 74 | `[${new Date().toLocaleTimeString()}] Final score received: ${finalScore}` 75 | ); 76 | this.finalScoreStatus = `Final Score: ${finalScore}`; 77 | }, 78 | complete: () => { 79 | console.log( 80 | `[${new Date().toLocaleTimeString()}] Stream completed by last().` 81 | ); 82 | }, 83 | }); 84 | } 85 | } 86 | ``` 87 | 88 | **Explanation:** 89 | 90 | 1. **`roundScores$ = of(10, 50, 20, 100)`**: We create a finite Observable using `of()`. This stream emits 10, 50, 20, 100 and then immediately completes. 91 | 2. **`last()`**: This operator subscribes to `roundScores$`. It internally keeps track of the most recently emitted value. Because `roundScores$` completes right after emitting 100, `last()` knows the stream is finished. 92 | 3. **Emission**: Once `roundScores$` completes, `last()` emits the very last value it saw, which is `100`. 93 | 4. **`catchError(...)`**: Handles the `EmptyError` case if the source (`of()`) had been empty (e.g., `of()`). 94 | 5. **`subscribe({...})`**: 95 | - The `next` handler receives the single value `100`. 96 | - The `complete` handler is called right after `next`, confirming the stream finished. 97 | 98 | ## Summary 99 | 100 | `last()` is used when you need the final value emitted by a **completing** Observable stream (optionally matching a condition). It inherently requires waiting for the source to finish before it can determine and emit the last value. Remember it won't work if the source stream never completes. 101 | -------------------------------------------------------------------------------- /docs/Operators/Filtering/skip.md: -------------------------------------------------------------------------------- 1 | The `skip()` operator is quite straightforward: it tells an Observable stream to simply **ignore the first `N` values** it emits. After skipping the specified number of items, it will then allow all subsequent emissions to pass through normally. 2 | 3 | Think of it as telling someone to start counting _after_ a certain number. If you say `skip(3)`, you're essentially saying "Ignore the 1st, 2nd, and 3rd things that happen, but tell me about the 4th, 5th, 6th, and so on." 4 | 5 | ## Key Characteristics 6 | 7 | 1. **Counts and Ignores:** It keeps an internal count of how many items have been emitted by the source. 8 | 2. **Skips the Start:** It prevents the first `N` emissions from reaching the subscriber or subsequent operators in the pipe. 9 | 3. **Emits After Skipping:** Once `N` items have been skipped, all following items are emitted without further modification by `skip()`. 10 | 4. **Argument:** It takes one argument: `count` (the number of emissions to skip). 11 | 12 | ## Real-World Analogy 13 | 14 | Imagine you are subscribing to a news feed that sends updates every hour. However, you know that the first 2 updates of the day are always just routine system checks or old news summaries that you don't care about. 15 | 16 | You can use `skip(2)` on this news feed stream. This way, you won't be bothered by the first two updates each day. You'll only start receiving notifications from the 3rd update onwards, which contains the actual news you're interested in. 17 | 18 | ## Angular Example: Ignoring Initial Value from `BehaviorSubject 19 | 20 | A common use case in Angular involves `BehaviorSubject` or `ReplaySubject(1)`. These types of Subjects store the "current" value and emit it immediately to any new subscriber. Sometimes, you only want to react to _future changes_ pushed to the Subject, not the value it happens to hold at the exact moment you subscribe. 21 | 22 | Let's say you have a service managing user authentication status: 23 | 24 | ```typescript 25 | import { Injectable } from "@angular/core"; 26 | import { BehaviorSubject } from "rxjs"; 27 | 28 | @Injectable({ providedIn: "root" }) 29 | export class AuthService { 30 | // Initially, user is logged out. Emits `false` immediately to new subscribers. 31 | private loggedInStatus = new BehaviorSubject(false); 32 | isLoggedIn$ = this.loggedInStatus.asObservable(); 33 | 34 | login() { 35 | // Simulate successful login 36 | console.log("AuthService: User logged in."); 37 | this.loggedInStatus.next(true); 38 | } 39 | 40 | logout() { 41 | console.log("AuthService: User logged out."); 42 | this.loggedInStatus.next(false); 43 | } 44 | } 45 | 46 | // --- In a Component --- 47 | import { Component, DestroyRef, inject, signal } from "@angular/core"; 48 | import { NgIf } from "@angular/common"; // Import NgIf for the template 49 | import { takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop"; // Core interop functions 50 | import { filter, skip } from "rxjs/operators"; 51 | import { AuthService } from "./auth.service"; // Assuming AuthService exists as defined previously 52 | 53 | @Component({ 54 | selector: "app-login-watcher", 55 | standalone: true, 56 | imports: [NgIf], 57 | template: ` 58 |
59 | User is currently: {{ isLoggedIn() ? "Logged In" : "Logged Out" }} 60 |
61 | 62 |
{{ loginMessage() }}
63 | 64 | 65 | 66 | `, 67 | }) 68 | export class LoginWatcherComponent { 69 | protected authService = inject(AuthService); 70 | private destroyRef = inject(DestroyRef); 71 | 72 | isLoggedIn = toSignal(this.authService.isLoggedIn$, { initialValue: false }); 73 | 74 | // Use a signal to hold the dynamic login message 75 | loginMessage = signal(""); // Initialize with an empty string 76 | 77 | constructor() { 78 | this.setupLoginSubscription(); 79 | } 80 | 81 | private setupLoginSubscription(): void { 82 | this.authService.isLoggedIn$ 83 | .pipe( 84 | skip(1), 85 | // Optional: Filter for only 'true' values if you only care about login events 86 | filter((isLoggedIn) => isLoggedIn === true), 87 | Automatically unsubscribe when the component is destroyed 88 | takeUntilDestroyed(this.destroyRef) 89 | ) 90 | .subscribe(() => { 91 | console.log("LoginWatcherComponent: Detected LOGIN event!"); 92 | // Use the signal's .set() method to update the state 93 | this.loginMessage.set( 94 | `Welcome back! Login detected at ${new Date().toLocaleTimeString()}` 95 | ); 96 | 97 | // Clear the message after a few seconds using standard setTimeout 98 | setTimeout(() => { 99 | // Use .set() again to clear the signal's value 100 | this.loginMessage.set(""); 101 | }, 5000); 102 | }); 103 | 104 | console.log( 105 | "LoginWatcherComponent initialized. Waiting for login events..." 106 | ); 107 | } 108 | } 109 | ``` 110 | 111 | **Explanation of the Angular Example:** 112 | 113 | 1. `AuthService` uses a `BehaviorSubject` (`loggedInStatus`) initialized to `false`. 114 | 2. `isLoggedIn` is created using toSignal, which converts the `isLoggedIn$` Observable into a signal. This is useful for displaying the current state reactively in the template (isLoggedIn()). `toSignal` requires an `initialValue`. 115 | 3. The `skip(1)` operator intercepts the initial `false` emission and discards it. 116 | 4. The `subscribe` block does _not_ run initially. 117 | 5. Later, if the user clicks the "Log In" button, `authService.login()` calls `this.loggedInStatus.next(true)`. 118 | 6. This new value `true` is emitted by the `BehaviorSubject`. 119 | 7. `skip(1)` has already done its job (skipped one item), so it lets `true` pass through. 120 | 8. The optional `filter(isLoggedIn => isLoggedIn === true)` also lets `true` pass. 121 | 9. The `subscribe` block now executes, logging the message and updating the component's `loginMessage` property, because a _new_ value was emitted _after_ the initial skipped one. 122 | 123 | ## Summary 124 | 125 | `skip(N)` is useful when you need to disregard a known number of initial emissions from an Observable stream, allowing you to focus on the values that come after that initial phase. 126 | -------------------------------------------------------------------------------- /docs/Operators/Filtering/take.md: -------------------------------------------------------------------------------- 1 | # take 2 | 3 | `take()` is an RxJS operator that allows you to limit the number of values emitted by a source Observable. You specify a number, `N`, and `take(N)` will: 4 | 5 | 1. Emit the first `N` values that come from the source Observable. 6 | 2. As soon as the Nth value is emitted, it immediately sends a **`complete`** notification. 7 | 3. It automatically **unsubscribes** from the source Observable. 8 | 9 | Think of it as telling the Observable, "Just give me the first N things you have, and then you can stop." It's useful for dealing with streams that might emit many or even infinite values when you only need a limited number from the beginning. 10 | 11 | **Key Characteristics:** 12 | 13 | - **Limits Emissions:** Only allows the first `N` values through. 14 | - **Completes the Stream:** Automatically sends a `complete` notification after the Nth value. 15 | - **Unsubscribes from Source:** Prevents further processing or potential memory leaks from the source after completion. 16 | - **Filtering/Completion:** Acts as both a way to filter by count and a way to ensure completion. 17 | 18 | ## Real-World Example Scenario 19 | 20 | It's Thursday morning here in Bengaluru (around 8:30 AM IST). Imagine you have a feature where you want to allow the user to perform an action, but only permit them to do it a limited number of times within a certain context, perhaps the first 3 times they click a specific "Try It" button during a tutorial phase. 21 | 22 | **Scenario:** You have a button. You want to react to the user clicking it, but only respond to the **first 3 clicks**. After the third click, you want to ignore any subsequent clicks on that button for that specific stream instance. `take(3)` is perfect for this. 23 | 24 | ## Code Snippet 25 | 26 | ```typescript 27 | import { 28 | Component, 29 | OnInit, 30 | OnDestroy, 31 | ViewChild, 32 | ElementRef, 33 | inject, 34 | } from "@angular/core"; 35 | import { fromEvent, Subscription, take, tap } from "rxjs"; 36 | 37 | @Component({ 38 | selector: "app-take-demo", 39 | standalone: true, 40 | imports: [], 41 | template: ` 42 |

Take Operator Demo

43 |

Reacting only to the first 3 clicks. Check the console.

44 | 47 |
    48 |
  • 52 | {{ log }} 53 |
  • 54 |
55 |

{{ completionStatus }}

56 | `, 57 | }) 58 | export class TakeDemoComponent implements OnInit, OnDestroy { 59 | @ViewChild("actionButton") actionButton: 60 | | ElementRef 61 | | undefined; 62 | 63 | private clickSubscription: Subscription | undefined; 64 | clickLog: string[] = []; 65 | completionStatus = "Stream active..."; 66 | 67 | ngOnInit(): void { 68 | if (!this.actionButton) { 69 | return; 70 | } 71 | 72 | const buttonClicks$ = fromEvent(this.actionButton.nativeElement, "click"); 73 | 74 | this.clickSubscription = buttonClicks$ 75 | .pipe( 76 | tap((event) => { 77 | console.log( 78 | `[${new Date().toLocaleTimeString()}] Button Clicked (Event before take)` 79 | ); 80 | }), 81 | take(3), 82 | tap((event) => { 83 | console.log( 84 | ` [${new Date().toLocaleTimeString()}] Click passed through take()` 85 | ); 86 | }) 87 | ) 88 | .subscribe({ 89 | next: (event: Event) => { 90 | const message = `[${new Date().toLocaleTimeString()}] Processed Click #${ 91 | this.clickLog.length + 1 92 | }`; 93 | console.log(` -> ${message}`); 94 | this.clickLog.push(message); 95 | }, 96 | error: (err) => { 97 | console.error("Stream error:", err); 98 | this.completionStatus = `Stream Error: ${err}`; 99 | }, 100 | complete: () => { 101 | const message = `[${new Date().toLocaleTimeString()}] Stream Completed by take(3) after 3 emissions.`; 102 | console.log(message); 103 | this.completionStatus = message; 104 | if (this.actionButton) { 105 | // Optional: Disable button after completion 106 | // this.actionButton.nativeElement.disabled = true; 107 | } 108 | }, 109 | }); 110 | } 111 | 112 | ngOnDestroy(): void { 113 | if (this.clickSubscription) { 114 | this.clickSubscription.unsubscribe(); 115 | console.log("Take demo subscription stopped on component destroy."); 116 | } 117 | } 118 | } 119 | ``` 120 | 121 | **Explanation:** 122 | 123 | 1. **`fromEvent(...)`**: Creates an Observable that emits an event every time the button is clicked. This stream could potentially go on forever. 124 | 2. **`tap(...)` (before take)**: Logs every single click event that `fromEvent` emits, just to show they are happening. 125 | 3. **`take(3)`**: This operator is applied. It will allow the first click event to pass through. It will allow the second click event to pass through. It will allow the third click event to pass through. 126 | 4. **After the third click event passes through**: `take(3)` immediately sends the `complete` notification down the stream and unsubscribes from the `buttonClicks$` source. 127 | 5. **`tap(...)` (after take)**: Logs only those clicks that were allowed through by `take(3)`. 128 | 6. **`subscribe({...})`**: 129 | - The `next` handler executes only for the first 3 clicks. 130 | - The `complete` handler executes immediately after the 3rd click is processed. The log message confirms this. 131 | - Any clicks on the button _after_ the third one will not trigger any logging from the taps or the `next` handler because the subscription managed by `take(3)` is already finished and unsubscribed from the source. 132 | 133 | ## Summary 134 | 135 | `take(N)` is a convenient way to limit the number of emissions you care about from an Observable and automatically ensure the stream completes and cleans up after itself once that limit is reached. It's very useful for handling "first N" scenarios or for putting a definite end on potentially infinite streams like `interval` or UI events. 136 | -------------------------------------------------------------------------------- /docs/Operators/Filtering/takeUntil.md: -------------------------------------------------------------------------------- 1 | # takeUntil 2 | 3 | `takeUntil()` is an RxJS operator primarily used for managing the lifetime of an Observable stream, effectively acting as a **completion operator**. It mirrors the source Observable, allowing its values to pass through, **until** a second Observable, called the `notifier`, emits its first value or completes. 4 | 5 | As soon as the `notifier` Observable emits _any_ value or completes, `takeUntil()` immediately: 6 | 7 | 1. Sends a `complete` notification for the stream it's operating on. 8 | 2. Unsubscribes from both the source Observable and the `notifier` Observable. 9 | 10 | The actual value emitted by the `notifier` doesn't matter; `takeUntil` only cares about the _event_ of an emission (or completion) from the `notifier`. 11 | 12 | ## Key Characteristics 13 | 14 | - **Conditional Completion:** Completes the main stream based on an external signal (the `notifier`). 15 | - **Takes a Notifier Observable:** You provide the Observable that signals when to stop: `takeUntil(notifier$)`. 16 | - **Passes Source Values:** Emits values from the source until the notification occurs. 17 | - **Automatic Unsubscription:** Handles cleanup by unsubscribing from both streams upon completion. 18 | 19 | ## Real-World Example Scenario 20 | 21 | The most common and idiomatic use case for `takeUntil()` in Angular is to automatically unsubscribe from Observables when a component is destroyed. This prevents memory leaks, which can occur if subscriptions remain active after a component is removed from the DOM. 22 | 23 | **Scenario:** You have an Angular component that needs to perform a periodic action, perhaps updating a timer displayed on the screen every second using `interval(1000)`. This interval would run forever if not stopped. You need to ensure that when the user navigates away from the component (and it gets destroyed), the interval subscription is automatically cleaned up. `takeUntil()` combined with a `Subject` triggered in `ngOnDestroy` is the standard pattern for this. 24 | 25 | ## Code Snippet 26 | 27 | ```typescript 28 | import { Component, OnInit, OnDestroy } from "@angular/core"; 29 | import { Subject, interval, takeUntil, tap } from "rxjs"; 30 | 31 | @Component({ 32 | selector: "app-take-until-demo", 33 | standalone: true, 34 | imports: [], 35 | template: ` 36 |

TakeUntil Demo

37 |

Timer running (check console). It stops when component is destroyed.

38 |

Current count: {{ currentCount }}

39 | `, 40 | }) 41 | export class TakeUntilDemoComponent implements OnInit, OnDestroy { 42 | currentCount = 0; 43 | private destroy$ = new Subject(); 44 | 45 | ngOnInit(): void { 46 | console.log( 47 | `[${new Date().toLocaleTimeString()}] Component Init - Starting Interval` 48 | ); 49 | interval(1000) 50 | .pipe( 51 | tap((count) => 52 | console.log( 53 | `[${new Date().toLocaleTimeString()}] Interval emitted: ${count}` 54 | ) 55 | ), 56 | takeUntil(this.destroy$) 57 | ) 58 | .subscribe({ 59 | next: (count) => (this.currentCount = count), 60 | complete: () => 61 | console.log( 62 | `[${new Date().toLocaleTimeString()}] Interval stream completed via takeUntil.` 63 | ), 64 | }); 65 | } 66 | 67 | ngOnDestroy(): void { 68 | console.log( 69 | `[${new Date().toLocaleTimeString()}] Component Destroy - Signaling takeUntil` 70 | ); 71 | this.destroy$.next(); 72 | this.destroy$.complete(); 73 | } 74 | } 75 | ``` 76 | 77 | **Explanation:** 78 | 79 | 1. **`destroy$ = new Subject()`**: A private `Subject` is created. This will act as our `notifier`. 80 | 2. **`interval(1000).pipe(...)`**: We create an Observable that emits numbers every second. 81 | 3. **`takeUntil(this.destroy$)`**: This is the key. The `interval` stream will continue emitting values _until_ the `this.destroy$` Subject emits a value. 82 | 4. **`subscribe({...})`**: We subscribe to process the values from the interval. 83 | 5. **`ngOnDestroy()`**: This Angular lifecycle hook is guaranteed to run when the component is about to be destroyed. 84 | - **`this.destroy$.next()`**: We emit a dummy value (`void`) from our `destroy$` Subject. 85 | - **`this.destroy$.complete()`**: It's good practice to also complete the Subject. 86 | 6. **Behavior**: As soon as `this.destroy$.next()` is called in `ngOnDestroy`, the `takeUntil(this.destroy$)` operator detects this emission. It immediately completes the `interval` stream (triggering the `complete` handler in the subscription) and unsubscribes from `interval`. No more values will be processed, and the interval timer stops, preventing a memory leak. 87 | 88 | ## Summary 89 | 90 | `takeUntil()` provides a clean, declarative way to complete an Observable stream based on a signal from another Observable, making it the standard and recommended pattern for managing subscription lifetimes tied to Angular component lifecycles. 91 | -------------------------------------------------------------------------------- /docs/Operators/RxJS-operators.md: -------------------------------------------------------------------------------- 1 | RxJS operators are functions that enable you to manipulate, combine, filter, and transform the data streams (Observables) in powerful ways. They take an Observable as input and return a new Observable. 2 | 3 | Think of operators as tools in a workshop for working with your asynchronous data streams. Instead of listing every single operator (there are many!), it's helpful to understand the general **categories** they fall into, based on what they _do_: 4 | 5 | ## Creation Operators 6 | 7 | - **Purpose:** To create new Observables from scratch or from existing data sources. 8 | - **Examples:** 9 | 10 | - [`of`](../Operators/Creation/of.md): Creates an Observable that emits the provided values sequentially and then completes. 11 | - [`from`](../Operators/Creation/from.md): Converts arrays, promises, iterables, or strings into Observables. 12 | - `fromEvent`: Creates an Observable from DOM events. (Hot Observable) 13 | - [`interval`](../Operators/Creation/interval.md): Emits sequential numbers every specified interval (in milliseconds). 14 | - [`timer`](../Operators/Creation/timer.md): Emits one value after an initial delay, then optionally emits subsequent values at a regular interval. 15 | - `throwError(() => new Error('Oops!'))`: Creates an Observable that immediately emits an error. 16 | - [`EMPTY`](../Operators//Creation/empty-never.md): Creates an Observable that emits no items and immediately completes. 17 | - [`NEVER`](../Operators//Creation/empty-never.md): Creates an Observable that never emits any items and never completes. 18 | 19 | ## Transformation Operators 20 | 21 | - **Purpose:** To change the format, type, or value of items emitted by an Observable. 22 | - **Examples:** 23 | 24 | - [`map`](../Operators/Transformation/map.md): Applies a function to each emitted value. 25 | - `pluck('propertyName')`: Selects a nested property from each emitted object. 26 | - `scan((acc, value) => acc + value, 0)`: Accumulates values over time, like `Array.reduce`. 27 | - [`mergeMap`](../Operators/Transformation/mergeMap.md): Projects each source value to an Observable and merges their emissions into a single stream. Good for handling multiple inner observables concurrently. 28 | - [`switchMap`](../Operators/Transformation/switchMap.md): Projects each source value to an Observable, but cancels the previous inner Observable when a new source value arrives. Ideal for scenarios like type-ahead searches where you only care about the latest request. 29 | - [`concatMap`](../Operators/Transformation/concatMap.md): Projects each source value to an Observable, but waits for the previous inner Observable to complete before subscribing to the next one. Ensures order. 30 | - `bufferTime(1000)`: Collects emitted values into arrays over a specified time period. 31 | - `groupBy(item => item.category)`: Groups items emitted by the source Observable based on a key. 32 | 33 | ## Filtering Operators 34 | 35 | - **Purpose:** To selectively emit values from a source Observable based on certain criteria. 36 | - **Examples:** 37 | 38 | - [`filter`](../Operators/Filtering/filter.md): Emits only the values that satisfy a condition. 39 | - [`first`](../Operators/Filtering/first.md): Emits only the first value (or the first value satisfying a condition) and then completes. 40 | - [`last`](../Operators/Filtering/last.md): Emits only the last value (or the last value satisfying a condition) when the source completes. 41 | - [`take`](../Operators/Filtering/take.md): Emits the first N values and then completes. 42 | - [`takeUntil`](../Operators/Filtering/takeUntil.md): Emits values until a second `notifier$` Observable emits. Very useful for unsubscribing/completing streams (e.g., when a component is destroyed). 43 | - [`skip`](../Operators/Filtering/skip.md): Skips the first N values. 44 | - [`debounceTime`](../Operators/Filtering/debounceTime.md): Emits a value only after a specified time has passed without another source emission. Useful for rate-limiting input events (like search inputs). 45 | - [`distinctUntilChanged`](../Operators/Filtering/distinctUntilChanged.md): Emits only when the current value is different from the previous one. 46 | 47 | ## Combination Operators 48 | 49 | - **Purpose:** To combine multiple source Observables into a single Observable. 50 | - **Examples:** 51 | 52 | - [`combineLatest`](../Operators/Combination/combineLatest.md): When _any_ source Observable emits, it combines the _latest_ values from _all_ sources and emits the combined result (usually as an array). Requires all sources to have emitted at least once. 53 | - [`zip`](../Operators/Combination/zip.md): Combines values from source Observables pairwise. Waits for each source to emit a value at the corresponding index before emitting the combined pair. 54 | - [`forkJoin`](../Operators/Combination/forkJoin.md): Waits for _all_ source Observables to _complete_ and then emits an array containing the _last_ value emitted by each source. Good for running parallel asynchronous operations and getting all results at the end. 55 | - `merge(obs1$, obs2$)`: Subscribes to all source Observables and simply passes through any value emitted by _any_ of them as soon as it arrives. Order depends on timing. 56 | - `concat(obs1$, obs2$)`: Subscribes to the first Observable, emits all its values, and _only then_ subscribes to the second Observable, emits its values, and so on. Preserves order strictly. 57 | - `race(obs1$, obs2$)`: Mirrors the first Observable (either `obs1$` or `obs2$`) to emit a value. Ignores the other(s). 58 | 59 | ## Error Handling Operators 60 | 61 | - **Purpose:** To gracefully handle errors that might occur in an Observable sequence. 62 | - **Examples:** 63 | 64 | - [`catchError`](../Operators/Error/catchError.md): Catches errors from the source Observable and either returns a replacement Observable (e.g., emitting a default value) or re-throws the error (or a new one). 65 | - [`retry`](../Operators/Error/retry.md): Re-subscribes to the source Observable up to N times if it encounters an error. 66 | - [`retryWhen`](../Operators/Error/retryWhen.md): Re-subscribes based on logic defined in a notifier Observable (e.g., retry after a delay). 67 | 68 | ## Utility Operators 69 | 70 | - **Purpose:** Miscellaneous operators useful for debugging, controlling timing, or other side effects. 71 | - **Examples:** 72 | 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. 77 | - `toArray()`: Collects all source emissions into a single array and emits that array when the source completes. 78 | 79 | ## Multicasting Operators 80 | 81 | - **Purpose:** To share a single subscription to an underlying Observable among multiple subscribers. This is key for turning Cold Observables Hot or optimizing shared resources. 82 | - **Examples:** 83 | 84 | - [`share`](../Operators/Multicasting/share.md): Shares a single subscription but doesn't necessarily replay past values. Subscription starts with the first subscriber and stops when the last one unsubscribes. 85 | - [`shareReplay`](../Operators/Multicasting/shareReplay.md): Shares a single subscription _and_ replays the last `bufferSize` emissions to new subscribers. Often used with `bufferSize: 1` to share API calls. The underlying subscription might stay active even after subscribers leave, depending on configuration. 86 | - `publish()`, `multicast()`: Lower-level operators for more complex multicasting scenarios, often used with Subjects. 87 | 88 | ## Conditional and Boolean Operators 89 | 90 | - **Purpose:** To evaluate conditions across sequences or emit boolean values. 91 | - **Examples:** 92 | 93 | - `every(x => x > 0)`: Emits `true` if all values satisfy the predicate, `false` otherwise, then completes. 94 | - `find(x => x === 5)`: Emits the first value that satisfies the predicate, then completes. 95 | - `isEmpty()`: Emits `true` if the source completes without emitting any values, `false` otherwise. 96 | - `defaultIfEmpty('default')`: Emits a default value if the source completes without emitting anything. 97 | 98 | Understanding these categories helps you navigate the RxJS library and choose the right tool for transforming, filtering, combining, or managing your asynchronous data streams effectively in Angular applications. 99 | -------------------------------------------------------------------------------- /docs/Operators/Subjects/subject-behaviorSubject-replaySubject.md: -------------------------------------------------------------------------------- 1 | Let's summarize the key differences between `Subject`, `BehaviorSubject`, and `ReplaySubject`. Understanding these distinctions is vital for choosing the right tool for the job. 2 | 3 | Here's a comparison table highlighting the main differences: 4 | 5 | | Feature | `Subject` | `BehaviorSubject` | `ReplaySubject` | 6 | | :----------------------- | :---------------------------- | :---------------------------------- | :--------------------------------------- | 7 | | **Initial Value?** | No | **Yes** (Required on creation) | No | 8 | | **Value for New Sub?** | **None**. Only future values. | **Yes**. The _single latest_ value. | **Yes**. The _last `n`_ buffered values. | 9 | | **Buffers Past Values?** | No | Implicitly buffers _only latest_ | **Yes**. Explicitly buffers last `n`. | 10 | | **Requires Config?** | No | Initial Value | Buffer Size (`n`), optionally time | 11 | | **`getValue()` Method?** | No | **Yes** (Synchronous access) | No | 12 | 13 | **Explanation in Simple Terms:** 14 | 15 | 1. **`Subject` ("The Basic Broadcaster")** 16 | 17 | - **Analogy:** Live Radio Broadcast / Event Emitter. 18 | - **Behavior:** It's like a plain event channel. When you emit (`next()`), it sends the value to _only those currently subscribed_. Anyone subscribing _later_ gets nothing until the _next_ emission. It doesn't remember past events. 19 | - **Use Case:** Simple event aggregation, triggers, or when you only care about future events from the moment of subscription. Good for bridging non-observable code (like button clicks via `.next()`) into streams. 20 | - **Example Recall:** Our cross-component communication example where the profile component only needed to know about _future_ login/logout events after it loaded. 21 | 22 | 2. **`BehaviorSubject` ("The State Holder")** 23 | 24 | - **Analogy:** Whiteboard / Status Board. 25 | - **Behavior:** It _must_ be created with an initial value. It always holds the _most recent_ value. When someone subscribes, they _immediately_ get this current value. Then, they receive any subsequent updates. 26 | - **Use Case:** Managing state that always has a current value (e.g., logged-in user status, current theme, selected filter). Perfect when components need the _current_ state immediately upon loading. Often used in state management services. Signals in Angular provide a compelling alternative for many state-holding scenarios, offering synchronous reads without needing `.getValue()`. 27 | - **Example Recall:** Our theme service example where components needed to know the _current_ theme ('light' or 'dark') right away. 28 | 29 | 3. **`ReplaySubject` ("The Recorder")** 30 | - **Analogy:** Meeting Recorder / Chat History. 31 | - **Behavior:** It records a specified number (`n`) of the most recent values. When someone subscribes, it _immediately replays_ those buffered values to the new subscriber, bringing them up to speed. After the replay, it behaves like a regular `Subject` for new values. 32 | - **Use Case:** Caching recent events when context is important for late subscribers. Useful for activity logs, notification streams, or data streams where missing the last few events would be problematic. 33 | - **Example Recall:** Our activity log service example where a display component needed to show the last 5 logged actions, even if it loaded _after_ those actions occurred. 34 | 35 | **When to Use Which:** 36 | 37 | - Use `Subject` when you just need to broadcast events as they happen, and subscribers don't need any history or initial value. 38 | - Use `BehaviorSubject` when you need to represent a piece of state that _always has a value_, and new subscribers should get the _current_ value immediately. (Consider if a Signal might be simpler for this state). 39 | - Use `ReplaySubject` when new subscribers need to get a history of the _last few_ emissions to have proper context. 40 | 41 | Remember to expose Subjects from services using `.asObservable()` to prevent external code from calling `.next()` on them, maintaining better encapsulation. 42 | -------------------------------------------------------------------------------- /docs/Operators/Subjects/subject.md: -------------------------------------------------------------------------------- 1 | Think of a `Subject` as a special kind of Observable that acts like **both** an Observable and an Observer: 2 | 3 | 1. **As an Observable:** You can `subscribe` to it just like any other Observable to receive values it emits. 4 | 2. **As an Observer:** You can manually push values _into_ it by calling its `next(value)` method. You can also make it emit an error with `error(err)` or signal completion with `complete()`. 5 | 6 | The **key characteristic** of a Subject is **multicasting**. This means that when the Subject emits a value (because `next()` was called on it), it pushes that value to _all_ of its current subscribers simultaneously. This is different from plain ("cold") Observables (like those from `HttpClient` or `interval`), which typically start a _new_, independent execution for _each_ subscriber. 7 | 8 | ## Analogy 9 | 10 | Imagine a **live radio broadcast**. 11 | 12 | - The **Radio Station Announcer** is the code calling `subject.next("Breaking news!")`. 13 | - The **Radio Station's Broadcast Tower** is the `Subject` itself. 14 | - The **Listeners** tuning their radios are the `Subscribers`. 15 | 16 | When the announcer speaks into the microphone (`next()`), the tower (`Subject`) broadcasts that message live, and _all_ listeners (`Subscribers`) who are currently tuned in hear the _same message at the same time_. Listeners who tune in _later_ will only hear broadcasts from that point forward; they miss the earlier messages (this is specific to the basic `Subject` type). 17 | 18 | ## Why Use a Subject? 19 | 20 | 1. **Event Bus:** To create a simple way for different parts of your application (like unrelated components) to communicate through a shared service. One part calls `next()` on the Subject, and other parts listening to that Subject react. 21 | 2. **Bridging:** To take values or events from non-Observable sources (like imperative button clicks, WebSocket messages, etc.) and push them into an Observable stream for further processing with RxJS operators. 22 | 3. **Sharing Observable Executions:** While there are operators like `shareReplay` often better suited for sharing the _result_ of an Observable, a Subject can sometimes be used to manually control and share a single subscription's output. 23 | 24 | ## Real-World Example: Cross-Component Communication 25 | 26 | Let's say you have a header component with a "Login" button and a user profile component elsewhere on the page. When the user successfully logs in (maybe via a popup triggered from the header), you want the user profile component to update immediately without a page refresh. 27 | 28 | We can use a shared service with a Subject to announce the login status change. 29 | 30 | **Code Snippets:** 31 | 32 | **1. Shared Authentication Service (`auth.service.ts`)** 33 | 34 | ```typescript 35 | import { Injectable } from "@angular/core"; 36 | import { Subject, Observable } from "rxjs"; 37 | 38 | export interface User { 39 | id: string; 40 | name: string; 41 | } 42 | 43 | @Injectable({ 44 | providedIn: "root", 45 | }) 46 | export class AuthService { 47 | // 1. Private Subject: Only the service can push values into it. 48 | // We use 'User | null' to indicate either a logged-in user or logged-out state. 49 | private userLoginStatusSubject = new Subject(); 50 | 51 | // 2. Public Observable: Components subscribe to this to listen for changes. 52 | // '.asObservable()' hides the .next(), .error(), .complete() methods from consumers. 53 | userLoginStatus$: Observable = 54 | this.userLoginStatusSubject.asObservable(); 55 | 56 | // Simulate a login process 57 | login(username: string): void { 58 | console.log("AuthService: Attempting login..."); 59 | // In a real app, this would involve an HTTP call, password check etc. 60 | setTimeout(() => { 61 | const fakeUser: User = { id: "user123", name: username }; 62 | console.log("AuthService: Login successful, broadcasting user."); 63 | // 3. Broadcasting the change: Push the new user data into the Subject. 64 | this.userLoginStatusSubject.next(fakeUser); 65 | }, 1000); // Simulate network delay 66 | } 67 | 68 | // Simulate a logout process 69 | logout(): void { 70 | console.log("AuthService: Logging out, broadcasting null user."); 71 | // 4. Broadcasting the change: Push 'null' into the Subject. 72 | this.userLoginStatusSubject.next(null); 73 | } 74 | } 75 | ``` 76 | 77 | **2. Header Component - Triggers Login/Logout** 78 | 79 | ```typescript 80 | import { Component, inject } from "@angular/core"; 81 | import { AuthService } from "./auth.service"; // Adjust path if needed 82 | 83 | @Component({ 84 | selector: "app-header", 85 | standalone: true, 86 | template: ` 87 | 92 | `, 93 | styles: [ 94 | "nav { display: flex; justify-content: space-between; padding: 10px; background-color: #eee; }", 95 | ], 96 | }) 97 | export class HeaderComponent { 98 | private authService = inject(AuthService); 99 | 100 | loginUser(): void { 101 | // Prompt or fixed user for simplicity 102 | const username = prompt("Enter username", "Alice"); 103 | if (username) { 104 | this.authService.login(username); 105 | } 106 | } 107 | 108 | logoutUser(): void { 109 | this.authService.logout(); 110 | } 111 | } 112 | ``` 113 | 114 | **3. User Profile Component - Listens for Changes** 115 | 116 | ```typescript 117 | import { 118 | Component, 119 | inject, 120 | signal, 121 | OnInit, 122 | DestroyRef, 123 | ChangeDetectionStrategy, 124 | } from "@angular/core"; 125 | import { CommonModule } from "@angular/common"; // Needed for @if 126 | import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; 127 | import { AuthService, User } from "./auth.service"; // Adjust path if needed 128 | 129 | @Component({ 130 | selector: "app-user-profile", 131 | standalone: true, 132 | imports: [CommonModule], // Import CommonModule 133 | template: ` 134 |
135 |

User Status

136 | @if (loggedInUser()) { 137 |

Welcome, {{ loggedInUser()?.name }}! (ID: {{ loggedInUser()?.id }})

138 | } @else { 139 |

You are currently logged out.

140 | } 141 |
142 | `, 143 | styles: [ 144 | ` 145 | .profile-status { 146 | border: 1px solid lightblue; 147 | padding: 10px; 148 | margin-top: 10px; 149 | } 150 | `, 151 | ], 152 | changeDetection: ChangeDetectionStrategy.OnPush, // Good practice with signals/observables 153 | }) 154 | export class UserProfileComponent implements OnInit { 155 | private authService = inject(AuthService); 156 | private destroyRef = inject(DestroyRef); 157 | 158 | // Use a signal to hold the user state for the template 159 | loggedInUser = signal(null); 160 | 161 | ngOnInit(): void { 162 | // Subscribe to the service's Observable 163 | this.authService.userLoginStatus$ 164 | .pipe( 165 | // Automatically unsubscribe when the component is destroyed 166 | takeUntilDestroyed(this.destroyRef) 167 | ) 168 | .subscribe((user) => { 169 | // Update the signal when a new status is broadcast by the Subject 170 | console.log("UserProfileComponent received user status:", user); 171 | this.loggedInUser.set(user); 172 | }); 173 | } 174 | } 175 | ``` 176 | 177 | **4. App Component (Hosting the others)** 178 | 179 | ```typescript 180 | import { Component } from "@angular/core"; 181 | import { HeaderComponent } from "./header.component"; // Adjust path 182 | import { UserProfileComponent } from "./user-profile.component"; // Adjust path 183 | 184 | @Component({ 185 | selector: "app-root", 186 | standalone: true, 187 | imports: [HeaderComponent, UserProfileComponent], // Import components 188 | template: ` 189 |

RxJS Subject Demo

190 | 191 | 192 | 193 | 194 | 195 | `, 196 | }) 197 | export class AppComponent {} 198 | ``` 199 | 200 | **Explanation:** 201 | 202 | 1. The `AuthService` creates a `Subject` (`userLoginStatusSubject`) to manage the login state. 203 | 2. It exposes only an `Observable` (`userLoginStatus$`) derived from the subject using `.asObservable()`. This is good practice – it prevents components from accidentally calling `next()` on the service's subject. Only the service itself controls when broadcasts happen. 204 | 3. When `authService.login()` or `authService.logout()` is called (triggered by `HeaderComponent`), the service calls `this.userLoginStatusSubject.next(...)`, pushing the new user data (or `null`) into the Subject. 205 | 4. The `UserProfileComponent` subscribes to `authService.userLoginStatus$` in its `ngOnInit`. 206 | 5. Whenever the Subject broadcasts a new value, the subscription in `UserProfileComponent` receives it, and updates the `loggedInUser` signal, causing the component's template to reactively display the current status. 207 | 6. `takeUntilDestroyed` ensures the subscription is cleaned up when the `UserProfileComponent` is destroyed. 208 | 209 | This demonstrates how a Subject acts as a central hub (multicasting) to notify multiple interested parties (subscribers) about events happening elsewhere in the application. 210 | -------------------------------------------------------------------------------- /docs/Operators/Transformation/map.md: -------------------------------------------------------------------------------- 1 | # map 2 | 3 | The `map()` operator is a **transformation operator**. Its job is to transform each value emitted by a source Observable into a _new_ value based on a function you provide. It then emits this new, transformed value. 4 | 5 | Think of it exactly like the `Array.prototype.map()` method you use with JavaScript arrays, but applied to values arriving over time in an Observable stream. For every single item that comes out of the source Observable, `map` applies your function to it and sends the result downstream. 6 | 7 | ## Key Characteristics 8 | 9 | - **Transforms Values:** Changes the data passing through the stream. 10 | - **One-to-One Emission:** For each value received from the source, it emits exactly one transformed value. 11 | - **Takes a Project Function:** You provide a function `map(projectFn)` where `projectFn` takes the source value as input and returns the transformed value. 12 | - **Preserves Timing/Order:** It doesn't delay emissions or change their order; it just modifies the data _within_ each emission. 13 | - **Passes Through Errors/Completion:** If the source Observable errors or completes, `map` simply passes those notifications along. 14 | 15 | ## Real-World Example Scenario (Very Common in Angular) 16 | 17 | Imagine you're fetching data from an API using Angular's `HttpClient`. The API might return a complex object or an array of objects with many properties, but your component only needs a specific piece of that data, or needs it in a slightly different format. 18 | 19 | **Scenario:** Let's say you fetch a list of products from an API. The API returns an array of product objects, each looking like this: 20 | 21 | ```json 22 | { 23 | "productId": "XYZ-123", 24 | "productName": "Super Widget", 25 | "price": { 26 | "amount": 99.99, 27 | "currency": "USD" 28 | }, 29 | "stock": 50, 30 | "category": "Widgets" 31 | } 32 | ``` 33 | 34 | Your component, however, only needs to display a simple list of product names (e.g., `["Super Widget", "Mega Gadget"]`). You can use `map()` to transform the raw API response (array of complex objects) into the desired array of strings. 35 | 36 | ## Code Snippet (Angular Service and Component) 37 | 38 | ```typescript 39 | // product.service.ts 40 | import { Injectable } from "@angular/core"; 41 | import { HttpClient } from "@angular/common/http"; 42 | import { Observable } from "rxjs"; 43 | import { map } from "rxjs/operators"; // Import the map operator 44 | 45 | // Define an interface for the raw API response structure (good practice) 46 | interface RawProduct { 47 | productId: string; 48 | productName: string; 49 | price: { 50 | amount: number; 51 | currency: string; 52 | }; 53 | stock: number; 54 | category: string; 55 | } 56 | 57 | @Injectable({ 58 | providedIn: "root", 59 | }) 60 | export class ProductService { 61 | private apiUrl = "/api/products"; // Your actual API endpoint 62 | 63 | constructor(private http: HttpClient) {} 64 | 65 | // Method to get only the product names as an Observable 66 | getProductNames(): Observable { 67 | console.log( 68 | `Workspaceing products from API at ${new Date().toLocaleTimeString( 69 | "en-IN", 70 | { timeZone: "Asia/Kolkata" } 71 | )} (IST)...` 72 | ); 73 | 74 | return this.http.get(this.apiUrl).pipe( 75 | // Use the map operator to transform the response 76 | map((products: RawProduct[]) => { 77 | // This function is executed when the HTTP request succeeds 78 | // 'products' here is the array of RawProduct objects from the API 79 | console.log("API returned raw products:", products); 80 | 81 | // Use JavaScript's Array.map to transform the array internally 82 | const names = products.map((product) => product.productName); 83 | 84 | console.log("Transformed raw products into names:", names); 85 | // Return the transformed array of names 86 | return names; 87 | }) 88 | // You could chain other operators here if needed, like filter, catchError etc. 89 | ); 90 | } 91 | 92 | // Example of fetching slightly more complex transformed data 93 | getActiveProductSummaries(): Observable<{ name: string; price: number }[]> { 94 | return this.http.get(this.apiUrl).pipe( 95 | map((products) => 96 | products 97 | .filter((p) => p.stock > 0) // First filter only active products 98 | .map((p) => ({ 99 | // Then map to the desired summary object 100 | name: p.productName.toUpperCase(), // Also transform name to uppercase 101 | price: p.price.amount, 102 | })) 103 | ) 104 | ); 105 | } 106 | } 107 | 108 | // product-list.component.ts 109 | import { Component, OnInit } from "@angular/core"; 110 | import { ProductService } from "./product.service"; 111 | import { Observable } from "rxjs"; 112 | 113 | @Component({ 114 | selector: "app-product-list", 115 | template: ` 116 |

Product Names

117 |
    118 |
  • {{ name }}
  • 119 |
120 | Loading product names... 121 | 122 |

Active Product Summaries

123 |
    124 |
  • 125 | {{ summary.name }} - Price: {{ summary.price | currency : "INR" }} 126 |
  • 127 |
128 | Loading summaries... 129 | `, 130 | }) 131 | export class ProductListComponent implements OnInit { 132 | productNames$: Observable | undefined; 133 | productSummaries$: Observable<{ name: string; price: number }[]> | undefined; 134 | 135 | constructor(private productService: ProductService) {} 136 | 137 | ngOnInit(): void { 138 | // Get the observable stream of product names (already transformed by map in the service) 139 | this.productNames$ = this.productService.getProductNames(); 140 | this.productSummaries$ = this.productService.getActiveProductSummaries(); 141 | } 142 | } 143 | ``` 144 | 145 | **Explanation:** 146 | 147 | 1. **`import { map } from 'rxjs/operators';`**: We import the `map` operator. 148 | 2. **`this.http.get(this.apiUrl).pipe(...)`**: We make the HTTP request, which returns an `Observable`. We use `.pipe()` to chain operators onto it. 149 | 3. **`map((products: RawProduct[]) => { ... })`**: We apply the `map` operator. The function inside `map` receives the emitted value from the source Observable – in this case, the array of `RawProduct` objects (`products`). 150 | 4. **`products.map(product => product.productName)`**: Inside the RxJS `map` function, we use the standard JavaScript `Array.map` method to iterate over the `products` array and pull out only the `productName` from each object. 151 | 5. **`return names;`**: The RxJS `map` operator takes the result of its inner function (the `names` array) and emits that as its own output value. 152 | 6. **Result:** The `getProductNames()` method now returns an `Observable`, which directly provides the data structure the component needs, thanks to the `map` operator doing the transformation in the service. The second example (`getActiveProductSummaries`) shows combining `filter` and `map` within the RxJS `map` operator's projection function for more complex transformations. 153 | 154 | ## Summary 155 | 156 | `map()` is your go-to tool whenever you need to change the _shape_ or _content_ of individual items flowing through your Observable stream without affecting the stream's overall timing or structure. 157 | -------------------------------------------------------------------------------- /docs/Operators/Transformation/switchMap-mergeMap-concatMap.md: -------------------------------------------------------------------------------- 1 | # switchMap Vs mergeMap Vs concatMap 2 | 3 | Let's break down the theoretical differences between `switchMap`, `mergeMap`, and `concatMap`. 4 | 5 | All three are higher-order mapping operators in RxJS, meaning they map each value from a source (outer) Observable to a new (inner) Observable. The key difference lies in how they handle the subscription and emissions of these inner Observables, especially when the source Observable emits values rapidly. 6 | 7 | Here’s a theoretical comparison: 8 | 9 | 1. **`switchMap`** 10 | 11 | - **Strategy:** Cancellation / Focus on Latest. 12 | - **Behavior:** When the source Observable emits a value, `switchMap` maps it to an inner Observable and subscribes. If the source emits a _new_ value _before_ the current inner Observable completes, `switchMap` will **unsubscribe** from the previous inner Observable (cancelling its ongoing work and discarding any potential future emissions from it) and then subscribe to the _new_ inner Observable created from the latest source value. 13 | - **Concurrency:** Only one inner Observable (the latest one) is active at any given time. 14 | - **Order:** Output values come only from the most recent inner Observable. The order depends on that inner Observable, but older inner streams are cancelled entirely. 15 | - **Use When:** You only care about the results corresponding to the **most recent** source emission. Useful for scenarios like type-ahead search suggestions where previous requests become irrelevant. 16 | 17 | 2. **`mergeMap` (alias: `flatMap`)** 18 | 19 | - **Strategy:** Concurrency / Merging. 20 | - **Behavior:** When the source Observable emits a value, `mergeMap` maps it to an inner Observable and subscribes. If the source emits a _new_ value, `mergeMap` **does not cancel** any previous inner Observables. It simply creates and subscribes to the new inner Observable, allowing multiple inner Observables to run **concurrently**. 21 | - **Concurrency:** Can have multiple inner Observables running in parallel. The level of concurrency can optionally be limited by passing a second argument to `mergeMap`. 22 | - **Order:** Output values from all active inner Observables are merged into a single stream as they arrive. The order of output values is not guaranteed to match the order of source emissions; it depends on how quickly each inner Observable emits. 23 | - **Use When:** You want to handle all source emissions by triggering potentially long-running operations and need them to run **in parallel** for efficiency. The order of completion doesn't matter as much as getting all the results eventually. Useful for making multiple concurrent API calls. 24 | 25 | 3. **`concatMap`** 26 | - **Strategy:** Sequential / Queueing. 27 | - **Behavior:** When the source Observable emits a value, `concatMap` maps it to an inner Observable. It subscribes to this inner Observable. If the source emits a _new_ value _before_ the current inner Observable **completes**, `concatMap` will **wait**. It holds onto the new source value and only maps/subscribes to its corresponding inner Observable _after_ the current one has finished completely. 28 | - **Concurrency:** Only one inner Observable is active at any given time. Others are effectively queued. 29 | - **Order:** Output values are guaranteed to be in the same order as the source emissions because each inner Observable is processed sequentially. 30 | - **Use When:** The **order of execution is critical**. You need to ensure that the operation triggered by one source value completes fully before starting the operation for the next source value. Useful for sequential API updates or processing items in a strict order. 31 | 32 | **In a Nutshell:** 33 | 34 | | Operator | Inner Observable Handling | Concurrency | Order | Analogy | 35 | | :---------- | :------------------------------------------- | :-------------- | :------------- | :-------------------------- | 36 | | `switchMap` | Cancels previous, switches to latest | Only latest | Latest matters | Restless TV channel surfing | 37 | | `mergeMap` | Runs all concurrently | High (Parallel) | Interleaved | Opening many browser tabs | 38 | | `concatMap` | Waits for completion, processes sequentially | One at a time | Strict | Waiting in a single queue | 39 | -------------------------------------------------------------------------------- /docs/Operators/Utility/delay.md: -------------------------------------------------------------------------------- 1 | The `delay` operator simply **shifts the emission** of each notification (`next`, `error`, `complete`) from its source Observable forward in time by a specified duration. 2 | 3 | Think of it like **scheduled mail delivery:** 4 | 5 | - The source Observable "drops a letter in the mailbox" (`next` emission occurs). 6 | - The `delay` operator picks it up but holds onto it. 7 | - It waits for the specified time (e.g., 500 milliseconds). 8 | - _Then_, it delivers the letter (emits the `next` value) downstream. 9 | 10 | The same happens for `error` and `complete` signals – they are also held for the specified duration before being passed on. 11 | 12 | ## Key Points 13 | 14 | 1. **Delays Emissions:** It delays _when_ the values/signals are sent to the next operator or subscriber. 15 | 2. **Doesn't Delay Subscription:** The subscription to the source happens immediately; only the emissions are postponed. 16 | 3. **Applies to All Notifications:** It delays `next`, `error`, and `complete`. 17 | 4. **Input:** Takes a duration in milliseconds (e.g., `delay(500)`) or a specific future `Date`. 18 | 19 | ## Why Use `delay`? 20 | 21 | 1. **UI Polish:** Simulate a minimum processing time. For example, if saving data is extremely fast, a "Saving..." message might just flash on and off. Using `delay` can ensure the message stays visible for at least, say, half a second, providing better user feedback. 22 | 2. **Testing/Debugging:** Introduce artificial latency into streams to test how your application handles timing issues or loading states. 23 | 3. **Simple Sequencing (Less Common):** Ensure a small pause before an action occurs after an event (though more complex sequencing often uses other operators). 24 | 25 | ## Real-World Example: Minimum Display Time for a "Saved" Message 26 | 27 | Imagine clicking a "Save" button. The backend operation might be incredibly fast (e.g., 50ms). If you immediately show and then hide a "Saved!" confirmation, the user might not even register it. Let's ensure the "Saved!" message stays visible for at least 750ms. 28 | 29 | ## Code Snippet 30 | 31 | ```typescript 32 | import { 33 | Component, 34 | inject, 35 | signal, 36 | ChangeDetectionStrategy, 37 | DestroyRef, 38 | } from "@angular/core"; 39 | import { CommonModule } from "@angular/common"; 40 | import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; 41 | import { Observable, of, timer } from "rxjs"; // Import 'of' and 'timer' 42 | import { delay, switchMap, tap, finalize, catchError } from "rxjs/operators"; 43 | import { EMPTY } from "rxjs"; // Import EMPTY 44 | 45 | // Mock Service Function (simulates a quick backend save) 46 | function mockSaveOperation(): Observable<{ 47 | success: boolean; 48 | timestamp: number; 49 | }> { 50 | console.log("Backend: Starting simulated save..."); 51 | const saveSuccess = Math.random() > 0.2; // Simulate occasional failure 52 | return of(saveSuccess).pipe( 53 | delay(100), // Simulate VERY FAST network/backend time (100ms) 54 | tap((success) => 55 | console.log( 56 | `Backend: Simulated save ${success ? "successful" : "failed"}.` 57 | ) 58 | ), 59 | switchMap((success) => { 60 | if (success) { 61 | return of({ success: true, timestamp: Date.now() }); 62 | } else { 63 | // Simulate an error being returned from backend 64 | return timer(50).pipe( 65 | switchMap(() => { 66 | throw new Error("Save failed due to backend validation."); 67 | }) 68 | ); 69 | } 70 | }) 71 | ); 72 | } 73 | 74 | @Component({ 75 | selector: "app-save-status", 76 | standalone: true, 77 | imports: [CommonModule], 78 | template: ` 79 |
80 |

Save Example with Delay

81 | 82 | 83 | @if (saving()) { 84 |

Saving...

85 | } @else if (statusMessage()) { 86 |

91 | {{ statusMessage() }} 92 |

93 | } 94 |
95 | `, 96 | changeDetection: ChangeDetectionStrategy.OnPush, 97 | }) 98 | export class SaveStatusComponent { 99 | private destroyRef = inject(DestroyRef); 100 | 101 | // --- State Signals --- 102 | saving = signal(false); 103 | statusMessage = signal(null); 104 | isSuccess = signal(false); 105 | 106 | saveData(): void { 107 | if (this.saving()) return; // Prevent multiple saves 108 | 109 | this.saving.set(true); 110 | this.statusMessage.set(null); // Clear previous status 111 | console.log('UI: Save initiated, showing "Saving..."'); 112 | 113 | const minimumDisplayTime = 750; // Ensure feedback shows for at least 750ms 114 | 115 | mockSaveOperation() 116 | .pipe( 117 | tap({ 118 | next: (result) => 119 | console.log("UI Stream: Save operation successful (before delay)"), 120 | error: (err) => 121 | console.error("UI Stream: Save operation failed (before delay)"), 122 | }), 123 | 124 | // --- Apply the delay --- 125 | // Delay the NEXT or ERROR notification by minimumDisplayTime 126 | delay(minimumDisplayTime), 127 | // --------------------- 128 | 129 | catchError((err: Error) => { 130 | // Handle the error AFTER the delay 131 | console.error("UI: Handling error after delay:", err.message); 132 | this.isSuccess.set(false); 133 | this.statusMessage.set(`Error: ${err.message}`); 134 | // Return EMPTY to gracefully complete the stream for finalize 135 | return EMPTY; 136 | }), 137 | // finalize runs after delay + next/error/complete 138 | finalize(() => { 139 | console.log('UI: Finalizing save operation (hiding "Saving...")'); 140 | this.saving.set(false); 141 | }), 142 | // Automatically unsubscribe when the component is destroyed 143 | takeUntilDestroyed(this.destroyRef) 144 | ) 145 | .subscribe({ 146 | next: (result) => { 147 | // Handle success AFTER the delay 148 | console.log(`UI: Displaying success message after delay.`); 149 | this.isSuccess.set(true); 150 | this.statusMessage.set( 151 | `Saved successfully at ${new Date( 152 | result.timestamp 153 | ).toLocaleTimeString()}` 154 | ); 155 | }, 156 | // Error is handled in catchError 157 | // Complete isn't strictly needed here as finalize covers the loading state change 158 | }); 159 | } 160 | } 161 | ``` 162 | 163 | **Explanation:** 164 | 165 | 1. When `saveData()` is called, `saving` is set to `true`, showing the "Saving..." message immediately. 166 | 2. `mockSaveOperation()` is called. It simulates a quick backend response (completes in ~100ms) using `of(...)` and `delay(100)`. 167 | 3. The result (or error) from `mockSaveOperation` flows into the component's RxJS pipe. 168 | 4. The first `tap` logs the immediate result from the "backend". 169 | 5. **`delay(minimumDisplayTime)`**: This is the key part. If the backend responded successfully (`next`), `delay` holds that success notification for 750ms before passing it on. If the backend responded with an error, `delay` holds that error notification for 750ms. 170 | 6. **After the 750ms delay:** 171 | - If successful: The `next` notification proceeds to the `subscribe` block's `next` handler. The success message is displayed. 172 | - If an error occurred: The `error` notification proceeds to the `catchError` operator. The error message is displayed. 173 | 7. **`finalize`**: This runs _after_ the delayed `next` or `error` has been processed (or if the stream completes/unsubscribes). It sets `saving` to `false`, hiding the "Saving..." message. 174 | 8. **`takeUntilDestroyed`**: Standard cleanup. 175 | 176 | Because of `delay(750)`, even though the backend might respond in 100ms, the UI won't update with the final "Saved!" or "Error..." message, and the "Saving..." indicator won't disappear, until _at least_ 750ms have passed since the backend responded. This gives the user time to perceive the feedback. 177 | -------------------------------------------------------------------------------- /docs/Operators/Utility/finalize.md: -------------------------------------------------------------------------------- 1 | The `finalize` operator lets you specify a callback function that will be executed **when the source Observable terminates**. Termination happens in one of three ways: 2 | 3 | 1. The Observable **completes** successfully (sends its last value and the `complete` notification). 4 | 2. The Observable emits an **error** notification. 5 | 3. The subscription to the Observable is **unsubscribed** (e.g., manually, or automatically via operators like `take`, `takeUntil`, or `takeUntilDestroyed`). 6 | 7 | Think of it like the `finally` block in a traditional `try...catch...finally` statement. The code inside `finalize` is **guaranteed** to run _after_ the Observable finishes its work or is stopped, regardless of _why_ it stopped (success, error, or unsubscription). 8 | 9 | ## Key Points 10 | 11 | 1. **Guaranteed Execution on Termination:** Runs whether the stream succeeds, fails, or is unsubscribed. 12 | 2. **No Arguments:** The callback function you provide to `finalize` receives no arguments. It doesn't know _if_ an error occurred or what the last value was; it just knows the stream is done. 13 | 3. **Side Effects Only:** Like `tap`, `finalize` is purely for side effects. It doesn't affect the values, errors, or completion signals passing _through_ the stream (because it runs _after_ them). 14 | 4. **Ideal for Cleanup:** Its primary purpose is resource cleanup or actions that must happen when an operation is finished. 15 | 16 | ## Why Use `finalize`? 17 | 18 | The most common and important use case is **managing loading states**. 19 | 20 | - You start an operation (e.g., HTTP request). 21 | - You set a `loading` flag/signal to `true`. 22 | - The operation might succeed or fail. 23 | - You need to ensure the `loading` flag/signal is _always_ set back to `false` when the operation is finished, no matter the outcome. `finalize` is perfect for this. 24 | 25 | Other uses include: 26 | 27 | - Closing connections (though often handled by unsubscription itself). 28 | - Logging the end of an operation. 29 | - Releasing any temporary resources acquired at the start of the subscription. 30 | 31 | ## Real-World Example: Managing Loading State for Data Fetching 32 | 33 | This is the classic example. We fetch data, show a loading indicator, and use `finalize` to hide the indicator when the fetch completes or fails. 34 | 35 | ### Code Snippet 36 | 37 | ```typescript 38 | import { 39 | Component, 40 | inject, 41 | signal, 42 | ChangeDetectionStrategy, 43 | DestroyRef, 44 | } from "@angular/core"; 45 | import { CommonModule } from "@angular/common"; 46 | import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; 47 | import { Observable, of, timer } from "rxjs"; 48 | import { delay, switchMap, tap, catchError, finalize } from "rxjs/operators"; 49 | import { EMPTY } from "rxjs"; // Import EMPTY 50 | 51 | // --- Mock Data Service --- 52 | interface Product { 53 | id: number; 54 | name: string; 55 | price: number; 56 | } 57 | 58 | function mockFetchProducts( 59 | failRequest: boolean = false 60 | ): Observable { 61 | console.log("Backend: Starting simulated product fetch..."); 62 | if (failRequest) { 63 | // Simulate a delayed error 64 | return timer(1200).pipe( 65 | tap(() => console.log("Backend: Simulating network error...")), 66 | switchMap(() => { 67 | throw new Error("Network Error: Failed to connect to server."); 68 | }) 69 | ); 70 | } else { 71 | // Simulate a successful response with delay 72 | const products: Product[] = [ 73 | { id: 101, name: "Super Widget", price: 19.99 }, 74 | { id: 102, name: "Mega Gadget", price: 29.95 }, 75 | ]; 76 | return of(products).pipe( 77 | delay(1500), // Simulate network latency 78 | tap(() => console.log("Backend: Simulated fetch successful.")) 79 | ); 80 | } 81 | } 82 | // --- End Mock Data Service --- 83 | 84 | @Component({ 85 | selector: "app-product-list", 86 | standalone: true, 87 | imports: [CommonModule], 88 | template: ` 89 |
90 |

Product List (Finalize Example)

91 | 94 | 97 | 98 | @if (loading()) { 99 |

Loading products...

100 | } @if (errorMessage()) { 101 |

Error: {{ errorMessage() }}

102 | } @if (products().length > 0 && !loading()) { 103 |
    104 | @for(product of products(); track product.id) { 105 |
  • {{ product.name }} - {{ product.price | currency }}
  • 106 | } 107 |
108 | } @else if (!loading() && !errorMessage()) { 109 |

Click button to load.

110 | } 111 |
112 | `, 113 | changeDetection: ChangeDetectionStrategy.OnPush, 114 | }) 115 | export class ProductListComponent { 116 | private destroyRef = inject(DestroyRef); 117 | 118 | // --- State Signals --- 119 | loading = signal(false); 120 | products = signal([]); 121 | errorMessage = signal(null); 122 | 123 | loadProducts(simulateError: boolean = false): void { 124 | if (this.loading()) return; // Don't load if already loading 125 | 126 | this.loading.set(true); // <-- Start Loading Indicator 127 | this.products.set([]); 128 | this.errorMessage.set(null); 129 | console.log("UI: Fetch initiated, showing loading state."); 130 | 131 | mockFetchProducts(simulateError) 132 | .pipe( 133 | tap((data) => 134 | console.log("UI Stream: Received product data (before finalize)") 135 | ), 136 | catchError((err: Error) => { 137 | console.error("UI Stream: Error caught:", err.message); 138 | this.errorMessage.set(err.message || "Could not load products."); 139 | // Return EMPTY to allow finalize to run after error handling 140 | return EMPTY; 141 | }), 142 | // --- Key Operator --- 143 | // This block runs AFTER success (tap), OR AFTER error (catchError), 144 | // OR if the subscription is cancelled (e.g., by takeUntilDestroyed). 145 | finalize(() => { 146 | this.loading.set(false); // <-- Stop Loading Indicator 147 | console.log( 148 | "UI: Finalize block executed - Loading state set to false." 149 | ); 150 | }), 151 | // -------------------- 152 | takeUntilDestroyed(this.destroyRef) // Ensure unsubscription on destroy 153 | ) 154 | .subscribe({ 155 | next: (data) => { 156 | console.log("UI: Subscribe next - updating product list."); 157 | this.products.set(data); 158 | }, 159 | // Error already handled by catchError 160 | error: (err) => { 161 | /* No need for code here usually if catchError handles UI state */ 162 | }, 163 | // Complete isn't needed for loading state because finalize covers it 164 | complete: () => { 165 | console.log("UI: Subscribe complete."); 166 | }, 167 | }); 168 | } 169 | } 170 | ``` 171 | 172 | **Explanation:** 173 | 174 | 1. When `loadProducts` is called, `loading` is immediately set to `true`, displaying the "Loading products..." message. 175 | 2. `mockFetchProducts` returns an Observable that simulates either success or failure after a delay. 176 | 3. The `pipe` chain processes the result: 177 | - `tap`: Logs successful data receipt (only runs on success). 178 | - `catchError`: Catches any error from the source. It sets the `errorMessage` signal and returns `EMPTY`. Returning `EMPTY` makes the stream complete gracefully _after_ the error, ensuring `finalize` still runs. 179 | - **`finalize(() => { this.loading.set(false); })`**: This is the crucial part. This callback function is registered to run when the stream terminates. 180 | - If `mockFetchProducts` succeeds, the `next` value passes through `tap`, then the stream completes. `finalize` runs, setting `loading` to `false`. 181 | - If `mockFetchProducts` fails, the error goes to `catchError`. `catchError` handles it and returns `EMPTY`, which immediately completes the stream. `finalize` runs, setting `loading` to `false`. 182 | - If the component is destroyed _while_ the fetch is in progress, `takeUntilDestroyed` triggers unsubscription. `finalize` runs, setting `loading` to `false`. 183 | - `takeUntilDestroyed`: Handles automatic unsubscription. 184 | 4. The `subscribe` block's `next` handler updates the `products` signal only on success. 185 | 186 | No matter what happens – success, failure, or component destruction – the `finalize` block ensures that `this.loading.set(false)` is called, correctly cleaning up the UI loading state. This makes it much more reliable than trying to manage the loading flag in both the `error` and `complete`/`next` handlers of the `subscribe` block. 187 | -------------------------------------------------------------------------------- /docs/Operators/Utility/timeout.md: -------------------------------------------------------------------------------- 1 | The `timeout` operator sets a time limit. If the source Observable doesn't emit its **first value** or **complete** within that specified duration, the `timeout` operator will cause the stream to **emit a `TimeoutError`** and terminate. 2 | 3 | Think of it like setting an **egg timer** for an operation: 4 | 5 | - You start an operation (subscribe to the source Observable). 6 | - You start the timer (`timeout` operator). 7 | - If the operation finishes (emits a value or completes) _before_ the timer goes off, everything is fine. 8 | - If the timer goes off _before_ the operation finishes, the timer rings loudly (`TimeoutError` is emitted), and you stop waiting for the original operation. 9 | 10 | ## Key Configurations & Behaviors 11 | 12 | You can configure `timeout` with a duration (milliseconds) or a specific Date. More advanced configurations allow specifying different timeouts for the first emission versus subsequent emissions, but the most common use is a single duration for the overall operation. 13 | 14 | - `timeout(5000)`: Throws `TimeoutError` if the _first_ emission doesn't arrive within 5 seconds of subscription. 15 | - `timeout({ first: 5000, each: 1000 })`: Throws if the first emission takes longer than 5s, OR if the time between _any two subsequent_ emissions exceeds 1s. (Less common). 16 | - `timeout({ each: 10000 })`: Allows the first emission to take any amount of time, but throws if subsequent emissions are more than 10s apart. 17 | 18 | ## Why Use `timeout`? 19 | 20 | 1. **Preventing Indefinite Waits:** Protects your application from hanging if a backend service or other asynchronous source becomes unresponsive. 21 | 2. **Improving User Experience:** Provides timely feedback (an error message) to the user instead of leaving them staring at a loading spinner forever. 22 | 3. **Resource Management:** Can help release resources tied up in waiting for a response that may never come. 23 | 24 | ## Real-World Example: Setting a Timeout for an API Request 25 | 26 | A common scenario is fetching data from an external API. Sometimes, the network might be slow, or the API server itself might be experiencing issues. We want to limit how long we wait for a response before giving up. 27 | 28 | ### Code Snippet 29 | 30 | **1. Mock Data Service (Simulates Slow/Fast Responses)** 31 | 32 | ```typescript 33 | import { Injectable } from "@angular/core"; 34 | import { Observable, of, timer } from "rxjs"; 35 | import { delay, switchMap, tap } from "rxjs/operators"; 36 | 37 | export interface ExternalData { 38 | id: string; 39 | value: number; 40 | } 41 | 42 | @Injectable({ 43 | providedIn: "root", 44 | }) 45 | export class SlowDataService { 46 | fetchData(id: string, responseTimeMs: number): Observable { 47 | console.log( 48 | `Backend: Request received for ID ${id}. Will respond in ${responseTimeMs}ms.` 49 | ); 50 | const data: ExternalData = { id: id, value: Math.random() * 100 }; 51 | 52 | // Simulate the delay 53 | return of(data).pipe( 54 | delay(responseTimeMs), 55 | tap(() => console.log(`Backend: Responding for ID ${id}.`)) 56 | ); 57 | } 58 | } 59 | ``` 60 | 61 | **2. Data Fetching Component (Applies `timeout`)** 62 | 63 | ```typescript 64 | import { 65 | Component, 66 | inject, 67 | signal, 68 | ChangeDetectionStrategy, 69 | DestroyRef, 70 | } from "@angular/core"; 71 | import { CommonModule } from "@angular/common"; 72 | import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; 73 | import { SlowDataService, ExternalData } from "./slow-data.service"; // Adjust path 74 | import { tap, timeout, catchError, finalize } from "rxjs/operators"; 75 | import { EMPTY, TimeoutError } from "rxjs"; // Import TimeoutError and EMPTY 76 | 77 | @Component({ 78 | selector: "app-data-fetcher", 79 | standalone: true, 80 | imports: [CommonModule], 81 | template: ` 82 |
83 |

Data Fetcher with Timeout

84 | 87 | 90 | 91 | @if (loading()) { 92 |

Loading data (Timeout set to 5s)...

93 | } @if (errorMessage()) { 94 |

Error: {{ errorMessage() }}

95 | } @if (fetchedData()) { 96 |
97 |

Data Received:

98 |
{{ fetchedData() | json }}
99 |
100 | } 101 |
102 | `, 103 | // No 'styles' section as per previous request 104 | changeDetection: ChangeDetectionStrategy.OnPush, 105 | }) 106 | export class DataFetcherComponent { 107 | private dataService = inject(SlowDataService); 108 | private destroyRef = inject(DestroyRef); 109 | 110 | // --- State Signals --- 111 | loading = signal(false); 112 | fetchedData = signal(null); 113 | errorMessage = signal(null); 114 | 115 | private readonly apiTimeoutMs = 5000; // Set timeout to 5 seconds 116 | 117 | getData(responseTimeMs: number): void { 118 | if (this.loading()) return; 119 | 120 | this.loading.set(true); 121 | this.fetchedData.set(null); 122 | this.errorMessage.set(null); 123 | console.log( 124 | `UI: Initiating fetch. Response expected in ${responseTimeMs}ms. Timeout is ${this.apiTimeoutMs}ms.` 125 | ); 126 | 127 | this.dataService 128 | .fetchData("item123", responseTimeMs) 129 | .pipe( 130 | tap((data) => 131 | console.log( 132 | "UI Stream: Data received (before timeout check completed)" 133 | ) 134 | ), 135 | 136 | // --- Apply the timeout --- 137 | timeout(this.apiTimeoutMs), // If no 'next' within 5000ms, throw TimeoutError 138 | // ----------------------- 139 | 140 | catchError((err) => { 141 | console.error("UI Stream: Error caught."); 142 | // --- Check specifically for TimeoutError --- 143 | if (err instanceof TimeoutError) { 144 | console.error("Error Type: TimeoutError"); 145 | this.errorMessage.set( 146 | `Operation timed out after ${this.apiTimeoutMs / 1000} seconds.` 147 | ); 148 | } else { 149 | console.error("Error Type: Other", err); 150 | this.errorMessage.set( 151 | `An unexpected error occurred: ${err.message || err}` 152 | ); 153 | } 154 | // Return EMPTY to allow finalize to run 155 | return EMPTY; 156 | }), 157 | finalize(() => { 158 | this.loading.set(false); 159 | console.log( 160 | "UI: Finalize block executed - Loading state set to false." 161 | ); 162 | }), 163 | takeUntilDestroyed(this.destroyRef) 164 | ) 165 | .subscribe({ 166 | next: (data) => { 167 | console.log("UI: Subscribe next - updating data signal."); 168 | this.fetchedData.set(data); 169 | }, 170 | // Error handled by catchError 171 | error: (err) => { 172 | /* Already handled */ 173 | }, 174 | complete: () => { 175 | console.log("UI: Subscribe complete."); 176 | }, 177 | }); 178 | } 179 | } 180 | ``` 181 | 182 | **Explanation:** 183 | 184 | 1. When `getData()` is called, `loading` is set to `true`. 185 | 2. The `SlowDataService.fetchData` method is called, which returns an Observable that will emit data after the specified `responseTimeMs`. 186 | 3. **`timeout(this.apiTimeoutMs)`**: This operator starts its internal timer (5000ms). It waits for the `fetchData` Observable to emit a `next` notification. 187 | - **Scenario 1 (Fetch Fast Data - 500ms):** The `fetchData` Observable emits data after 500ms. This is well within the 5000ms timeout. `timeout` sees the emission, cancels its internal timer, and passes the data along the stream. The data is displayed. 188 | - **Scenario 2 (Fetch Slow Data - 6000ms):** The `fetchData` Observable is set to respond after 6000ms. The `timeout` operator's timer reaches 5000ms _before_ `fetchData` emits anything. `timeout` stops waiting, throws a `TimeoutError`, and terminates the source subscription. 189 | 4. **`catchError((err) => ...)`**: This catches any error, including the `TimeoutError`. 190 | - We use `instanceof TimeoutError` to specifically check if the error was due to the timeout. 191 | - We set an appropriate `errorMessage` based on the error type. 192 | - We return `EMPTY` to ensure the stream completes gracefully for `finalize`. 193 | 5. **`finalize(() => { this.loading.set(false); })`**: This runs reliably after the stream terminates (either successfully after `next`, or after `catchError` handles the `TimeoutError` or any other error), ensuring the loading indicator is hidden. 194 | 6. **`takeUntilDestroyed`**: Standard automatic unsubscription. 195 | 7. The `subscribe` block updates the `fetchedData` signal only if the operation completed successfully within the timeout period. 196 | 197 | By using `timeout`, you make your data fetching more robust against unresponsive services, leading to a better user experience. 198 | -------------------------------------------------------------------------------- /docs/assets/Rx_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnkitSharma-007/rxjs-angular-interview-guide/85b27f3667e41f862695265f293fff582d945570/docs/assets/Rx_Logo.png -------------------------------------------------------------------------------- /docs/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnkitSharma-007/rxjs-angular-interview-guide/85b27f3667e41f862695265f293fff582d945570/docs/assets/favicon.ico -------------------------------------------------------------------------------- /docs/cold-observables.md: -------------------------------------------------------------------------------- 1 | A **Cold Observable** is one where the data producer (the logic inside the Observable) doesn't start running or emitting values **until** you subscribe to it. 2 | 3 | Crucially, **each time you subscribe** to a cold Observable, it starts its work from the very beginning and generates a **fresh, independent sequence of values** just for that subscriber. 4 | 5 | ## Key Characteristics 6 | 7 | 1. **Lazy Execution:** The code that produces values only runs when `subscribe()` is called. No subscription = no execution. 8 | 2. **Independent Execution per Subscriber:** Every subscriber gets their own private stream of data. What happens in one subscription doesn't affect another. 9 | 10 | ## Real-World Analogy 11 | 12 | 1. **Watching a YouTube Video (On-Demand):** 13 | 14 | - When you click "Play" on a video (you _subscribe_), the video stream starts playing from the beginning _just for you_. 15 | - If your friend clicks "Play" on the same video later (another _subscription_), they also get the video starting from the beginning, completely independent of your playback. 16 | - The YouTube server (the _Observable_) delivers a separate, unique stream to each viewer (each _subscriber_). 17 | 18 | 2. **Playing a DVD or Blu-ray:** 19 | 20 | - Putting the disc in the player and pressing play (_subscribing_) starts the movie from the beginning. 21 | - Every time someone does this with their own player (_another subscription_), the movie starts fresh for them. 22 | 23 | ## Examples in Angular/RxJS 24 | 25 | 1. **`HttpClient` (Very Common):** 26 | 27 | - Observables returned by Angular's `HttpClient` (e.g., `http.get()`, `http.post()`) are **cold**. 28 | - Every time you `subscribe()` to `dataService.getItems()`, Angular makes a **new HTTP request** to the server. 29 | - If Component A subscribes and Component B subscribes to the _same_ service call, **two separate** network requests will be made. 30 | 31 | ```typescript 32 | // In some component: 33 | ngOnInit() { 34 | console.log("Subscribing first time to getItems..."); 35 | this.dataService.getItems().subscribe(data => { 36 | console.log("First subscription received data."); 37 | }); 38 | 39 | // Some time later, maybe in response to a user action 40 | console.log("Subscribing second time to getItems..."); 41 | this.dataService.getItems().subscribe(data => { 42 | console.log("Second subscription received data."); // This triggers a NEW HTTP request 43 | }); 44 | } 45 | ``` 46 | 47 | 2. **Basic Creation Functions:** Many RxJS creation functions like `of()`, `from()`, `range()`, `timer()`, `interval()` typically create cold Observables. 48 | 49 | ```typescript 50 | import { interval } from "rxjs"; 51 | 52 | // interval(1000) emits 0, 1, 2... every second 53 | const coldInterval = interval(1000); 54 | 55 | console.log("Subscribing A"); 56 | const subA = coldInterval.subscribe((val) => console.log(`Sub A: ${val}`)); // Starts a timer for A 57 | 58 | setTimeout(() => { 59 | console.log("Subscribing B"); 60 | const subB = coldInterval.subscribe((val) => 61 | console.log(`Sub B: ${val}`) 62 | ); // Starts a NEW timer for B 63 | }, 3000); 64 | 65 | // Output will show: 66 | // Subscribing A 67 | // Sub A: 0 (at 1s) 68 | // Sub A: 1 (at 2s) 69 | // Sub A: 2 (at 3s) 70 | // Subscribing B 71 | // Sub B: 0 (at 4s, because B's timer started at 3s) 72 | // Sub A: 3 (at 4s) 73 | // Sub B: 1 (at 5s) 74 | // Sub A: 4 (at 5s) 75 | // ... and so on. A and B have independent timers. 76 | ``` 77 | 78 | ## Summary 79 | 80 | Think of **Cold Observables** as blueprints or recipes for generating data streams. Each time you ask for the stream (by subscribing), the recipe is followed from the start, creating a unique instance just for you. Most Observables you deal with for one-off tasks like API calls are cold. 81 | -------------------------------------------------------------------------------- /docs/hot-observables.md: -------------------------------------------------------------------------------- 1 | A **Hot Observable** is one that produces values **even if no one is subscribed** to it. It's like a live broadcast – it's happening whether you tune in or not. 2 | 3 | When you subscribe to a hot Observable, you start receiving values from that point forward. You **miss any values** that were emitted _before_ you subscribed. 4 | 5 | ### Key Characteristics: 6 | 7 | 1. **Eager Execution (Potentially):** The source of values might start producing immediately (like UI events) or based on some external trigger, not necessarily tied to the first subscription. 8 | 2. **Shared Execution:** All subscribers listen to the _same_ stream of values. When the Observable emits a value, all current subscribers receive that same value simultaneously. 9 | 10 | ### Real-World Analogy: 11 | 12 | - **Live Radio Broadcast:** 13 | 14 | - The radio station is broadcasting music or talk shows continuously (_producing values_). 15 | 16 | - When you tune your radio to that station (_subscribe_), you start hearing whatever is being broadcast _at that moment_. You don't get to hear the beginning of the song or show that started before you tuned in. 17 | 18 | - Everyone else listening to the same station at the same time hears the exact same broadcast (_shared execution_). 19 | 20 | - **Mouse Clicks on a Web Page:** 21 | 22 | - The browser generates mouse click events whenever and wherever the user clicks, regardless of whether your specific code is listening for them yet. 23 | 24 | - If you attach an event listener (_subscribe_) to a button at some point, you will only capture the clicks that happen _after_ your listener is active. Clicks that happened before are lost (to your listener). 25 | 26 | ### Contrast with Cold Observables: 27 | 28 | Remember, Cold Observables only start when you subscribe, and each subscription gets its own independent run from the beginning (like watching a recorded YouTube video). Hot Observables are live and shared. 29 | 30 | ### Examples in Angular/RxJS: 31 | 32 | 1. **DOM Events using `fromEvent`:** Observables created from browser events are inherently hot. 33 | 34 | ```typescript 35 | import { Component, ViewChild, ElementRef, inject } from "@angular/core"; 36 | import { fromEvent } from "rxjs"; 37 | import { map } from "rxjs/operators"; 38 | import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; 39 | 40 | @Component({ 41 | selector: "app-hot-example", 42 | template: ` 43 | 44 |

Check console log.

45 | `, 46 | }) 47 | export class HotExampleComponent { 48 | @ViewChild("myButton", { static: true }) myButton!: ElementRef; 49 | 50 | constructor() { 51 | const destroyRef = inject(takeUntilDestroyed); 52 | 53 | // Create a HOT observable from click events 54 | const buttonClicks$ = fromEvent( 55 | this.myButton.nativeElement, 56 | "click" 57 | ).pipe(map(() => `Clicked at ${new Date().toLocaleTimeString()}`)); 58 | 59 | console.log( 60 | "Component initialized. Try clicking the button now - nothing logged yet." 61 | ); 62 | console.log("Subscribing to clicks in 5 seconds..."); 63 | 64 | setTimeout(() => { 65 | console.log("Subscribing now!"); 66 | buttonClicks$ 67 | .pipe(destroyRef()) 68 | .subscribe((message) => console.log(message)); 69 | }, 5000); 70 | } 71 | } 72 | 73 | // If you click the button in the first 5 seconds, the clicks happen, 74 | // but the console.log inside the subscribe won't show anything 75 | // because the subscription isn't active yet. Clicks after 5 seconds will be logged. 76 | ``` 77 | 78 | 2. **Subjects:** RxJS `Subject` and its variations (`BehaviorSubject`, `ReplaySubject`) are used to create hot, multicast Observables. They are often used for state management or communication between different parts of an Angular application. 79 | 80 | 3. **Making Cold Observables Hot (`share`, `shareReplay`):** Sometimes you have a cold Observable (like `HttpClient`) but want to share its single execution among multiple subscribers. Operators like `shareReplay()` make the source Observable hot. 81 | 82 | - **Use Case:** Imagine multiple parts of your UI need the same user profile data. You only want to fetch it via HTTP once. 83 | 84 | ```typescript 85 | import { Injectable, inject } from "@angular/core"; 86 | import { HttpClient } from "@angular/common/http"; 87 | import { Observable } from "rxjs"; 88 | import { shareReplay } from "rxjs/operators"; 89 | 90 | @Injectable({ providedIn: "root" }) 91 | export class UserService { 92 | private readonly http = inject(HttpClient); 93 | 94 | // Shared observable for user profile. 95 | // HTTP call is made once, result is cached and replayed to all subscribers. 96 | private readonly userProfile$: Observable; 97 | 98 | constructor() { 99 | console.log("UserService: Setting up shared user profile fetch."); 100 | this.userProfile$ = this.http.get("/api/user/profile").pipe( 101 | shareReplay(1) // Cache the latest value and share across subscribers 102 | ); 103 | } 104 | 105 | // Expose the shared observable so components get the cached result 106 | getUserProfile(): Observable { 107 | console.log( 108 | "UserService: getUserProfile called, returning shared observable." 109 | ); 110 | return this.userProfile$; 111 | } 112 | } 113 | 114 | // Component A calls getUserProfile().subscribe() -> Triggers HTTP request. 115 | // Component B calls getUserProfile().subscribe() -> Does NOT trigger HTTP, gets the same result (or waits for the ongoing one). 116 | ``` 117 | 118 | ## Summary 119 | 120 | Think of **Hot Observables** as live, shared streams. They are active regardless of listeners, and subscribers tune in to the ongoing broadcast, potentially missing past events. They are common for UI events, real-time data, and scenarios where you explicitly want to share a single data source execution among multiple consumers using subjects or operators like `shareReplay`. 121 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # RxJS Angular interview guide 2 | 3 | Ready to take your Angular skills to the next level? Welcome to the ultimate **RxJS interview guide** designed for Angular developers like you! Whether you're preparing for your next job interview or simply want to deepen your understanding of **reactive programming** in Angular, this guide has you covered. Explore **real-world RxJS examples**, **clear explanations**, and **detailed interview questions and answers** that will give you the edge over other candidates. 4 | 5 | Learn how to use **RxJS operators** effectively in Angular applications, understand best practices, and discover expert tips that will help you tackle even the toughest interview challenges. With this guide, you’ll not only master the intricacies of RxJS but also develop a deeper understanding of how to apply these concepts to real-world scenarios. 6 | 7 | Are you ready to **crack your next Angular interview** and become a true expert in reactive programming? Let’s dive in! 8 | -------------------------------------------------------------------------------- /docs/observable.md: -------------------------------------------------------------------------------- 1 | Think of an **Observable** as a **stream** of data or events that happens over time. It's like subscribing to a newsletter or a YouTube channel. Once you subscribe, you start receiving updates (emails, new videos) whenever they are published. You can receive **zero, one, or many** updates over the lifetime of that subscription. 2 | 3 | ### Key Ideas: 4 | 5 | 1. **Stream of Values:** Unlike a function that returns a single value, or a Promise that resolves with a single value (or rejects), an Observable can emit multiple values over time. 6 | 2. **Lazy:** Observables are "lazy." They don't start emitting values until someone actually subscribes to them. If nobody subscribes, the code that produces the values won't even run. 7 | 3. **Asynchronous (or Synchronous):** They are often used for asynchronous operations (like fetching data from a server), but they can also emit values synchronously. 8 | 4. **Cancellation:** You can "unsubscribe" from an Observable, meaning you stop listening to the stream. This is important for cleaning up resources and preventing memory leaks. 9 | 10 | ### Real-World Analogy: 11 | 12 | Imagine you've ordered food online. 13 | 14 | - You place the order (this is like _creating_ the Observable). 15 | - The system _doesn't_ start processing your order until you actually confirm it (like _subscribing_). 16 | - Then, you get updates over time: 17 | - "Order received" (_first value_) 18 | - "Restaurant is preparing your food" (_second value_) 19 | - "Delivery partner assigned" (_third value_) 20 | - "Food is on the way" (_fourth value_) 21 | - "Delivered" (*fifth value, and maybe the stream *completes* here*) 22 | - If something goes wrong (e.g., restaurant cancels), you get an error notification (_error_). 23 | - You can choose to stop receiving notifications if you want (_unsubscribe_). 24 | 25 | ### How it's used in Angular (Examples): 26 | 27 | 1. **Fetching Data with `HttpClient`:** This is the most common use case. When you make an HTTP request using Angular's `HttpClient`, it returns an Observable. You subscribe to this Observable to get the response from the server. 28 | 29 | #### Service (`data.service.ts`): 30 | 31 | ```typescript 32 | import { Injectable, inject } from "@angular/core"; 33 | import { HttpClient } from "@angular/common/http"; 34 | import { Observable } from "rxjs"; 35 | 36 | @Injectable({ 37 | providedIn: "root", 38 | }) 39 | export class DataService { 40 | private readonly http = inject(HttpClient); 41 | private readonly apiUrl = "https://api.example.com/items"; 42 | 43 | getItems(): Observable { 44 | return this.http.get(this.apiUrl); 45 | } 46 | } 47 | ``` 48 | 49 | #### Component (`my-component.component.ts`): 50 | 51 | ```typescript 52 | import { Component, inject, effect } from "@angular/core"; 53 | import { DataService } from "./data.service"; 54 | import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; 55 | 56 | @Component({ 57 | selector: "app-my-component", 58 | template: ` 59 |
    60 |
  • {{ item.name }}
  • 61 |
62 |
{{ errorMsg }}
63 | `, 64 | }) 65 | export class MyComponentComponent { 66 | private readonly dataService = inject(DataService); 67 | items: any[] = []; 68 | errorMsg = ""; 69 | 70 | constructor() { 71 | this.dataService 72 | .getItems() 73 | .pipe(takeUntilDestroyed()) 74 | .subscribe({ 75 | next: (data) => { 76 | this.items = data; 77 | console.log("Data received:", data); 78 | }, 79 | error: (error) => { 80 | this.errorMsg = "Failed to load items."; 81 | console.error("Error fetching items:", error); 82 | }, 83 | complete: () => { 84 | console.log("Finished fetching items."); 85 | }, 86 | }); 87 | } 88 | } 89 | ``` 90 | 91 | 2. **Reactive Forms:** Listening to changes in form input values (`valueChanges`) or status (`statusChanges`). 92 | 3. **Router Events:** Subscribing to events from the Angular Router to know when navigation starts, ends, or fails. 93 | 94 | In simple terms, an Observable is a powerful way to handle sequences of events or data over time in a manageable way. You subscribe to listen, receive values/errors, and can unsubscribe when you're done. 95 | -------------------------------------------------------------------------------- /docs/observer.md: -------------------------------------------------------------------------------- 1 | An **Observer** is the **consumer** of the values delivered by an Observable. It's the "thing" that listens to the stream and knows what to do when a new value, an error, or a completion signal arrives. 2 | 3 | Think of it like this: 4 | 5 | - **Observable:** The radio station broadcasting music (the stream). 6 | - **Observer:** Your radio receiver at home, which _listens_ to the broadcast and plays the music through its speakers (consumes the stream). 7 | 8 | ### Structure of an Observer: 9 | 10 | An Observer is typically an object literal (a plain JavaScript object) that can have up to three methods (or "callbacks"): 11 | 12 | 1. **`next(value)`:** This method is called whenever the Observable emits a new value. This is where you put the code to handle the actual data you receive. You might update a variable, display something on the screen, etc. This can be called zero, one, or multiple times. 13 | 2. **`error(err)`:** This method is called if the Observable fails or encounters an error. The error object is passed as an argument. Once `error()` is called, the Observable stops, and neither `next()` nor `complete()` will be called anymore. This is where you'd handle error conditions, maybe show an error message to the user. 14 | 3. **`complete()`:** This method is called when the Observable successfully finishes emitting all its values and won't emit anything further. After `complete()` is called, `next()` and `error()` will not be called. This is useful for knowing when a stream has finished gracefully (e.g., after an HTTP request successfully returns its response). 15 | 16 | **How it connects to Observables:** 17 | 18 | You connect an Observer to an Observable using the Observable's `.subscribe()` method. You pass the Observer object to `.subscribe()`. 19 | 20 | **Real-World Analogy (Continuing the food delivery):** 21 | 22 | You are the **Observer** watching the delivery app. 23 | 24 | - **`next(update)`:** Your action when you see a status update like "Preparing" or "Out for delivery". You might just read it (`console.log(update)`), or maybe you feel relieved (`this.status = update`). 25 | - **`error(problem)`:** Your action when you see "Order Cancelled by Restaurant". You might feel annoyed (`console.error(problem)`) and decide to order from somewhere else (`this.showError('Order failed: ' + problem)`). 26 | - **`complete()`:** Your action when you see "Delivered". You might think, "Great, food's here!" (`console.log('Food has arrived!')`). 27 | 28 | ### Code Example (using the previous `HttpClient` example): 29 | 30 | In the `MyComponentComponent` example from before, the object we passed to `this.dataSubscription = this.dataService.getItems().subscribe(...)` _is_ the Observer: 31 | 32 | ```typescript 33 | // ... inside ngOnInit() of MyComponentComponent ... 34 | 35 | this.dataSubscription = this.dataService.getItems().subscribe( 36 | // This object is the Observer 37 | { 38 | // Handler for new data values 39 | next: (data) => { 40 | this.items = data; 41 | console.log("Data received:", data); 42 | }, 43 | // Handler for errors 44 | error: (error) => { 45 | this.errorMsg = "Failed to load items."; 46 | console.error("Error fetching items:", error); 47 | }, 48 | // Handler for completion 49 | complete: () => { 50 | console.log("Finished fetching items."); 51 | }, 52 | } 53 | ); 54 | ``` 55 | 56 | **Simplified Syntax:** 57 | 58 | You don't always need to provide all three methods. RxJS allows you to pass callback functions directly to `subscribe()`: 59 | 60 | - **Just `next`:** 61 | ```typescript 62 | myObservable.subscribe((data) => console.log(data)); 63 | ``` 64 | - **`next` and `error`:** 65 | ```typescript 66 | myObservable.subscribe( 67 | (data) => console.log(data), // next handler 68 | (err) => console.error(err) // error handler 69 | ); 70 | ``` 71 | - **`next`, `error`, and `complete`:** 72 | ```typescript 73 | myObservable.subscribe( 74 | (data) => console.log(data), // next handler 75 | (err) => console.error(err), // error handler 76 | () => console.log("Done!") // complete handler 77 | ); 78 | ``` 79 | 80 | So, in short: The **Observer** is the set of callbacks (next, error, complete) that you provide to the `subscribe()` method to react to the values and notifications emitted by an **Observable**. 81 | -------------------------------------------------------------------------------- /docs/promise-vs-observable.md: -------------------------------------------------------------------------------- 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. 55 | -------------------------------------------------------------------------------- /docs/stylesheets/extra.css: -------------------------------------------------------------------------------- 1 | :root > * { 2 | --md-primary-fg-color: #c2185b; 3 | } -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: RxJS Angular interview guide 3 | site_description: 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. 4 | site_author: Ankit Sharma 5 | 6 | # Repository 7 | repo_name: "RxJS Angular interview guide" 8 | repo_url: "https://github.com/AnkitSharma-007/rxjs-angular-interview-guide" 9 | 10 | # Navigation 11 | nav: 12 | - "Home": 13 | - index.md 14 | - "Overview": 15 | - "Observable": observable.md 16 | - "Observer": observer.md 17 | - "Cold Observables": cold-observables.md 18 | - "Hot Observables": hot-observables.md 19 | - "Promise Vs Observable": promise-vs-observable.md 20 | - "Async pipe": async-pipe.md 21 | - "Operators": 22 | - "RxJS operators": Operators/RxJS-operators.md 23 | - "Combination": 24 | - "combineLatest": Operators/Combination/combineLatest.md 25 | - "forkJoin": Operators/Combination/forkJoin.md 26 | - "zip": Operators/Combination/zip.md 27 | - "withLatestFrom": Operators/Combination/withLatestFrom.md 28 | - "Creation": 29 | - "empty VS never": Operators/Creation/empty-never.md 30 | - "from": Operators/Creation/from.md 31 | - "interval": Operators/Creation/interval.md 32 | - "of": Operators/Creation/of.md 33 | - "timer": Operators/Creation/timer.md 34 | - "Error Handeling": 35 | - "catchError": Operators/Error/catchError.md 36 | - "retry": Operators/Error/retry.md 37 | - "retryWhen": Operators/Error/retryWhen.md 38 | - "Filtering": 39 | - "debounceTime": Operators/Filtering/debounceTime.md 40 | - "distinctUntilChanged": Operators/Filtering/distinctUntilChanged.md 41 | - "filter": Operators/Filtering/filter.md 42 | - "find": Operators/Filtering/find.md 43 | - "first": Operators/Filtering/first.md 44 | - "last": Operators/Filtering/last.md 45 | - "skip": Operators/Filtering/skip.md 46 | - "take": Operators/Filtering/take.md 47 | - "takeUntil": Operators/Filtering/takeUntil.md 48 | - "Multicasting": 49 | - "share": Operators/Multicasting/share.md 50 | - "shareReplay": Operators/Multicasting/shareReplay.md 51 | - "Subjects": 52 | - "subject": Operators/Subjects/subject.md 53 | - "behaviorSubject": Operators/Subjects/behaviorSubject.md 54 | - "replaySubject": Operators/Subjects/replaySubject.md 55 | - "Subject Vs BehaviorSubject Vs ReplaySubject": Operators/Subjects/subject-behaviorSubject-replaySubject.md 56 | - "Transformation": 57 | - "concatMap": Operators/Transformation/concatMap.md 58 | - "exhaustMap": Operators/Transformation/exhaustMap.md 59 | - "map": Operators/Transformation/map.md 60 | - "mergeMap": Operators/Transformation/mergeMap.md 61 | - "switchMap": Operators/Transformation/switchMap.md 62 | - "switchMap Vs mergeMap Vs concatMap": Operators/Transformation/switchMap-mergeMap-concatMap.md 63 | - "Utility": 64 | - "delay": Operators/Utility/delay.md 65 | - "finalize": Operators/Utility/finalize.md 66 | - "tap": Operators/Utility/tap.md 67 | - "timeout": Operators/Utility/timeout.md 68 | 69 | # Configuration 70 | theme: 71 | name: material 72 | language: "en" 73 | font: 74 | text: Roboto 75 | code: Roboto Mono 76 | logo: assets/Rx_Logo.png 77 | favicon: assets/favicon.ico 78 | palette: 79 | # Palette toggle for light mode 80 | - scheme: default 81 | toggle: 82 | icon: material/weather-night 83 | name: Switch to dark mode 84 | 85 | # Palette toggle for dark mode 86 | - scheme: slate 87 | toggle: 88 | icon: material/weather-sunny 89 | name: Switch to system preference 90 | features: 91 | - navigation.instant 92 | - navigation.instant.progress 93 | - navigation.footer 94 | - navigation.indexes 95 | - navigation.tracking 96 | 97 | extra: 98 | social: 99 | - icon: fontawesome/brands/linkedin 100 | link: https://www.linkedin.com/in/ankitsharma-007 101 | - icon: fontawesome/brands/square-x-twitter 102 | link: https://twitter.com/ankitsharma_007 103 | 104 | extra_css: 105 | - stylesheets/extra.css 106 | 107 | # Extensions 108 | markdown_extensions: 109 | - attr_list 110 | - codehilite: 111 | guess_lang: false 112 | - footnotes 113 | - toc: 114 | permalink: "#" 115 | - pymdownx.betterem 116 | - pymdownx.superfences 117 | - pymdownx.tabbed: 118 | alternate_style: true 119 | - pymdownx.details 120 | --------------------------------------------------------------------------------