How to call a service function in the app.component.ts file?

Hi all,

I have implemented the following service that gets information from capacitor storage.

export class StartupService {

  public myActiveEntityDesc: BehaviorSubject<any> = new BehaviorSubject<any[]>([]);
  public myActiveEntityID: BehaviorSubject<any> = new BehaviorSubject<any[]>([]);
  
  constructor() { }
  
  async getActiveEntity() {
    Plugins.Storage.get({key: 'activeEntityDesc'}).then((data) => {
      this.myActiveEntityDesc.next(data);
    });
    Plugins.Storage.get({key: 'activeEntityID'}).then((id_data) => {
      this.myActiveEntityID.next(id_data);
    });
  }
}

and when I use this in a separate page, it all works great and updates as this value changes. Here is the code for a separate page:

    this.startupService.getActiveEntity();
    this.startupService.myActiveEntityDesc.subscribe((data) => {
      this.retrievedEntityDesc = data;
    });
    this.startupService.myActiveEntityID.subscribe((id_data) => {
      this.retrievedEntityID = id_data;
    });

Now I have a menu defined in the app.component.ts file and I would like to display the Entity Name on the Menu.
Initially I call the function within the initializeApp() section, which works fine when the app loads and it gets the correct values, BUT… if this value changes it does not update in the Menu.

My suspicion is because this actually only runs once on App initialization.
My question is how or where do I need to call this function then so that it will update where it is displayed on the menu everytime this value is changed keeping in mind that I do want to be careful of not running queries or calls on every single page?

Any suggestions?

  1. What is the mission of StartupService? I tend to name my services based on the currency they trade in: EmployeeService, CompanyService, ProductService, OrderService and so on. You may think this a trivial and irrelevant question, but I suspect it gets at the core issue here. I’m going to call this class EntityService for the rest of this post.

  2. Why does getActiveEntity exist? To rephrase, what bad thing would happen if you put the guts of getActiveEntity into EntityService's constructor?

  3. How is the active entity changed during the app’s ordinary execution? I would add this responsibility to EnityService's portfolio, and I suspect that doing so will magically solve your issue.

1 Like

Thank you @rapropos.
To answer your questions:

  1. The idea was that everything that needs to happen at startup would be in the StartupService, my thinking was that this would make it easier to not have anything creep in overtime that “steals” resources or gets run at application startup that should not be. But your way is probably better and I will adjust accordingly.

  2. Here my question is if it is not in it’s own method and in the constructor, won’t it run every time the class is called? So in the example of using EntityService, there might be many methods inside that service, but they should not run every time but only when needed. On some places one method within the service will be needed but not the others.

  3. The active entity is only changed on one specific page specifically for changing the entity, “entity-select”. After the user chose the correct entity, and they clicked submit, it stores it in localstorage as well as:

    this.startupService.myActiveEntityDesc.next(selectedEntity);
    this.startupService.myActiveEntityID.next(selectedEntity);

So on all other pages I reference this, it updates automatically as it should, but not on my menu which is in my app.component.ts file.

I hope this makes sense?
Thank you for your inputs and help! Much appreciated.

@rapropos I am soooo frustrated by now…

I just can’t seem to get this working…

Ok, I have made some changes from the original code and changed the name of the service as you suggested.

Here then is the new code:

Starting with the entity.service.ts file:

import { Injectable } from '@angular/core';
import { Plugins } from '@capacitor/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class EntityService {

  public activeEntityID: BehaviorSubject<any> = new BehaviorSubject<any[]>([]);

  constructor() { }

  load(): void {
    Plugins.Storage.get({ key: 'activeEntityID' }).then((data) => {
      this.activeEntityID.next(data.value);
    });
  }

  updateData(data): void {
    Plugins.Storage.set({
      key: 'activeEntityID',
      value: JSON.stringify(data)
    });
    this.activeEntityID.next(data);
  }

}

the entity-select.page.ts file:

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { NgForm } from '@angular/forms';
import { Router } from '@angular/router';

import { EntityService } from '../../services/entity.service';

@Component({
  selector: 'app-entity-select',
  templateUrl: './entity-select.page.html',
  styleUrls: ['./entity-select.page.scss'],
})
export class EntitySelectPage implements OnInit {

  theEntityList: any;

  constructor(
    private http: HttpClient,
    private router: Router,
    public entityService: EntityService
  ) { }

  ngOnInit() {
    
    this.getEntityList();

  }

  onChangeSubmit(form: NgForm){
    if (!form.valid){
      return;
    } 
    const selectedEntity = form.value.sel_Entity;  
    this.entityService.updateData(selectedEntity);

    this.router.navigateByUrl('dashboard');
  }

  getEntityList(){
    this.http.get('https://xxxxxxxx').subscribe((response) => {
      // console.log(response);
      this.theEntityList = response;
    });
  }
}

and let’s for this purpose use the dashboard.page.ts file:

import { Component, OnInit } from '@angular/core';
import { EntityService } from '../../services/entity.service';

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.page.html',
  styleUrls: ['./dashboard.page.scss'],
})
export class DashboardPage implements OnInit {

  retrievedEntityDesc: any;
  retrievedEntityID: any;

  constructor(public entityService: EntityService) { }

  ngOnInit() {

  }

}

everywhere I use {{this.entityService.activeEntityID.value}} to display the value.

So now… some screenshots:

Step 1: Open App:
image
Step 2: Open Menu and click on Change
image
this just uses a normal button to route to entity-select page:

<ion-button color="success" routerLink="entity-select">Change</ion-button>

Step 3: Choose Entity #2 and Click Change:
image
This does now immediately change the stored value in the storage:
image
and redirects back to Dashboard using : this.router.navigateByUrl('dashboard');

BUT: image
It does not change on the Dashboard and also not on the menu:
image

The strange thing is if I click BACK on the browser, it does display the newly selected entity:
image

By now I am very frustrated because I don’t seem to get how this works and keep on missing something…

Can you please assist?

Apologies if you’ve seen this before, but I think it describes the situation you are in pretty closely. Never make Subjects public, and you will prevent yourself from doing this to yourself. Nobody outside EntityService should be able to reach activeEntityId.value - they should only receive its value as emissions from an Observable.

1 Like

Thank you @rapropos, can you perhaps share some video where this is explained… i am struggling to understand this though especially yout watch, peek, poke concepts… sorry if I am slow here…

Sorry, while I understand that many people love learning from videos, I find them supremely frustrating and useless, so I have absolutely no clue what you would consider a helpful video.

Generally speaking, I have found that most everything you want to do with business layer data boils down to three operations:

watch: I want to enter into a contract whereby I receive all updates to the data. Calling this function should always give one an Observable<Thingy> that must be subscribed to. Terminating that subscription (which must be done somehow, typically on ngOnDestroy for components, in order not to leak subscriptions) ends the contract

peek: I have an urgent need to know what the current value is right now. I don’t care about the future. Internally this typically calls value like you are doing, but structuring it this way makes it obvious that clients should have no expectation of updates. Your current code is effectively calling peek and expecting it to act like watch.

poke: An externally-delivered update to the data. Calling this will send updates to anybody who has called watch and still has an active subscription to the Observable it returned.

@rapropos ok thank you. I agree with you that these three will most likely fit most needs. The main reason for the videos is that sometimes the explanations help me. I will setup a clean project tomorrow and try to implement your example. I really need to get this down… hate being this slow…

Thank you for your patience. Much appreciated.