Updating variables after making a GET API call in Angular5 using HttpClient

I am facing an issue with a service (referred to as MySharedService) that is utilized by multiple components. Within MySharedService, I invoke another service responsible for making API calls. The JavaScript object in MySharedService is only assigned after the GET call is completed.

The challenge lies in my components relying on this JavaScript object to configure their values within their constructors. How can I ensure that their values are set when the JavaScript object might still be undefined due to the incomplete API call? Here's an example:

ApiService

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
/* Other imports */

@Injectable()
export class ApiService {

    constructor(private http: HttpClient) { }

    getTestData(): Observable<MyDataInterface> {
        const API_URL = 'some_url_here';
        return this.http.get<MyDataInterface>(API_URL, { observe: 'response' });
    }
}

MySharedService

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
/* Other imports */

@Injectable()
export class MySharedService {

    myData: MyDataInterface;

    constructor(private apiServie: ApiService) {
        this.apiService.getTestData().subscribe((response) => {
            this.myData = { ...response.body };
            console.log(this.myData);
        });
    }
}

TestComponent

import { Component, OnInit } from '@angular/core';
import { MySharedService } from '../../../services/myshared.service';
/* Other imports */    

@Component({
    selector: 'app-test',
    templateUrl: './test.component.html',
    styleUrls: ['./test.component.css']
})
export class TestComponent implements OnInit {

    name: string;
    /* Other variables here. */

    constructor(private mySharedService: MySharedService) {
        // Error occurs here if myData has not been populated yet.
        this.name = this.mySharedService.myData.name;
    }
}

The dilemma arises within my components when trying to access data from the myData object before it is fully populated (the console.log() eventually displays the data after a short delay). What would be the best approach to retrieve these values? I aim to make a single call to the REST service, save the object within MySharedService, and allow all components to utilize that object.

Answer №1

If you want to optimize your Angular application, consider implementing a BehaviorSubject

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
/* Other imports */

@Injectable()
export class MyCustomService {

    myData: BehaviorSubject<MyDataInterface> = new BehaviorSubject(null);

    constructor(private apiServie: ApiService) {
        this.apiService.getInitialData().subscribe((response) => {
            this.myData.next({ ...response.body });
            console.log(this.myData.getValue());
        });
    }
}

Avoid subscribing in component constructors and utilize the ngOnInit lifecycle hook instead.

export class DataComponent implements OnInit {

    name: string;
    /* Other variables here. */

    constructor(private mySharedService: MyCustomService) {
    }

    ngOnInit() {
        this.mySharedService.myData.subscribe((data: MyDataInterface) => { 
            if (data)
                this.name = data.name;
        });
    }
}

By using a BehaviorSubject, you can make a single network request and store the data for all subscribed components.

Why choose a BehaviorSubject over an Observable? Here's why:

  • When you subscribe, BehaviorSubject provides the most recent value whereas a regular observable only triggers on new updates.

  • To access the last value without subscribing, you can use the getValue() method with a BehaviorSubject.

Answer №2

When working with your TestComponent, make sure to subscribe to the service in the following manner and also trigger it within the ngOnInit event:

ngOnInit() {

  if (this.mySharedService.myData) {
    this.name = this.mySharedService.myData.name;
  } else {
    this.apiService.getTestData().subscribe(
      data => {
        // Handling success case
        this.mySharedService.myData = data.body.myData;
        this.name = this.mySharedService.myData.name;
      },
      err => {
        // Handling error case
      });
  }

}

Answer №3

Dealing with nulls/undefined from any pub/sub source has become a standard practice for me.

In my Shared Service:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
/* Other imports */

@Injectable()
export class MySharedService {

public message = new Subject<any>();

myData: MyDataInterface;

constructor(private apiServie: ApiService) {
    this.apiService.getTestData().subscribe((response) => {
        if(response){
           this.myData = { ...response.body };
           this.sendUpdate(this.myData);
           console.log(this.myData);
        } else {
            console.log("Handling in the else block");
        }
    });


    sendUpdate(message) {
        this.message.next(message);
    }

    getUpdate(): Observable<any> {
    return this.message.asObservable();
    }

    clearUpdate() {
        this.message.next();
    }

}

In Test Component:

import { Component, OnInit } from '@angular/core';
import { MySharedService } from '../../../services/myshared.service';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/takeWhile';
/* Other imports */    

@Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.css']
})
export class TestComponent implements OnInit, OnDestroy {

subscription: Subscription;
private alive: boolean = true;
name: string;
/* Other variables here. */

constructor(private mySharedService: MySharedService) {
    
    this.subscription = this.mySharedService.getUpdate().takeWhile(() => this.alive).subscribe(message => {
            if (message) {
               this.name = message;
            }
        });
    this.name = this.mySharedService.myData.name;
   }
}
   
    ngOnDestroy() {
    this.updateService.clearUpdate();
    this.subscription.unsubscribe();
    this.alive = false;
}

I believe that the edits I made demonstrate the use of modern Angular service with Rxjs subscription functionality. In the past year, I have alternated between utilizing stores such as Flux/Redux/Mobx to pass resolved data throughout the application and implementing a pub/sub pattern to receive updates from services when they have the required data. Handling both scenarios where there is data and when there isn't is crucial, similar to Angular's pre-populated "...loading" text in components.

Remember to properly clean up observables using ngOnDestroy to avoid unintended behavior. Good luck!

Answer №4

Give this a try and let me know what you think.

DataSharingService

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
/* Additional imports */

@Injectable()
export class DataSharingService {

    sharedData: SharedDataInterface;

    constructor(public apiConnectionService: ApiConnectionService) {
    }

    fetchData() {
       return this.apiConnectionService.fetchData();
    }

}

