mrsan22
mrsan22

Reputation: 747

Trying to use Async pipe instead of subscribe in my current Angular application

I am working on an Angular(v4) application. Like any other web app, this app also has HTTP requests and for each of these requests, I have a subscribe to get the data and show them on the UI. I have been reading about of use of async pipe and why it is better than subscribe. From what I have read, it surely has benefits over normal subscribe method and I am convinced that I should use it in my application but I am not sure how to structure my app with this aync pipe instead of subscribe. Let me post the structure of my application:

I have a service layer that issues the Http request to backend, something like:

some-service.component.ts

@Injectable()
export class SomeService {
 constructor(private http: HttpClient) {
}
 getSomething() {
    return this.http.get(`${this.baseUrl}/${'test'}`);
}
}

The above service provides an Observable to my component, where I usually subscribe to it as shown below:

some-component.ts

    @Component({
    selector: 'some-app',
    templateUrl: './some.component.html',
    styleUrls: []
})
export class SomeComponent {
 constructor(private someService: SomeService) {
    }
  getSomething() {
        this.someService
            .getSomething()
            .subscribe(
                // the first argument is a function which runs on success
                (data) => {
                  // DO SOME DATA TRANSFORMATION HERE
                                    },
                // the second argument is a function which runs on error
                (err: HttpErrorResponse) => {
                    this.exceptionService.errorResponse(err);
                },
                // the third argument is a function which runs on completion
                () => {
                      // MAKE SOME FUNCTION CALL
                                }
            );
    }
}

Above is a general structure as how I do a subscribe for a general HTTP response. I want to get rid of this subscribe and use async instead. However, my issue is, I do some data transformation upon receiving the data (as shown above) or sometime I make some function call from thecompletionobserver (as shown above) and with such operations, I am not sure as how I am going to these operations once switch to async.

For data transformation, I guess, I can do it in my service (not sure though) but how can I make function call as I do it now.

Any input would be highly appreciated. Also let me know if my question needs more clarifications. Thanks!

Upvotes: 5

Views: 10591

Answers (5)

Deepak-ranolia
Deepak-ranolia

Reputation: 33

Advantages:

  1. It will automatically subscribe && unsubscribe on component Init && Destroy. No use of subscribe() and unsubscribe() ever again :)
  2. You don't have to use ?(elvis operator)
  3. Less line of code.

NOTE: You will definitely like this, code is for reference purpose only. Please implement below code step by step to test with your API data.

// call your imports
@Component({
   selector: 'some-app',
   template: '
     <ng-container *ngIf="data$ | async as data">
         <input [value]="data.new" />
         <div *ngFor="let val of data">
            // some more todo things
         </div>
     </ng-container>
   '
})
export class SomeComponent {
     data$: Observable<data>;
}

Upvotes: 0

Arpp
Arpp

Reputation: 301

1. Data transformation upon receiving the data

When you work with observables, you can use a very common operator to transform the incoming data into something else : the map operator (documentation here). "The Map operator applies a function of your choosing to each item emitted by the source Observable, and returns an Observable that emits the results of these function applications."

some-service.component.ts (modified)

// Don't forget this, or it will cause errors
import 'rxjs/add/operator/map';

@Injectable()
export class SomeService {
  constructor(private http: HttpClient) {
}
getSomething() {
  return this.http.get(`${this.baseUrl}/${'test'}`)
    .map(data => transformData(data));
}

some-component.ts

@Component({
  selector: 'some-app',
  templateUrl: './some.component.html',
  styleUrls: []
})
export class SomeComponent {

  constructor(private someService: SomeService) { }

