How to create a singleton service

I am trying to create a singleton service at the app level to be used by various component pages. In angular 2, I would do this from the bootstrap method in the boot file:

bootstrap(MyApp, [MySingletonService]);

Then the service could be injected in the constructors of the various components as a singleton, that is giving each component the same instance.

But there is no boot file in Ionic 2, there is an @App component. And it looks to me that framework calls bootstrap on its own.

If I inject my service into a component page in Ionic 2 with

@Page({
templateUrl: ‘app/myPage/myPage.html’,
providers: [ MyService]

})

I get a new instance of my service, rather than a singleton. So in Ionic 2 how do you get a singleton service?

2 Likes

Edit: This is no longer the correct solution in the latest versions of Ionic 2. See shaikhspear’s reply below.


Inject it into the @App itself:

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

then import it in the @Page to use it. See the conference app:

https://github.com/driftyco/ionic-conference-app/blob/master/app/app.js#L10-L13

https://github.com/driftyco/ionic-conference-app/blob/master/app/pages/schedule/schedule.js#L12

3 Likes

Yes, thank you. That is working. I had tried that before, but had forgotten that I still needed the import statement in the page component. Great.

1 Like

I’m have the same problem. If I add my provider to the providers array in both the App decorator, and my Component decorator, the provider constructor is called twice.

My solution was to remove the dependency from the Component decorator, and leave it only in the app. This fixes my main problem, but creates another one.

Now the component only works if App decorator injects the provider. Any insights on how to make this more modular while keeping my provider as a singleton(Not calling the constructor twice) would be much appreciated

Here is a list of my dependencies:

“dependencies”: {
“angular2”: “2.0.0-beta.14”,
“d3”: “^3.5.16”,
“es6-promise”: “3.0.2”,
“es6-shim”: “0.35.0”,
“es7-reflect-metadata”: “^1.6.0”,
“ionic-angular”: “2.0.0-beta.3”,
“ionic-native”: “^1.1.0”,
“ionicons”: “3.0.0-alpha.3”,
“reflect-metadata”: “0.1.2”,
“rxjs”: “5.0.0-beta.2”,
“weather-icons”: “git+ssh://git@github.com/erikflowers/weather-icons.git”,
“zone.js”: “0.6.10”
},
“devDependencies”: {
“d3”: “^3.5.16”,
“del”: “2.2.0”,
“gulp”: “3.9.1”,
“gulp-watch”: “4.3.5”,
“ionic-gulp-browserify-typescript”: “^1.0.0”,
“ionic-gulp-fonts-copy”: “^1.0.0”,
“ionic-gulp-html-copy”: “^1.0.0”,
“ionic-gulp-sass-build”: “^1.0.0”,
“ionic-gulp-scripts-copy”: “^1.0.0”
}

  1. in your app.ts :

ionicBootstrap(MyApp, [MyDataService]);

  1. in your component(s).ts :
@Component({
	templateUrl: 'html.html.html',
        /* do not add the declaration : [MyDataService] */
})
export class MyPage{
	constructor(myDataService: MyDataService) {
		// myDataService : singleton instance
	}
1 Like

Hey, can someone help me?

I’m trying to put 2 providers in my bootstrap, then all pages can use both correct?

Well, when i push a page in my app, this message appears:
Error in build/pages/login/login.html:19:16BrowserDomAdapter.logError @ browser_adapter.js:84
browser_adapter.js:84ORIGINAL EXCEPTION: Can’t resolve all parameters for HomePage: (NavController, App, MenuController, LoadingController, Services, ?).

Someone know why?

Thanks!

For latest Angular version, register provider in the root module itself and inject it in subsequent components you need to use.

 imports: [
    BrowserModule,
    HttpModule,
    CloneUserModule,
    RouterModule.forRoot([
      { path: 'welcome', component: WelcomeComponent }
    ])
  ],
  declarations: [AppComponent, WelcomeComponent],
  bootstrap: [AppComponent],
  providers: [SingletonService]
})
2 Likes

Hi, I am confused by your response, during RC0 upgrade it said remove all providers [] arrays from inside of all @components( ), which I did and moved them to the app.modules.ts providers [] array. Likewise all components needed to be listed twice once under declarations:[] array and once under entryComponents:[] in app.modules.ts. I am now at RC6 / 2.0
Once you do this, it appears that ionic2 will only create the service once, unless, I decide to do something like this in my component/service when needed.

@Injectable()
export class CatsService {
   UMSData: UMSData;   // every service uses its own instance of this service
  // 20160718
  constructor(
    private http: Http
    // , private UMSData: UMSData
  ) {
    console.log("CatsService------------>>Created");
    this.UMSData = new UMSData();   // every service uses its own instance of this service
  }

However I am still fighting what once was working, the complaint about "No provider for NavController"
I have tried to save NavController into my GlobalService and access it from a service. which basically attempts to navigate to a tab using something like this.

