Reputation: 747
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 the
completionobserver
(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
Reputation: 33
Advantages:
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
Reputation: 301
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));
}
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.
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
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
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
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