Typescript: Subscribed information mysteriously disappeared

[ Voting to avoid putting everything inside ngOnit because I need to reuse the API response and model array in multiple functions. Need a way to reuse without cluttering up ngOnInit.

I could simply call subscribe repeatedly in each function to solve the problem.

Tried other solutions on SO, but most suggest putting everything inside ngOnInit. I prefer to keep only necessary functions there.

Please assist in creating a function for reusing API responses or initialized models like this.menu = data;]

Attempting to display a menu from an API response.

Additionally, requiring the response to be used several times in different functions, facing null values outside of the subscribe block.

Code snippet:

import { Component, OnInit } from '@angular/core';

// import { LoginModel } from "../../models/login/login-model";
import { MenuModel } from "../../models/menu/menu-model";
import { SubmenuModel } from "../../models/submenu/submenu-model";
// import { AccessModel } from "../../models/access/access-model";

import { MenuService } from "../../services/menu/menu.service";
import { SubmenuService } from "../../services/submenu/submenu.service";

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

  menu: MenuModel[] = null;
  submenu: SubmenuModel[] = null;

  constructor(private menuService: MenuService, private submenuSerive: SubmenuService) { }

  ngOnInit(): void {
    this.getMenu();
    this.printMenu();
  }

  getMenu() {
    this.menuService.GetAllMenu().subscribe((data: MenuModel[]) => {
      this.menu = data;
      console.log("first use : ");
      console.log(data);

      console.log("second use : ");
      console.log(this.menu);
    })
  }

  printMenu(){
    console.log("third use : ");
    console.log(this.menu);
  }
}

Output :

https://i.stack.imgur.com/r7tMp.png

All responses are null from the printMenu() function. Why is this happening? I subscribed and stored the value previously.

How can I permanently retain a value from an API response?

Answer №1

(I am sharing this solution to help beginners who may struggle with similar issues in the future, so they don't have to waste time and effort trying to solve problems that others have already faced.

There are two key points I want to emphasize, especially for beginners [please refrain from commenting on them - everyone is entitled to express their feelings about their questions without judgment]:

1. pros:

Many people did not grasp what I was asking for. While some provided suggestions (which I appreciate), most didn't make an effort to understand or inquire further. So, beginners, be patient until you find someone who truly understands your question.

2. Docs

In short - the documentation can be a challenge for beginners.

My experience with Angular's documentation reveals that it may not be helpful unless you have a certain level of programming knowledge. Although I had never used `observable` or `ngOnInit` before in such a manner, someone guided me through it. I still have much to learn about `observable` and `ngOnInit`, but I now have a better understanding.

For beginners: If you find the documentation difficult to comprehend, consider turning to YouTube tutorials instead of relying solely on official docs.

Issue Explanation:

Remember my ngOnInit?

ngOnInit(): void {

    //line 1

    this.getMenu();

    //line 2

    this.printMenu();
    
}

`ngOnInit` does not execute line by line. If it did, I could have corrected the value earlier rather than ending up with a null value (as seen in the screenshot). `ngOnInit` executes all at once, not sequentially like in a regular function.

Solution

When I found the solution, I realized there was no coding error. I simply needed to call `printMenu()` outside of `ngOnInit` after initializing `getMenu()` within `ngOnInit`.

How do I call it outside? I need an event.

The easiest way to trigger an event is by creating a button with a click event.

Here is my .html:

<button (click)="this.printMenu()"> Let's test it </button>

.ts:

import { MenuService } from "../../services/menu/menu.service";
import { SubmenuService } from "../../services/submenu/submenu.service";

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

  menu: MenuModel[] = null;
  submenu: SubmenuModel[] = null;

  constructor(private menuService: MenuService, private submenuSerive: Sub
menuService) {
}

  ngOnInit(): void {
    this.getMenu();
  }

  getMenu() {
    this.menuService.GetAllMenu().subscribe((data: MenuModel[]) => {
      this.menu = data;

    })
  }

  printMenu(){
    console.log("third use : ");
    console.log(this.menu);
  }
}

With minimal changes, I removed `printmenu` from `ngonit` and called it using an event (such as a button click).

[I also added comments indicating "first use" and "second use" in my code]

https://i.stack.imgur.com/2RD02.png

