-3

[ Those who vote for close : I don't want to put everything inside ngOnit - because I told you : I need to reuse the API response and model array in many functions, so I need to write something so that I can reuse

You know, I could actually solve each problem just calling everything inside subscribe or subscribe again and again in each function.

I already tried other SO question , most of them are putting all inside ngOnIt . I want to call only necessary functions there, so don't mess that place please

Please help me to write a function so that I can reuse my API response or I can reuse the model that was Initialized by API response this.menu = data;]

I want to print a menu from my API response.

Moreover, I need to use the response multiple time in multiple functions, but I am getting null value when I am out of my subscribe block

Here is my code:

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);
  }
}

Here is output :

enter image description here

See from printMenu() function all of my response is null. But why? I did subscribe and saved the value before.

So how can I save a value from API response permanently?

ruth
  • 29,535
  • 4
  • 30
  • 57
  • Does this answer your question? [How do I return the response from an Observable/http/async call in angular?](https://stackoverflow.com/questions/43055706/how-do-i-return-the-response-from-an-observable-http-async-call-in-angular) – R. Richards Aug 17 '20 at 15:43
  • Also tell me should I use subscribe or should I use async-await –  Aug 17 '20 at 15:43
  • Actually you can try a demo. Write a function and in that you do a console log first, then write a subscribe and inside subscribe do console log, then outside subscribe one more log. Then observe the sequence. Mostly you will get your answer. In typescript anything which is outside subscribe will continue its execution. – Ankit Garg Aug 17 '20 at 15:44
  • 1
    Does this answer your question? [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – ruth Aug 17 '20 at 15:54
  • No that doesnt @MichaelD –  Aug 17 '20 at 16:01
  • No that doesnt @R. Richards –  Aug 17 '20 at 16:01
  • 2
    I didn’t downvote, however, I understand why this is downvoted. Probably because your example isn’t actually a real world implementation - plus, you don’t seem to grasp the async concept yet. So even though your question might sound valid, it shows to many that you haven’t bothered to actually look up how scenarios like this are done in actual applications. – MikeOne Aug 17 '20 at 16:52
  • Try making ```this.menu``` a behaviour subject and subscribe to that wherever you want. This will not clear any data unless you manually make it null. – Anglesvar Aug 17 '20 at 20:09
  • @AnglesvarCheenu ok can you show me a simple example ? from anywhere –  Aug 18 '20 at 03:22
  • @AnkitGarg I did a print outside subscribe, its gone –  Aug 18 '20 at 03:23

2 Answers2

1

(The only reason I am posting this solution because : In future a beginner like myself doesn't have to bang their head against wall on daily basis [metaphorically] or getting sick of observable losing more than 2 weeks of project finding it

2 thing I want to share with you also - you will understand it only if you are a beginner [ do not comment on it - I request you - I have a right to express my feelings on my question - Please don't judge me]

1. pros :

most of them didn't understand what I mean. I believe they actually know this answers, but it seems they didn't understand what I wanted, only some of them tried to give me suggestion (I thanks them for that) but most of them didn't care to understand or didn't even bother to ask anything to understand. So beginners : you just have to find a lucky day when people actually understand what you are asking

2. Docs

In shot - a very big joke (for beginners)

In long - [in my case - angular] docs won't help you unless you haven't get a certain level of programming knowledge, Like I never used observable before or ngOnit in this way , but someone pointed me how it works. Still I don't know most of observable or ngOnit but now I know 4 or 5 things about it.

So my suggestion to beginners : Leave docs (if needed or if u feed it difficult to understand) and goto youtube tutorials)

Reason for problem :

Remeber my ngOnIt?

ngOnInit(): void {

    //line 1

    this.getMenu();

    //line 2

    this.printMenu();
    
}

ngOnIt will not execute line by line , if it did, I could actually correct value third time not null value (see value in screenshot). ngOnit executes all at a time (not line by line). So though it seems line 2 will be executed after line 1 , its not, because we are not in normal function, we are in ngOnit

Solution

When I got the solution, I was surprised (and was very happy to know that ) there was no coding error actually, I just have to call my printMenu() outside ngOnit after initializing the getMenu() on ngOnit

So how can I call outside? I need event

How can I trigger event? The most easiest way is to make a button with click event

so here is my .html :

<button (click)="this.printMenu()"> Lets 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: SubmenuService) { }

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

  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);
  }
}