DisplayComponent

import { Component, OnInit } from '@angular/core';
import { DataSharingService } from '../../../services/datasharing.service';
/* Additional imports */    

@Component({
    selector: 'app-display',
    templateUrl: './display.component.html',
    styleUrls: ['./display.component.css']
})
export class DisplayComponent implements OnInit {

    displayInfo: string;
    /* Any other variables needed. */

    constructor(private dataSharingService: DataSharingService) {
        this.dataSharingService.fetchData().subscribe((response: any) => {
            this.displayInfo = response.body; 
     });
    }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What methods can be employed to ensure that external stylesheets are properly loaded within the shadow DOM?

I created a new CSS utility for responsive design and built a documentation site using Angular 16 to showcase its features. To demonstrate the responsiveness, I developed a component with a resizable <iframe> where I embed the demonstration component ...

What is the best method for transitioning to a new page in React Native using Ignite Bowser?

Recently I ventured into the world of React Native with Ignite Bowser. My current project involves building a React Native app using Ignite Bowser. At the start of my app, there's a welcoming screen that pops up. It features a 'continue' bu ...

Why aren't updates to Angular template in node_modules showing up?

I have been attempting to make modifications to the HTML within my node_modules folder, but it seems that the changes are not being reflected. Is there a restriction on updating files within the node_modules directory? Within my project, I am importing a ...

Enable Parse5's case sensitivity

Recently, I've attempted to parse Angular Templates into AST using the powerful parse5 library. It seemed like the perfect solution, until I encountered an issue - while parsing HTML, it appears that the library transforms everything to lowercase. Fo ...

Determining TypeScript function attributes through inference

I am working on creating a customized form generator that is strongly typed, and I need to configure it with options based on the model. I'm wondering if it's possible to determine the return type of a function from its arguments in TypeScript, a ...

Creating a customizable stepper component that can be easily reused with Angular 6 Material

With Angular material stepper, my objective is to customize steps for reusing the stepper component. I am dynamically loading steps, so depending on the requirement, I need to load the stepper in different components. The scenarios I have are as follows: ...

The Angular Tooltip feature is unable to interpret the characters "' '" or "' '"

Let me explain the scenario: I am receiving a list of objects from my back-end service, which I then break apart using ngFor and display accordingly. Each item in the list has an associated toolTip that provides additional information. The info for each i ...

When selecting a different file after initially choosing one, the Javascript file upload event will return e.target as null

Currently, I have implemented file uploading using <input>. However, when attempting to change the file after already selecting one, the website crashes and states that event.target is null. <Button label="Upload S3 File"> <input ...

Tips for incorporating a fresh variant into the default typography of MUI using TypeScript

While following the official MUI instructions here, a question arose. To customize the primary color in the file newTheme.ts and add a new variant type post: import { createTheme } from "@mui/material"; const newTheme = createTheme({ palette ...

Developing React component libraries with TypeScript compared to Babel compiler

Currently, I'm utilizing the babel compiler for compiling my React component libraries. The initial choice was influenced by Create React App's use of the same compiler. However, I've encountered challenges with using babel for creating libr ...

Having trouble accessing data through Angular's subscription?

UPDATE How I accomplished this task is detailed below (click on the link to view my answer): I am currently working on developing an APIService in Angular 7. My approach involves using subscribe to add data to an array that seems inaccessible through anot ...

Every day, I challenge myself to build my skills in react by completing various tasks. Currently, I am facing a particular task that has me stumped. Is there anyone out there who could offer

Objective:- Input: Ask user to enter a number On change: Calculate the square of the number entered by the user Display each calculation as a list in the Document Object Model (DOM) in real-time If Backspace is pressed: Delete the last calculated resul ...

What is the method for determining the type in this component configuration?

When working with a custom hook from React DnD, I encounter an issue where TypeScript restricts my access to the property of an item object in the drop function due to its unknown type. function MyComponent(props){ const [{item}, drop] = useDrop({ ...

Understanding the limitations of function overloading in Typescript

Many inquiries revolve around the workings of function overloading in Typescript, such as this discussion on Stack Overflow. However, one question that seems to be missing is 'why does it operate in this particular manner?' The current implementa ...

Monitoring changes to @Input in Angular2 child components

I am facing an issue with a parent and child component interaction. The parent component has an index value that is passed to the child component using an @Input property. This index value keeps changing in the parent component, and I want to trigger a fun ...

Angular2 Dropdown not updating with values from API

Here is the structure of my project flow: import_product.html <div class="row custom_row"> <div class="col-md-2">Additional Duty: </div> <div class="col-md-2"> < ...

After compilation, what happens to the AngularJS typescript files?

After utilizing AngularJS and TypeScript in Visual Studio 2015, I successfully developed a web application. Is there a way to include the .js files generated during compilation automatically into the project? Will I need to remove the .ts files bef ...

Using 2 mat-paginator components to control pagination on a single data source

I have implemented a mat-table that can display up to 100 entries per page. Initially, I had one mat-paginator at the bottom which was functioning correctly. However, I have now been tasked with adding another paginator at the top of the table as well, so ...

Utilizing Provide/Inject in Vue 3 Typescript to retrieve a method within a component, encountering the possibility of an 'undefined' Error Object

Having trouble calling the loginWithRedirect function within the header.vue component using inject feature in Vue 3 and Typescript. ERROR in src/components/global/HeaderMenu.vue:77:17 TS2339: Property 'loginWithRedirect' does not exist on type ...

What is the best way to test TypeScript optional parameter in Jasmine?

I am currently revising a TypeScript function to include an optional parameter with a default value. This function is a crucial operation, and it is utilized by several high-level operations. Existing calls to the function do not include the new parameter ...