    this.NavController.parent.select(this.GlobalService.TabsIndex.AMSPlayer).then(() => ...

I have also tried

this.GlobalService.NavController.parent.select(this.GlobalService.TabsIndex.AMSPlayer).then(() => 

but this was not successful.

What is the ionic2 official way to access something like NavController from within a service that basically was invoked from a component to paginate to another tab?
Could you please point to some basic clear examples of @app() and @page() and how they relate to accessing providers / services.

I am aware of

Thank you.

I obviously cannot speak for the ionic2 official way, and my opinion is probably unpopular on this topic, but it’s “don’t”. I have yet to run across a situation where the ultimate goal cannot be achieved by alternate means, where the service merely exposes data and observables and components react and handle navigation. I feel this is much cleaner and easier to maintain and test.

So I am sure I am doing things wrong, and confusing Contoller as in Model-View-Controller, and an ionic2 service which is solely expected to provide data per your comment.
I have a MediaListComponent where the user selects a media of a certain type, then I would like to delegate the “playback sequencing” logic to PlayersController by doing the following from MediaListComponent

this.PlayersController.Play(Media);

I am receiving PlayersController through the constructor of MediaListComponent.

Should the PlayersControlller class use @Injectable() ? if it is not regarded as a service and just another global class, how do I receive the following:

constructor(
   // private NavController: NavController
    public GlobalService: GlobalService
    public Player1 : Player1
    public Player2 : Player2
    public PlayersViewComponent : PlayersViewComponent 
    

PlayersControlller in turn will decide which actual media player to use and switch to a shared players tab and activate the View for that particular player, thus I need NavController also. Something like…

...
 var videoId: string = Media.PLAY.__text;
 this.Player1.PlayVideoByID(videoId);
this.ShowPlayer();
...
....
ShowPlayer() {
if (this.MediaCurrent._TYPE == "AMS") {
this.NavController.parent.select(this.GlobalService.TabsIndex.AMSPlayer).then(() => {
this.PlayersView.Player2Hide();
this.PlayersView.Player1Show();
});
....
}

PlayersViewComponent currently basically wraps/hosts Player1Component and Player2Component hosted in the same tab.

I would appreciate if you could help me lay down these classes, the way you would approach things?
Thank you.

I would move all the view layer logic (which player is to be shown, etc) out of PlayersController and into PlayersViewComponent, where you have easy access to a NavController. In the situation you described, I don’t think you really need it, though. You can just have both Player1 and Player2 in that template, and use ngIf based on the media type to decide which to show. So I would eliminate PlayersController entirely and do something like this:

###MediaListPage

<ion-list>
  <template ngFor let-track [ngForOf]="tracks">
  <button ion-item (click)="play(track)">[{track.name}}</button>
  </template>
</ion-list>

play(track:Track): void {
  this._nav.push(PlayerPage, {track: track});
}

###PlayerPage

constructor(np: NavParams) {
  this.track = np.get('track');
}

<a-player *ngIf="track.flavor === 'A'" [track]="track"></a-player>
<b-player *ngIf="track.flavor === 'B'" [track]="track"></b-player>

@Component({selector: 'a-player'})
export class TypeAPlayer {
  @Input track: Track;

  ngOnChanges(changes: any): void {
    // this.track is now initialized, do whatever we need to do with it
  }
}

Thanks for the sample code. I see you are using *ngIf to handle showing / hiding the player element, however I could not get it to work in this plunker. Perhaps you can figure out why I can’t seem to be able switch to a tab and tell the page at the new tab to display the element desired.

Background: I have looked at using and experimented with .push/pop navigation but decided I want to keep the player live while the user browses other tabs of interest. So a shared tab for players is what I have decided on.
I would really appreciate if you could help me resolve this nagging road block I can’t seem to resolve.
Thank you.

I am suggesting having some sort of property of the selected media track (I like to use “flavor” for this purpose because so many of the typical names like “format” and “type” are frequently reserved words in various languages) determine which player is shown. There should be no need to explicitly hide and show anything from methods.

Hi, I don’t think I am running into a name collision with reserved words in the plunker that I shared above. The expressions used with *ngIf work but only from method calls affecting them and triggered from within from the same page.

You happen to pass the media object via .push( , Media object ), I cannot and don’t want to use .push() behavior for many reasons.
Tabs Select( ) used to switch does not seem to support passing data like .push() does.
I found lots of related topics/requests on this here is one of them

Could you explain what is fundamentally wrong with calling a method for example, .ShowPlayer( Media ) on the page?
Is there a lifecycle of the tabs/pages that I am totally missing?

The methods work and hide the appropriate player(s) when triggered from the page as the plunker demonstrates, but NOT when invoked from another page. How do you explain that?

Breakpointing and stepping seems to execute the lines of the methods from either invocations but no effect on the *ngIf from the external calls so I don’t know if this is an artifact/bug of the debugger fooling me.

I even wrapped all the code with switching tabs and activating the player in the same method of the target page PlayersView.ts like so

   SelectTab(index: number) {

            this.AMSPlayerShow_ = true;
            this.YTPlayerShow_ = true;
            
        var Tabs: Tabs = this.NavController.parent;
        Tabs.select(index);   
    }

The tab select works but the 1st two lines seems to have no effect. Does somehow Angular2 have a cached value of the expressions? My brain hit a wall.

You can store the media object in an activeTrack property of a provider. Anybody who wants to set the active track can inject the provider and do so and anybody who wants to react when the track is changed need merely reference it from their template. No method calling. No explicit triggering or hiding or showing of anything. All reactive when the track is changed.

Hi, I had to read your reply like 10 times before all this jargon talk sunk in :slight_smile:

Next I needed to capture my brain like this …

I am now consider removing the red controllers, I got the feeling that in this ionic2 reactive architecture, the .TS files are the “Controllers” of their corresponding .HTML “Views” files.
So…

  1. AMSController.ts code should move into AMSPlayerComp
  2. PlyrController.ts should move to PlyrPlayerComp.ts
  3. PlayersController.ts should move into PlayersView.ts

It will lead to the following cleaner layout and provide several benefits like

  1. Provides access to NavController in PlayersView which now would be chained from another View/Controller

My revised brain is now looking more like this, if you wanna provide more feedback…

I keep running into a lot of others struggling like I was :slight_smile: HAHA

image

Thank you.