Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a singleton service without needing to inject it in Angular 7

Situation

I've been trying to find a way where I can instantiate a service that will purely sit "in the background" and listen to events (and do stuff) - which I would like to be created when the app initializes, and be forgotten about.

Unfortunately, I would need to use dependency injection into a component, for the service to be instantiated - most paths I take lead to using the AppComponent's constructor.

I won't be directly interacting with the service though (calling methods/properties), and want to keep it out of other components/services which don't have anything directly to do with it.


The service

The service and the logic in it is pretty straightforward. My service is based on a Dynamic page titles in Angular 2 tutorial.

The service will listen to NavigationEnd events from the Router, grab the ActivatedRoute, and then use the route's data to set the page title.

Unlike the example in the tutorial, I've created my own service instead of putting the logic within the AppComponent; I want to keep my separation of concerns tip-top.

page-title.service.ts:

import { Injectable } from '@angular/core';
import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { filter, map, mergeMap } from 'rxjs/operators';

@Injectable()
export class PageTitleService {

  constructor(
    router: Router,
    activatedRoute: ActivatedRoute,
    titleService: Title
  ) {
    router.events
      .pipe(
        filter((event) => event instanceof NavigationEnd),
        map(() => activatedRoute),
        map((route) => {
          while (route.firstChild) {
            route = route.firstChild;
          }

          return route;
        }),
        filter((route) => route.outlet === 'primary'),
        mergeMap((route) => route.data)
      )
      .subscribe((routeData) => titleService.setTitle(routeData['title']));
  }

}

Obviously, the service itself will rely on dependency injection to use the Router, ActivatedRoute, and Title services.


The problem

The only way I currently know to instantiate this service is to use dependency injection into another component/service.

E.g. in app.component.ts:

export class AppComponent implements OnInit {

  constructor(
    pageTitleService: PageTitleService, // inject the service to instantiate it
    // ... other services
  ) { }

  ngOnInit() {
    // do stuff with other services, but not the pageTitleService
  }

}

The problem is, I want to avoid doing this if at all possible.


Question

Is it possible to instantiate the service somewhere other than a component/service?


Possibility?

I do have an app-load.module.ts, which does some upfront initialization, before the rest of the app is loaded:

import { APP_INITIALIZER, NgModule } from '@angular/core';

import { OrganisationService } from './core/organisation/organisation.service';

export function initApp(organisationService: OrganisationService) {
  return () =>
    organisationService
      .initialize()
      .then(() => window.document.documentElement.classList.remove('app-loading'));
}

@NgModule({
  imports: [],
  declarations: [],
  providers: [
    OrganisationService,
    { provide: APP_INITIALIZER, useFactory: initApp, deps: [OrganisationService], multi: true }
  ]
})
export class AppLoadModule { }

Could I perhaps instantiate the PageTitleService in here, somewhere?

Or, is there a better place/way to do it?

Thanks in advance.

like image 262
Geoff James Avatar asked Jun 11 '19 10:06

Geoff James


People also ask

Can we use service without injectable?

If you want to use angular services (and Http is an angular service) you must inject them as I told above as a constructor attribute to another service / component , which means if you want to use Http you need to have your service injectable. So the answer is no, you can't do it in a nice way.

How do you provide a singleton service in Angular?

There are two ways to make a service a singleton in Angular: Set the providedIn property of the @Injectable() to "root" Include the service in the AppModule or in a module that is only imported by the AppModule.

Can we create a service without @injectable in Angular?

It works. We have successfully created an Angular service without the @Injectable decorator.

Are Angular services singletons by default?

The answer would be no. The main objective of angular services is to share data across Angular application. Practically an angular service can be shared between all the components or can be limited to some component. Hence Angular service can be a singleton as well as non-singleton in nature.


2 Answers

Just an observation why injecting in a component (App component) would not be such a bad idea:

  1. Showing title in the browser window is a Application requirement and app component is actually the container (or root) your application.
  2. So can't we say that it is a concern of application (app component) to have the title updated promptly?
  3. A service as i understand can be seen as a helper and App component can use this service to update the title
  4. You can have a init method in the service which will be called from app component so that it can start listening to router event and update the title. This makes it very imperative, explicit too and you can move calling this init method to some other component if required.
like image 120
saivishnu tammineni Avatar answered Sep 29 '22 12:09

saivishnu tammineni


Solution

Instead of injecting the service via your app.component.ts, you could inject the service via the app.module.ts (or via your app-load.module.ts)

Eg:

@NgModule({
  ...
})
export class AppModule {
  constructor(pageTitleService: PageTitleService) {}
}
like image 26
Hedgybeats Avatar answered Sep 29 '22 14:09

Hedgybeats