Can't make singleton works

Hello! I’m having issues to make my singleton work in my application. It is basically one class to manage some objects in the application, example, database (heaviest), router, navcontroller, etc. I’ve read many posts and followed the documentation but I am probably doing something wrong since it is not working.

This is the post that I’ve followed: https://angular.io/guide/singleton-services

My singleton is this, only meant to store the objects.

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { GoogleAnalytics } from '@ionic-native/google-analytics/ngx';
import { SQLiteObject } from '@ionic-native/sqlite/ngx';
import { NavController, Platform } from '@ionic/angular';

@Injectable({
    providedIn: 'root'
})

export class DatabaseService {    

    private database: SQLiteObject;
    private platform: Platform;
    private router: Router;
    private navCtrl: NavController;
    private http: HttpClient;
    private ga: GoogleAnalytics;

    constructor(){}

    // getter and setter

}

I am trying to store the content from my AppComponent, which is where I first call the database and the other classes, but when I try to reach the value in another class, it doesn’t work.

export class AppComponent {

  public unsubscribeBackEvent: any;
  private database: SQLiteObject;
  private dbReady: BehaviorSubject<boolean> = new BehaviorSubject(false);

  constructor(
    private platform: Platform,
    private splashScreen: SplashScreen,
    private router: Router,
    private navCtrl: NavController,
    private sqlitePorter: SQLitePorter,
    private sqlite: SQLite,
    private http: HttpClient,
    private statusBar: StatusBar,
    private ga: GoogleAnalytics,
    private dbService: DatabaseService) {

      // set common data once to be user across the app
      this.dbService.setPlatform(this.platform);
      this.dbService.setHttp(this.http);
      this.dbService.setNavCtrl(this.navCtrl);
      this.dbService.setRouter(this.router);
      this.dbService.setGA(this.ga);
    }
}

I’ve declared it as a provider in the app.module.ts:

providers: [
    StatusBar,
    SplashScreen,
    GoogleAnalytics,
    ScreenOrientation,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
    { provide: LOCALE_ID, useValue: 'pt-BR'},
    SQLite,
    Keyboard,
    SQLitePorter,
    GlobalProvider,
    DatabaseService,
    Geolocation,
]

But when I try to get the data in another class, it is just all null:

@Injectable()
@Component({
    providers: [
      DatabaseService
    ]
  })

export abstract class BasicController implements OnInit {

    constructor(private dbService: DatabaseService) {
        console.log(dbService.getDatabase());
        console.log(dbService.getGA());
        console.log(dbService.getHttp());
        console.log(dbService.getNavCtrl());
        console.log(dbService.getPlatform());
        console.log(dbService.getRouter());
    }
}

These up there are all null. Why can’t the other class see the data from my singleton?

Is dbService itself null / undefined? My guess is no, otherwise you’d be getting NPEs. So is it there, but your functions don’t work? That would be step one, that would tell us if it’s being injected.

So is it being injected? If it is, okay, then what do your functions look like? If it’s just your functions that are returning null…soething is wrong with those. Are they literally just returning this.db or whatever?

Secondarily, have you put breakpoints in your setters and getters to confirm that your setters are running before your getters? You have both in constructors, not sure which is going to run first necessarily.

Several things here.

In AppComponent's constructor, you take advantage of the fact that giving a constructor parameter an access modifier (public or private) automatically declares a property in the enclosing class and initializes it as appropriate. That’s a much cleaner way to do that than with the naked properties in DatabaseService.

I’m concerned about the NavController in DatabaseService. NavController is a presentation-layer concept, and DatabaseService should be all about slinging data. I would rearchitect things in order to move whatever navigation-related functionality is sitting in DatabaseService out of there.

Do not attempt end-runs around DI with the getters and setters in DatabaseService. Inject these things in the constructor as you do in AppComponent.

I am not a fan of providedIn: "root", because it eliminates the ability to mock out the given service. Get rid of it and instead put DatabaseService into the providers stanza of the AppModule.

Now, if you’re still reading, here’s your proximate problem. You virtually never want to put a providers entry in any @Component, and this exact situation is why. When you do that, you are explicitly telling Angular that you want each instance of those providers to be distinct and pinned to that component. In other words, the presence of providers in BasicController ensures that the DatabaseService you see there is not the DatabaseService seen anywhere else in the app.

1 Like

