Injectables are a singleton only per IonicPage and not for the whole app


#1

We have a app which is using several IonicPage Components which would be lazy loaded.

In any of this IonicPage we have an html code like this

<ion-header>
    <custom-toolbar></custom-toolbar>
</ion-header>
<ion-content>
  .....
</ion-content>

So you can see, in the ion-header we have a CustomToolbarComponent which is a angular component loaed by an angular module. So until here anything is fine and it works great.

The CustomToolbar is injecting in his constructor a injectable global service.

@Component({
    selector: 'custom-toolbar',
    templateUrl: 'custom-toolbar.html'
})
export class CustomToolbarComponent {
    constructor(public myService: MyService) {

and this is our global service which should be injected only once per app.

@Injectable()
export class MyService {}

The problem is, we have N myService object injectes and app per N IonicPage, so this would not be a singleton per app but per page.

So the question is, should this not be a bug?? Or I’m missing something on singletons and angular?


#2

Where do you provide it?


#3

Hi, provide it in the CustomToolbarModule,

@NgModule({
    imports: [
        IonicModule,
        .......
    ],
    declarations: [
        CustomToolbarComponent
    ],
    providers: [
        ---> MyService <---
    ],
    exports: [
        CustomToolbarComponent
    ]
})
export class CustomToolbarModule { }

So probably I have to declare it in my main.module.ts to have it app wide? If so, I have a knowledge gap, is ths also on angular so??? For example the HTTP provider we declare in the main.module.ts, probably for the same reason right?


#4

If you declare in app.module.ts, the same version of the provider will be injected into each page that injects it. If you declare the provider in each pages ngModule, a distinct version of the provider will be injected into each page. Probably most programmers want the first behavior – so put your providers into app.module.ts, don’t lazyload the providers.

In my case, I want all my providers to be global except for one, which is only used if the user goes to a specific page or connected group of pages. So I load that one provider lazily, and all other providers at app startup.


#5

You are right, perhapse you can help I have an last question.

I have a multi level navigation with lazy loaded pages, which is respecting so

                                              ------->   Page1 (lazy loaded)
                --------> MainPage(main nav)  ------->   Page2 (lazy loaded)
app (root nav)                                ------->   Page3 (lazy loaded)
                                              ------->   Page (lazyLoaded)
                --------> LoginPage

So if you can see there is the root nav which is first level navigator which loads MainPage or LoginPage, always LazyLoaded.
In the MainPage there is a main root which is the second level navigator which loads Page1, Page2, Page3, Page4. All this Pages loads a the CustomToolbarComponent which injects our service. So if I provide the Service in the module for CustomToolbarComponent every time the component is loaded new we have a new provider. If we load the provider into the app module it does work and we have provided the provider for every component and it is always the same, but we don’t want it provided in LoginPage. So we won’t the service provided only for the MainPage and sub pages and components.
But if we declare the provider in the MainPage module it doesn’twork. Can you help?


#6

I don’t quite understand yet. Why can’t you declare the provider in app.module.ts, and then only inject it into pages where you want it?


#7

This provider handles a WEBSOCKET connection to the server which handles alarms and others. Things you have to be logged in. So all this should be available only from MainPage and SubPages. So we would like inject the provider in MainPage and if MainPage is leaving (destroing) we have to close manually the websocket.

On LoginPage or potentially other Pages loaded in RootNav which are not MainPage should not handle this provider.


#8

Then declare it in app.module.ts, inject it in MainPage, but don’t inject it in LoginPage. Either there’s something simple I don’t understand, or there’s something simple you don’t understand, and I’m not sure which is happening.


#9

This service for example is handling alarms and push notifications on client. We have to guarantee on leaving MainPage this socket would be closed and there could not be other Push or Alarm Notifications. All this is doing the service by his own. (Perhaps this is not elegant)

If we declare it on App, but don’t close or destroy the Service/Websocket after Logout on MainPage on which we redirect user to LoginPage, the Service would always be up? Right? So you don’t have injected Service on LoginPage, but it was injected before and it is working in background, because there is a WS which is still connected and give alarms.
If this is not so, I’m really missing something on DI and Providers :sweat:


#10

Let’s say you have a global auth provider. Have it it produce a stream that emits null if the user is logged out, or a non-null auth object if the user is logged in. Or just boolean false or true if that’s all you need. You could use an rxjs Subject like BehaviorSubject, or use the Events service from ionic-angular.

Then the WebSocket service subscribes to this auth stream, and changes state when the auth state changes.


#11

Ok, obviously we can do this and we’ll implement this, thx for your help!!!

But only for technical interest, should the declaration of the provider in main.module.ts instead of app.module.ts not simple work? What I’m missing? Thx!


#12

It occurs to me that if you never considered injecting providers into other providers, that might be the missing piece. A simpler option that wouldn’t require a subscription might be that, whenever the websocket provider is about to check a socket, it first asks the auth service whether the app is logged in, and only continues with the query if it gets a yes back. But a subscription might be better in the long run, because then you’re only checking auth state at one point, so less chance of bugs creeping in I think.

I’ve never tried putting a global provider anywhere except app.module.ts. It might be ok to put it other places too, I don’t know. All I can say for sure is that what I’m describing has worked for me.