There is no need to convert the observable returned by Http's get() method into a promise. In most cases, the service can simply return the observable.
If we are fetching an array or a primitive type (i.e., string, number, boolean) from the server, we can simplify our controller logic by using the returned observable directly in our template, with the asyncPipe. This pipe will automatically subscribe to the observable (it also works with a promise) and it will return the most recent value that the observable has emitted. When a new value is emitted, the pipe marks the component to be checked for changes, hence the view will automatically update with the new value.
If we are fetching an object from the server, I'm not aware of any way to use asyncPipe, we could use the async pipe, in conjunction with the safe navigation operator as follows:
{{(objectData$ | async)?.name}}
But that looks complicated, and we'd have to repeat that for each object property we wanted to display.
Instead, I suggest we subscribe()
to the observable in the component and store the contained object into a component property. We then use the safe navigation operator (?.) or (as @Evan Plaice mentioned in a comment) NgIf in the template. If we don't use the safe navigation operator or NgIf, an error will be thrown when the template first tries to render, because the object is not yet populated with a value.
Note how the service below always returns an observable for each of the get methods.
service.ts
import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
import 'rxjs/add/operator/map'; // we need to import this now
@Injectable()
export class MyService {
constructor(private _http:Http) {}
getArrayDataObservable() {
return this._http.get('./data/array.json')
.map(data => data.json());
}
getPrimitiveDataObservable() {
return this._http.get('./data/primitive.txt')
.map(data => data.text()); // note .text() here
}
getObjectDataObservable() {
return this._http.get('./data/object.json')
.map(data => data.json());
}
}
app.ts
import {Component} from 'angular2/core';
import {MyService} from './my-service.service';
import {HTTP_PROVIDERS} from 'angular2/http';
@Component({
selector: 'my-app',
providers: [HTTP_PROVIDERS, MyService],
template: `
<div>array data using '| async':
<div *ngFor="#item of arrayData$ | async">{{item}}</div>
</div>
<div>primitive data using '| async': {{primitiveData$ | async}}</div>
<div>object data using ?.: {{objectData?.name}}</div>
<div *ngIf="objectData">object data using NgIf: {{objectData.name}}</div>`
})
export class AppComponent {
constructor(private _myService:MyService) { console.clear(); }
ngOnInit() {
this.arrayData$ = this._myService.getArrayDataObservable();
this.primitiveData$ = this._myService.getPrimitiveDataObservable();
this._myService.getObjectDataObservable()
.subscribe(data => this.objectData = data);
}
}
Note: I put "Observable" in the service method names – e.g., getArrayDataObervable()
– just to highlight that the method returns an Observable. Normally you won't put "Observable" in the name.
data/array.json
[ 1,2,3 ]
data/primitive.json
Greetings SO friends!
data/object.json
{ "name": "Mark" }
Output:
array data using '| async':
1
2
3
primitive data using '| async': Greetings SO friends!
object data using .?: Mark
object data using NgIf: Mark
Plunker
One drawback with using the async
pipe is that there is no mechanism to handle server errors in the component. I answered another question that explains how to catch such errors in the component, but we always need to use subscribe()
in this case.