Angular 8 How to get value from observable in ngOnInit and how do they behave
Solution 1
The getLights
function returns an Observable
. The Observable
itself doesn't block the execution, it only holds the information if a result is available. To get notified when a result is available a subscription with a callback function will be registered on the observable by the subscribe
function. Within the callback the resulting data will be present.
As mentioned above the observable does not block the execution of your code, nor does the subscribe call. That means:
- A callback will be registered which will print
Test: 1.1
but isn't fired yet, because the http request didn't complete yet. So nothing is printed. - The code execution continues and prints
Test: 1.2
withundefined
as result, because the previous subscription didn't fire and so didn't set a value forthis.test1
. - Some time later the callback of the subscription is executed because a result arrived and so
Test: 1.1
will be printed with the result.
If you want to explicitly block execution after registering a subscription to retrieve the result first, you can do the following:
async ngOnInit(): void {
console.log("----- ngOnInit -----");
this.test1 = await this.ExperimentalService.getLights().toPromise();
console.log("Test: 1.2", this.test1); // --- 1.2 ---
}
Note: It's important that the
ngOnInt
itself is awaitable.
This will implicitly register a subscription which will be converted into a Promise. The Promise will hold the last value emitted, after the Observable has completed. Because Angular's Http-Client completes the returned Observable after it received a value, the resulting Promise will be resolved when the request was successful or rejected on an error.
You can turn any Observable into a Promise, but you must ensure that the Observable is completed at some point in the future or it will wait (block) forever. To ensure this, mostly a pipe is registered with the first()
operator:
async ngOnInit(): void {
console.log("----- ngOnInit -----");
this.test1 = this.ExperimentalService.getLights().pipe(first()).toPromise();
console.log("Test: 1.2", this.test1); // --- 1.2 ---
}
Note:
first()
[doc] will complete the Observeable when the first value is emitted and passes it through to the subscription. For a http requestfirst()
is redundant, because the Observable will be completed at the latest when the http request runs into a timeout.
Solution 2
This is due to the asynchronous nature of JavaScript, and the returning of observables are asynchronous.
Yes, you are awaiting for the response.
You can get the values by moving your subsequent logic within the
subscribe()
block.
This is how you should carry out your subscriptions:
ngOnInit(): void {
this.ExperimentalService.getLights().subscribe(
(data) => {
this.test1 = data;
console.log("Test: 1.1", this.test1); // --- 1.1 ---
console.log("Test: 1.2", this.test1); // --- 1.2 ---
}
);
}
Related videos on Youtube
Comments
-
WastedFreeTime almost 2 years
Hello Community,
i am trying to use the HttpClient Module to get some data from my api.
The api calls are defined in my service. I call them in Lifecyclehooks in my component but i struggle to understand the behavior of the returned observables and how i get the value of it in ngOnInit.I built a sample code which logs the returned data objects to understand their behavior. The service functions return the observable.
This is what my sample code looks like:My sample service:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { ConfigService } from '../config/config.service'; @Injectable( { providedIn: 'root' } ) export class ExperimentalService { constructor(private http: HttpClient, private ConfigService: ConfigService) {} private _apiRootUrl: string = this.ConfigService.get('api_root_url'); private _apiUser: string = this.ConfigService.get('api_user'); private _apiPath: string = this.ConfigService.get('api_path'); public getLights() { return this.http.get( `${this._apiRootUrl}/${this._apiUser}/${this._apiPath}` ); } }
My sample compenent:
import { Component } from '@angular/core'; import { ExperimentalService } from '../../services/experimental/experimental.service'; @Component({ selector: 'app-experimental', templateUrl: './experimental.component.html', styleUrls: ['./experimental.component.scss'] }) export class ExperimentalComponent { constructor(private ExperimentalService: ExperimentalService) { } private test1; private test2; private executed: boolean = false; ngOnInit(): void { console.log("----- ngOnInit -----"); this.ExperimentalService.getLights().subscribe( (data) => { this.test1 = data; console.log("Test: 1.1", this.test1); // --- 1.1 --- } ); console.log("Test: 1.2", this.test1); // --- 1.2 --- } ngDoCheck(): void { console.log("----- ngDoCheck -----"); if (this.executed === false) { // if: only to avoid endless loop this.ExperimentalService.getLights().subscribe( (data) => { this.test2 = data; console.log("Test: 2.1", this.test2); // --- 2.1 --- } ); console.log("Test: 2.2", this.test2); // --- 2.2 --- console.log("Test: 1.3", this.test1); //to check if test1 is coming after ngOnInit if (this.test2) { this.executed = true; } } } }
The console output:
----- ngOnInit ----- Test: 1.2 undefined ----- ngDoCheck ----- Test: 2.2 undefined Test: 1.3 undefined ----- ngDoCheck ----- Test: 2.2 undefined Test: 1.3 undefined Test: 1.1 Object { 1: {…}, 2: {…}, 3: {…} } ----- ngDoCheck ----- Test: 2.2 undefined Test: 1.3 Object { 1: {…}, 2: {…}, 3: {…} } Test: 2.1 Object { 1: {…}, 2: {…}, 3: {…} } ----- ngDoCheck ----- Test: 2.2 Object { 1: {…}, 2: {…}, 3: {…} } Test: 1.3 Object { 1: {…}, 2: {…}, 3: {…} } Test: 2.1 Object { 1: {…}, 2: {…}, 3: {…} }
- Why does the .2 gets logged before .1 ?
- Why do the first cycles return undef? (My thougt: The Api takes to long to deliver the response?!)
- How can i get the Value in ngOnInit? Is there a option to wait for the api response if my thougt of 2. is right?
Thank you, for every help!-
Michelangelo over 4 yearsYour data is not retrieved yet when you console log it. That data is retrieved async so your 1.2 test is already logged but data has not returned yet.
-
WastedFreeTime over 4 yearsThank you! That helped me a lot but how can i specify a datatype in there? As i did before i would normally write: .subscribe( (data: LightModel) => {} ); Because i call multiple asynchrone functions in ngOnInit and i want to pass the returned data of one function in a model
-
iY1NQ over 4 yearsThe
get
method of the HttpClient accepts an generic typeT
where you can specify the return type of the response and so the type of the resulting observable.