When we use async pipe in the html, we use the syntax "Products$ | async as products".
What if I want to use the same products in ts file? Can this be done?
When we use async pipe in the html, we use the syntax "Products$ | async as products".
What if I want to use the same products in ts file? Can this be done?
In simple terms, we can use async pipe when the object Products$ is observable array, if you want to you use in ts file, you can subscribe and get the data as shown below
Products$.subscribe((products) => {
console.log(Products);
});
After extensive exploration, I have found the solution, and I hope it addresses all your queries.
There is no concise answer to your question, but I will do my best to explain everything. If you encounter any difficulties in understanding, feel free to search for more information and then ask me.
Let's consider it as a product.service file, which can have a array of either an Observable<T>, Subscribable<T>, or Promise<T> that's coming from an API Endpoint. Our objective is to resolve it and retrieve its value in our '.ts' file using a async pipe so we can save ourselves everytime from unsubscribing manually.
To make it clear, I am also mentioning type annotations for the values.
Here's our product.service.ts file, which serves as a part of Angular's Dependency Injection system. So we can making them available for use throughout the application.
import { Injectable } from '@angular/core';
import { Observable, shareReplay } from 'rxjs';
import { ProductsList } from '../products.interface';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root',
})
export class ProductsService {
constructor(private http: HttpClient) {}
// You can also use this.
// getProducts$: Observable<ProductsList[]> = this.http.get<ProductsList[]>('/api/rooms').
getRooms$: Observable<ProductsList[]> = this.http.get<ProductsList[]>('/api/products').pipe(shareReplay(1));
}
I attempted to implement some logic using the AsyncPipe but it returns a null value. This approach is not recommended, and I'll explain why shortly; from
import { AsyncPipe } from '@angular/common';
Here's the implementation in our app.component.ts Component.
import {
OnInit,
OnDestroy,
AfterViewInit,
} from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ProductsList } from './products/products.interface';
import { ProductsService } from './products/services/products.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
products$!: ProductsList[] | null;
private destroy$: Subject<void> = new Subject<void>();
constructor(
private productsService: ProductsService,
) {
this.productsService.getProducts$
.pipe(takeUntil(this.destroy$))
.subscribe((products: productsList[]) => {
this.products$ = products;
console.log(this.products$); // Correct Value
});
// console.log(this.products$); // Undefined Value
}
anyOtherFunction(): void {
// console.log(this.products$); // Correct Value
}
ngOnInit(): void {
// console.log(this.products$); // Undefined Value
}
ngAfterViewInit(): void {
// console.log(this.products$); // Undefined Value
}
ngOnDestroy(): void {
// Automatically unsubscribe when the component is destroyed
this.destroy$.next();
this.destroy$.complete();
}
}
Here you can see, this.products$ is undefined in every life cycle hook because life cycle hooks called synchronously in angular and this.products$ is async. So to get this.products$'s value in any
life cycle we have to subscribe it again; like
this.productsService.getProducts$
.pipe(takeUntil(this.destroy$))
.subscribe((products: productsList[]) => {
this.products$ = products;
console.log(this.products$); // Correct Value
});
In this scenario, we've opted not to use Angular's AsyncPipe, and instead, we manage subscriptions manually. This implies that we also need to handle manual unsubscription.
However, there's a crucial difference. We no longer have to perform manual unsubscription; it now happens automatically when the component is destroyed. We can add multiple subscribers as needed, and they will be unsubscribed automatically.
And don't forget to add ngOnDestroy, see the app.component.ts file
ngOnDestroy(): void {
// Automatically unsubscribe when the component is destroyed
this.destroy$.next();
this.destroy$.complete();
}
We achieve this by using a destroy$ subject to manage the component's lifecycle, automatically handling subscription cleanup upon component destruction.
During the ngOnInit lifecycle hook, we subscribe to the getProducts$ observable using takeUntil. This subscription is designed to automatically unsubscribe when the destroy$ subject emits.
As data arrives, it's stored in the products property, making it readily available for use in your component's logic and templates. But not inside life cycles.
When the component is destroyed, the ngOnDestroy hook finalizes the destroy$ subject, ensuring that any pending subscriptions are correctly handled.
This approach provides a similar level of subscription management as the async, albeit with some manual configuration. Nonetheless, it effectively prevents memory leaks and guarantees proper subscription cleanup upon component destruction.
We can have multiple observables to subscribe
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Observable, Subject, merge } from 'rxjs';
import { ProductsList } from './products/products.interface';
import { ProductsService } from './products/services/products.service';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit, OnDestroy {
products: ProductsList[] | null = null;
otherData: any; // You can have multiple data properties
private destroy$: Subject<void> = new Subject<void>();
constructor(private productsService: ProductsService, private otherService: OtherService) {}
ngOnInit(): void {
// Combine multiple observables using merge
const products$ = this.productsService.getProducts$.pipe(takeUntil(this.destroy$));
const otherData$ = this.otherService.getOtherData$.pipe(takeUntil(this.destroy$));
// Merge the observables into a single subscription
const combined$ = merge(products$, otherData$);
// Subscribe to the combined observable
combined$.subscribe((data) => {
if (data instanceof ProductsList) {
this.products = data;
} else {
this.otherData = data;
}
});
}
ngOnDestroy(): void {
// Automatically unsubscribe when the component is destroyed
this.destroy$.next();
this.destroy$.complete();
}
}
we use the merge operator to combine multiple observables (products$ and otherData$) into a single observable (combined$). Then, we subscribe to combined$ and handle different types of data as needed.
OR you can go with recommended way Using Async Pipe given by angular; HTML file, you can use the async pipe to handle the asynchronous data binding.
<ng-container *ngIf="products$ | async as products"> {...} </ng-container>
Incase you are like me and want to try diff things, then you can use the first approach
Create a new variable products in ts file. When you create Products$, right side is an expression. At there, pipe like below.
.pipe(tap(someproducts => this.products = someproducts))
Need side effect. So, this way, when template async made subscription, your variable products will be initialized.
If you subscribed products$ in ts file again, you will get two subscriptions or two backend calls