3

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?

1
  • You can’t use the async in the ts, however, if you need to do some processing inside the component (while still async subscribing in the template), you can still map the products$ observable (without subscribing) in the component and do things with the stream. Commented Jul 24, 2022 at 15:56

3 Answers 3

1

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);
});
Sign up to request clarification or add additional context in comments.

3 Comments

Is there a way for me to use the one (here products) we define in the html template in the ts?
I don't think we can do anything like that
you will get two subscription! If you subscribe again!
1

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

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.