  getSomething() {
    this.someService
      .getSomething()
        .subscribe(
          // the first argument is a function which runs on success
          (data) => {

            // DO SOMETHING WITH DATA TRANSFORMED HERE
                                },
          // the second argument is a function which runs on error
          (err: HttpErrorResponse) => {

            this.exceptionService.errorResponse(err);

          },
          // the third argument is a function which runs on completion
          () => {

            // MAKE SOME FUNCTION CALL

          }
        );
  }
}

Note that you could also use map operator a second time in some-component.ts (after importing it). You could also decide to use it one time, only in some-component.ts. Or your could pass a function to this.someService.getSomething(myFunction) to transform the data. See the service implementation below.

// Don't forget this, or it will cause errors
import 'rxjs/add/operator/map';

@Injectable()
export class SomeService {
  constructor(private http: HttpClient) {
}
getSomething(myFunction) {
  return this.http.get(`${this.baseUrl}/${'test'}`)
    .map(data => myFunction(data));
}

2. Using async pipe instead of subscription

To use async pipe, store your Observable in a property.

import { Component, OnInit } from '@angular/core'

@Component({
  selector: 'some-app',
  templateUrl: './some.component.html',
  styleUrls: []
})
export class SomeComponent implements OnInit {

  myObservable;

  constructor(private someService: SomeService) { }

  ngOnInit() {
    this.myObservable = this.getSomething();
  }

  getSomething() {
    return this.someService.getSomething();
  }
}

Then, apply the async pipe to your template like this (documentation here) :

<p>{{ myObservable | async }}</p>

Or, as said Optiq (when you deal with objects for exemple) :

<div *ngFor="let a of myObservable | async">
  <p>{{ a.whatever }}</p>
</div>

Note that you must not apply async pipe to a function.

3. Do something when the data has been received

Finally, you could handle the case when data has been received. See below :

import { Component, OnInit } from '@angular/core'

@Component({
  selector: 'some-app',
  templateUrl: './some.component.html',
  styleUrls: []
})
export class SomeComponent implements OnInit {

  myObservable;

  constructor(private someService: SomeService) { }

  ngOnInit() {
    this.myObservable = this.getSomething();
  }

  getSomething() {
    const obs = this.someService.getSomething();
    obs.subscribe((data) => {
      // Handle success
    }, (err) => {
      // Handle error
    }, () => {
      // Make some function call
    })
    return obs;
  }
}

Upvotes: 3

Chatterji Neerugatti
Chatterji Neerugatti

Reputation: 399

I will answer based on your code in more generic way

foo.service.ts

@Injectable()

export class FooService {

    constructor(private _http:HttpClient){ }

    public getFoo():Observable<any> {
        return this._http.get(`${API_URL}`).map(r => r.json());
    }
}

In map operator you can do manipulations and return new state

foo.component.ts

Import rxjs finally operator to invoke method at final

import 'rxjs/add/operators/finally';


export class FooComponent {

    public bar$:Obervable<any>;

    constructor(private _foo:FooService){}

    getBar(): void {

        this.bar$ = this._foo.getFoo()
                             .finally(() => {

                               // INVOKE DESIRED METHOD

                             });

    }
}

foo.html

<div *ngIf="bar$ | async; let bar"> <h3> {{bar}} </h3> </div>

This will create div wrapper element and will provide visiblity hidden option to it, so there's no need for ?. 'Elvis' operator

You can also modify ngIf expression based on your requirement

Upvotes: 3

Joshua Craven
Joshua Craven

Reputation: 4565

1) You could create a custom pipe to do the data transformation (docs)

2) And you could use the catch() method to deal with error handling (docs)

Upvotes: 0

Optiq
Optiq

Reputation: 3202

the async pipe is applied in your template binding like this

<div *ngFor="let a of yourData | async">
    <p>{{a.whatever}}</p>
</div>

Incase you don't know a is whatever name you want to make up to call each array and yourData is the name of the variable in your class that you're iterating from.

I wouldn't neccessarily say this is "better" than using subscribe() because it's not an alternative way of doing the same thing. In fact the async pipe is what helps your binding handle subscriptions because when a component loads it may call the value of your variables before the data gets there which will give you a bunch of undefined errors or not seeing anything in your console.log() even though you see it binding in your template.

What headache(s) are you attempting to do away with by sniffing around the async pipe? It sounds like there's something else you need to learn that you don't know enough to know how to ask for yet and this is an attempt to discover it.

Upvotes: 1

Related Questions