Angular 8 How to get value from observable in ngOnInit and how do they behave

10,090

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:

  1. 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.
  2. The code execution continues and prints Test: 1.2 with undefined as result, because the previous subscription didn't fire and so didn't set a value for this.test1.
  3. 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 request first() is redundant, because the Observable will be completed at the latest when the http request runs into a timeout.

Solution 2

  1. This is due to the asynchronous nature of JavaScript, and the returning of observables are asynchronous.

  2. Yes, you are awaiting for the response.

  3. 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 ---
    }
  );
} 
Share:
10,090

Related videos on Youtube

WastedFreeTime
Author by

WastedFreeTime

Nothing to say about me.

Updated on June 04, 2022

Comments

  • WastedFreeTime
    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: {…} }
    


    1. Why does the .2 gets logged before .1 ?
    2. Why do the first cycles return undef? (My thougt: The Api takes to long to deliver the response?!)
    3. 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
      Michelangelo over 4 years
      Your 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
    WastedFreeTime over 4 years
    Thank 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
    iY1NQ over 4 years
    The get method of the HttpClient accepts an generic type T where you can specify the return type of the response and so the type of the resulting observable.