Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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!

like image 926
mrsan22 Avatar asked Sep 23 '17 00:09

mrsan22


2 Answers

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

like image 88
Victor Avatar answered Oct 17 '22 21:10

Victor


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;
  }
}
like image 42
Arpp Avatar answered Oct 17 '22 22:10

Arpp