Answer №2

Based on your response, it seems like there is still a misunderstanding about the reason why the variable remains undefined when you try to print it outside of the subscription. The assignment of this.menu happens asynchronously. To gain more insights into this issue, I recommend taking some time to go through the answer provided in the attached link: this.

The variable doesn't get assigned any value when printed within the printMenu() function. Therefore, your explanation is still incorrect as it assumes that the variable has been defined by the time you press the button, which may not always be the case. Remember, with asynchronous operations, you can never guarantee that a variable has already been defined.

To Replicate the Error

If you want to recreate this error intentionally, you can introduce an artificial delay using the RxJS delay operator. In my example, I have added a 10-second delay. So, if you click the button within the first 10 seconds after the application launches, the printMenu() function will still output null. In real-world situations, such delays might be caused by the processing time of the GetAllMenu() function over which the frontend has no control.

import { delay } from "rxjs/operators";

@Component({
  selector: 'app-admin-access',
  templateUrl: './admin-access.component.html',
  styleUrls: ['./admin-access.component.css']
})
export class AdminAccessComponent implements OnInit {
  menu: MenuModel[] = null;
  submenu: SubmenuModel[] = null;

  constructor(private menuService: MenuService, private submenuSerive: SubmenuService) { }

  ngOnInit(): void {
    this.getMenu();
  }

  getMenu() {
    this.menuService.GetAllMenu().pipe(
      delay(10000)             // <-- simulating a 10-second delay
    ).subscribe((data: MenuModel[]) => {
      this.menu = data;        
    });
  }

  printMenu() {
    console.log(this.menu);    // <-- will still show `null` if called within 10 seconds
  }
}

A better approach would be to make all subsequent statements asynchronous as well.

Solution: Leveraging RxJS ReplaySubject

ReplaySubject serves as a multi-cast observable that buffers and emits the last n emitted values immediately upon subscription. A buffer size of 1 should suffice for this scenario. You can push the source notification from GetAllMenu() into this observable where other components can subscribe to it.

import { ReplaySubject } from "rxjs";

@Component({
  selector: 'app-admin-access',
  templateUrl: './admin-access.component.html',
  styleUrls: ['./admin-access.component.css']
})
export class AdminAccessComponent implements OnInit {
  menu$: ReplaySubject<MenuModel[]> = new ReplaySubject<MenuModel[]>(1);
  submenu: SubmenuModel[] = null;

  constructor(private menuService: MenuService, private submenuSerive: SubmenuService) { }

  ngOnInit(): void {
    this.getMenu();
    this.printMenu();
  }

  getMenu() {
    this.menuService.GetAllMenu().subscribe((data: MenuModel[]) => {
      this.menu$.next(data);        // <-- pushing value to the `ReplaySubject` observable
      console.log("first use : ");
      console.log(data);
    });
  }

  printMenu(){
    this.menu$.subscribe((data: MenuModel[]) => {     // <-- subscribing to the `ReplaySubject` observable
      console.log("third use : ");
      console.log(data);
    });
  }
}

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 are the different methods to display information in Angular?