See almost no change, I just had to remove printmenu from ngonit and then call it via an event (button or anything else you prefer)

[I also commented the first use and second use in my code]

enter image description here

  • 1
    You still don't seem to understand why the variable was undefined/null when it's printed outside the subscription. You could refer my answer to get a quick overview. – ruth Aug 18 '20 at 07:08
  • @MichaelD ok I will look into it, Thanks for your effort –  Aug 18 '20 at 07:50
0

From your answer, it appears you still fail to understand why the variable is undefined when you print outside the subscription. The this.menu is assigned asynchronously. You would know more about it if you took your time to read through this answer that I attached in the comment.

Essentially the variable isn't assigned any value when you print it inside the printMenu() function. Your answer still is erroneous. It assumes the variable is defined when you press the button, but it might not be the case. You can never assume an asynchronous variable is already defined.

Simulate error

You could simulate this error by artificially inducing the delay using the RxJS delay operator. I am inducing a delay of 10 seconds. So if you were to press the button within 10 seconds after the app starts, the printMenu() would still print null. In a real-world scenario, this delay might actually be incurred by the GetAllMenu() function, since the front-end has no control over it's response time.

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)             // <-- simulate delay of 10 seconds
    ).subscribe((data: MenuModel[]) => {
      this.menu = data;        
    });
  }

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

The correct solution would be to make all the subsequent statements asynchronous as well.

Solution: Using RxJS ReplaySubject

ReplaySubject is a multi-cast observable that holds/buffers the last emitted n values and emits it immediately upon subscription. Buffer size of 1 should be enough for this case. You would push the source notification from GetAllMenu() to this observable to which other dependents would subscribe to.

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);        // <-- push value to `ReplaySubject` observable
      console.log("first use : ");
      console.log(data);
    });
  }

  printMenu(){
    this.menu$.subscribe((data: MenuModel[]) => {     // <-- subscribe to the `ReplaySubject` observable
      console.log("third use : ");
      console.log(data);
    });
  }
}
ruth
  • 29,535
  • 4
  • 30
  • 57
  • about your first solution : look I said in question, if I need to subscribe again and again, I wouldn't post this question. and I could solve each error by subscribing again and again or writting all of my code under subscribe block, I want to subscribe once and use the result later. [ In short I don't want to make my code complex gathering everything in one function or in one subscribe block.] However, your second solution is very interesting. I would like to test it soon –  Aug 18 '20 at 08:03
  • unfortunately your second solution didn't work. now menu is showing blank output (instead of undefined or null) https://ibb.co/TTs29bz –  Aug 18 '20 at 08:44
  • 1
    @Frost: You need to calm down. The first subscription isn't similar to the second subscription. The first subscription is to a cold observable whereas the second is to a hot observable. When dealing with async variables, you either need to depend on observables (`subscribe`) or promises (`then`). If you're worried about multiple subscription streams, you could pipe in `take(1)` or `takeUntil()` to take care of the memory leaks. **You cannot make an async variable synchronous**. Please ignore the second solution. I've updated the answer. – ruth Aug 18 '20 at 08:58
  • It worked, so I accepted, https://ibb.co/d04RqV9 but there are 2 bugs 1) see screenshot : 2nd use is not printing, (it used to print before but i didn't need to print it btw - so I ignored 2) Put a new before ur replay subject otherwise code errors doesnt work 3) in 2nd use u have to this.menu$ since we dont have any this.menu , add $ sign, update your code. Thanks for all your effort –  Aug 18 '20 at 09:10
  • menu$: ReplaySubject = new ReplaySubject(1); add a new there in ur code –  Aug 18 '20 at 09:11
  • 1
    @Frost: I've updated the answer to remove the overlooked typos. – ruth Aug 18 '20 at 09:51