How to update property of singleton for display in template?

I am trying to locally store a username by updating this.username property of a global (singleton?) User service component, which is rendered as {{username}}.

But when I update this.username from another component, the change is not reflected in my {{username}} template tag upon entering a new page (once the user logs in). I have included User in the array of @App providers to give it global scope.

Could I be instancing a new User service, rather than modifying it? I’ve tried [Angular 2 singleton service] (http://twofuckingdevelopers.com/2015/04/angular-2-singleton-service/) and other tutes, without luck.

I’m using a lot of global singletons, here’s some questions for you to get info to be able to answer this:

  1. Are you telling the other component to pull the variable changes from the singleton after you updated it?
  2. Do you have any Subscribe to events or are you relying in the constructor of the component that doesn’t update the template?
  1. No, I’m not telling the other component to pull the variable changes after it updates it, because I’m navigating to a different page immediately after changing the variable. I assume the new page should show the updated variable without having to ‘pull’ any changes. How do I pull variable changes?

  2. I am not using Subscribe events. Just relying on the constructor of the User service component, and other pages to include the User service and define this.user = user; and username = this.user.username in the constructor.

Thanks for your help!

If you rely on the constructor and you are sure you set the variable right before navigating to the other variable then you’re missing something, i use the constructors too.

Can you show 3 things of your code? i want to see how you inject the singleton in the App and in the two components, also where you set and pull the variable in both components.

Ok no problems! A little simplified, but basically my code.

services/user.ts

import {Injectable} from 'angular2/core';

@Injectable()
export class User {

  constructor() {
    this.name = null;
  }

  storeUsername(userName){
    this.name = userName;
  }

  getUsername() {
    return this.name;
  };

}

pages/login.ts

import {Page, NavController} from 'ionic-framework/ionic';
import {User} from   '../../services/user';
import {DB} from   '../../services/db';

@Page({
  templateUrl: 'build/pages/login/login.html', 
  providers: [User, DB]
})

export class LoginPage {
   constructor(nav: NavController, user: User) {
     this.db = db;
     this.nav = nav;
     this.user = user;
     var that = this;
  };

 //save username
 saveUser(){
   that.db.ref.onAuth(function(authData) {
      if (authData) {
        //store username locally for fewer DB calls (save our Firebase quota?)
        that.user.storeUsername(authData.username);
        that.nav.push(HomePage);
      }
    });
 }

login.html

<button (click)="saveUser()">Login</button>

pages/homepage.ts

import {Page} from 'ionic-framework/ionic';
import {User} from '../../services/user';

@Page({
  templateUrl: 'build/pages/homepage/homepage.html', 
  providers: [User]
});

export class HomePage {

 username: String;

  constructor(user: User) {
    this.username = this.user.name;
  }

}

homepage.html

<p>Hi there {{username}}</p> <!-- this template tag doesn't update, renders nothing -->

app.ts

import {App} from 'ionic-framework/ionic';
import {LoginPage} from './pages/login';
import {DB} from   './services/db';


@App({
  templateUrl: 'build/app.html',
  providers: [User, DB]
})

export class MyApp {
  constructor(app: IonicApp) {
    this.app = app;
    this.root = LoginPage;
  }


}

I know what’s your problem.

You must import User service only at an app.ts level, it will then propagate to child components. If you do this in every component you would just create a new instance.

I wrote an article that also covers this problem: http://www.gajotres.net/ionic-2-sharing-data-between-pagescomponents/

If you take a look, you’ll notice ShareService is imported in every class but it included only in app.js:

@App({
  templateUrl: 'build/app.html',
  providers: [ShareService]
})
3 Likes

like Gajotres sais -->

there are two types of injectables.

  1. “local” --> an instance for each usage --> this happens if you set the injectable via providers-metadata in each component you are using it
  2. “global” (more or less) --> an instance for multiple usages --> set the injectable via providers-metadata only in a parent-component or at boot-time
2 Likes

Both @Gajotres and @bengtler are right, by adding it in the providers array in each component you are creating a new instance for that service, if you just put it in the providers array of @App component and nowhere else, it will be a global provider to it’s children, after that you just need to import it as a dependency in both components.

1 Like

Thank you people!! Can’t wait to discover more of these gotchas. :frog: Stuff like this should go in the troubleshooting section of the docs.

How do you ONLY reference SharedService in one component and then it’s available in all the other components? This is not processing with me. In order to use the SharedService in other components outside of App.js (e.g. to emit it), you’d have to import in ANY component that is needed to update the prop, right? Sorry if I’m misunderstanding your thought process on this, but this isn’t clicking for me. I’d have to see examples of this logic…

The first thing to remember is that the syntax in this thread is five years old. Specifically, the @Page decorator no longer exists - it used to be an Ionic-specific thing that has been subsumed under current recommendations to just use Angular’s @Component.

The second thing I want to emphasize is that naked scalar singletons in services are a bad idea unless you are 150% sure that never ever ever is it even remotely possible that you’ll ever want to change them while the app is running. If there’s even a chance that they’ll become mutable, expose them as Observables out of the service and subscribe to them in the places that reference them (either for reading or for writing).

So, to summarize so far, you probably don’t want to be doing exactly what OP was in the first place, and you definitely don’t want to be trying to use any of the old syntax from this thread verbatim.

Now, to address your direct question.

Refer back to the first post in this thread that contains code. It has this decorator:

@Page({
  templateUrl: 'build/pages/login/login.html', 
  providers: [User, DB]
})
@Page({
  templateUrl: 'build/pages/homepage/homepage.html', 
  providers: [User]
})

The point under discussion here initially was that the two pages decorated here weren’t seeing the same User, and that’s because they were declared in providers on each page. The way to get them both seeing the same User is to only declare it in providers of the AppModule. You’re absolutely right that everybody needing access needs to inject it, but not everybody needs to declare it as a provider, and in fact doing so breaks the main point of shared services.