list.component.ts import { Component, OnInit } from '@angular/core'; import { StudentAPIService } from 'src/app/services/student-api.service'; import { StudentModel } from 'src/app/model/student'; @Component({ selector: &ap ...

Do I need to convert AngularJS to .ts for an Angular/AngularJS hybrid application?

Currently in the process of upgrading from AngularJS v1.25 to Angular 14 using the ng-Upgrade approach outlined on angular.io/guide/upgrade. Adding an extra challenge, our main page is built with ASP.NET MVC 5, and I am aiming to incorporate the Angular CL ...

The modal popup feature is dysfunctional within the hierarchical component structure of angular-bootstrap-md

In my project, there is a structured hierarchy of components that includes: Agent task-list (utilizing the shared task-list-table component) task-type (as a separate component) preview-task (a modal component) agent.component.html (where task-type, ta ...

Error: Unable to access the 'registerControl' property of the object due to a type mismatch

I'm struggling to set up new password and confirm password validation in Angular 4. As a novice in Angular, I've attempted various approaches but keep encountering the same error. Seeking guidance on where my mistake lies. Any help in resolving t ...

Having trouble figuring out how to display a tooltip using the show() method in @teamhive/ngx-tooltip?

I am looking for a way to toggle this tooltip on and off as I navigate with my mouse, especially because it is attached to nested elements. Although I can detect cursor movement for other purposes, I need a solution for controlling the tooltip display. Ac ...

Exploring the capabilities of the Angular 2 expression parser alongside the functionality of the

Is there a way to create an equivalent of the Angular 1.x ngInit directive in Angular 2? I am familiar with the ngOnInit hook, which is recommended for initialization code. The ngInit directive seems like a quick and declarative way to prototype or fix a ...

In Angular, use the ngFor directive to iterate through items in a collection and add a character to each item except

Currently, I am iterating through my data list and displaying it in the view using spans: <span *ngFor="let d of myData"> {{d.name}}, </span> As shown above, I am adding a comma ',' at the end of each item to ensure a coherent displ ...

Error message: "ReferenceError occurred while trying to access the Data Service in

As I embark on the journey of creating my very first MEAN stack application - an online cookbook, I have encountered a challenge in Angular. It seems like there is an issue between the service responsible for fetching recipe data from the API (RecipeDataSe ...

Implementing indexers in TypeScript to accommodate both string and numeric keys

Seeking to incorporate different types into existing code. In the code, there exists a transitionData object in which objects can be added by index as shown below: this.transitionData[id] = transition where id is a number and transition is of type Trans ...

Tips for effectively handling the data received from a webservice when logging into a system

My web service provides me with permissions from my user. The permissions are stored as an array in JSON format. I need to find a way to access and display this data in another function. {"StatusCode":0,"StatusMessage":"Authenticated Successfully", "Token ...

An issue arises when using enums in TypeScript

Let's analyze this demonstration. Initially, an enum is created as follows: enum myEnum { a = 'a', b = 'b' } The next step involves creating a similar enum but with the addition of one more numeric value! This alteration is c ...

The directive is failing to apply the active class to the host element

My goal is to develop a directive that enables page scrolling when a menu item is clicked and also adds an 'active' class when the page is scrolled. Initially, I managed to implement page scroll on menu click successfully. However, I encountered ...

Ways to access a nested route in Angular 4

My routing configuration is set up as depicted below: export const Approute: Routes = [ { path: '', component: DashboardComponent }, { path: 'add-course', component: AddCourseComponent }, { path: 'bui ...

Exploring Angular Components with Jasmine and Karma while integrating third-party tools such as ExcelJS

Currently tackling the challenge of writing tests for a project using ExcelJS. The project runs smoothly in both build and production environments, but when attempting to incorporate unit tests for certain components, I'm encountering issues with Test ...

The function RegisterOnChange is not a valid function

I utilized FormBuilder(fb) to create this form. this.serviceRecordForm = this.fb.group({ client: [], serviceRecordItem: this.fb.group({ productStatus: [''], password: [''], hasBackup: ...

Testing an asynchronous function in JavaScript can lead to the error message: "Have you neglected to utilize await?"

Here is an example of the code I am working with: public getUrl(url) { //returns URL ... } public getResponseFromURL(): container { let myStatus = 4; const abc = http.get(url, (respon) => const { statusCode } = respon; myStatus = statusCode ...

The hidden pop-up window from a particular tool is currently not being displayed

I attempted to upload my code onto Stackblitz in search of assistance with my dynamic modal not displaying. I am working on a function that I intend to be able to call from any component to create a popup where I can dynamically modify the header, body, an ...

Create an object using a combination of different promises

Creating an object from multiple promise results can be done in a few different ways. One common method is using Promise.all like so: const allPromises = await Promise.all(asyncResult1, asyncResult2); allPromises.then([result1, result2] => { return { ...

"Exploring the best way to open a new tab in Angular from a component

I am working on a simple Angular application with two components. My goal is to open one component in a new tab without moving any buttons between the components. Here is an overview of my application setup: Within my AppComponent.html file, there is a b ...

Obtain the coordinates of the pixel in an image on the canvas when a mouse

I am currently working on a project that involves using canvas. I have set a picture as the background of the canvas and would like to be able to get the original pixel point when clicking in the image area. In order to achieve this, I need to convert canv ...