Thanks for the replies guys. Regarding what you said @rapropos, my idea was to use this singleton to avoid reinstancing the sqlite database all the time. I notice that the process is a bit time consuming. Even though it take about 2~5ms per instantiation, when using it in several views this time becomes a bothersome.

Is it normal to have this long time when starting it? The operations once it is initialized goes with normal speed, even if done in bulks, but when it starts this delay is always presented.

I thought it might be another feature or importation in the project but, through exclusion, I`ve noticed that it is the database declaration that increases the delay, without it, it is less than 1ms.

That’s a great idea, but we just have some implementation wrinkles to iron out. All of the stuff you’re doing in the AppComponent that’s related to SQLite should go out of there and into the DatabaseService constructor. Let DatabaseService be a completely self-reliant and independent entity, declare it only in the AppModule’s providers, remove all providers from any other components in the app, and you will have what you desire: a single DatabaseService that centralizes all SQLite-related efforts in it.

Incidentally, just in my casual watching of posts here, roughly 90% of apps don’t really need all the complexity and overhead of SQLite. They work just fine using a NoSQL strategy like GitHub - ionic-team/ionic-storage: Ionic Storage module for Ionic apps. If your data can also be organized so that things can just be fetched by a single unique id, I would suggest at least contemplating that switch. It is much lighter and easier to work with than SQLite. Pretty much the only class of apps that demands SQLite are those where one has to do complex queries offline.

Pretty much the only class of apps that demands SQLite are those where one has to do complex queries offline .

Unfortunately this is my scenario. It is an application to manage crops and product applications along with several other functions to build dashboards and etc.

I’ll try to implement the way that you suggested isolating it to the class alone and retrieve it in the other classes. Only another question came up. The singleton is an injectable. If I call it in another injectable, will it be ok as weill?

I`ve created a structure to avoid having lots of work using OO.

From the bottom to the top
BasicController > QueryController > CrudController > RecordController > ObjectPage

Since many objects are polymorphic, I just extends the RecordController and inherit all the required methods for the persistence. BasicController > QueryController > CrudController > RecordController are all injectables that works with inheritance.

One of the things that didn’t work out at first was trying to instantiate it in the BasicController directly.

To avoid having to always put the instantiation in the constructor in the BasicController I’ve created a static injector where I just call inside it for objects that wont mutate.

This is the basiccontroller constructor, I manage to instantiate it all without having the need to use in the constructor due the staticinjectorservice that I’ve created (with google help)

constructor() {
        // inject the requirements
        const injector: Injector = StaticInjectorService.getInjector();

        this.global = injector.get<GlobalProvider>(GlobalProvider as Type<GlobalProvider>);
        this.screenOrientation = injector.get<ScreenOrientation>(ScreenOrientation as Type<ScreenOrientation>);
        this.navCtrl = injector.get<NavController>(NavController as Type<NavController>);
        this.router = injector.get<Router>(Router as Type<Router>);
        this.anexoService = injector.get<AttachService>(AttachService as Type<AttachService>);

        this.alertController = injector.get<AlertController>(AlertController as Type<AlertController>);
        this.modalController = injector.get<ModalController>(ModalController as Type<ModalController>);
        this.toastController = injector.get<ToastController>(ToastController as Type<ToastController>);
        this.sanitizer = injector.get<DomSanitizer>(DomSanitizer as Type<DomSanitizer>);

        this.http = injector.get<HttpClient>(HttpClient as Type<HttpClient>);
        this.ga = injector.get<GoogleAnalytics>(GoogleAnalytics as Type<GoogleAnalytics>);

        this.attachmentController = injector.get<AttachmentController>(AttachmentController as Type<AttachmentController>);
        this.poolRequestController = injector.get<RequestPoolController>(RequestPoolController as Type<RequestPoolController>);        

        this._servidorService = injector.get<ServidorService>(ServidorService as Type<ServidorService>);
        this._tokenService = injector.get<TokenService>(TokenService as Type<TokenService>);
    }

Anyway, this was just to provide some background in how I’m performing my implementation. I’ll try to do as you suggested now and will get back here as soon as I have it done, success or not.

Thanks again!

If you’re asking “can I inject service B from service A?”, absolutely and emphatically. That’s crucial to how DI works.

At this point we’re going to have a philosophical disconnect, because I am strongly opposed to using inheritance this way, and especially in JavaScript which has no actual OO language support. However, hopefully, even if you do decide to stick with your inheritance hierarchy, you can at least abandon your shadow DI implementation and just use the ordinary Angular one directly.