Services in the shared context

Let's talk about what the impact of having services in a shared context and what the addition of @NgModule has brought to the table. There are two types of services we need to care about:

  • A transient service; this service creates a new copy of itself and may or may not contain an inner state; for each copy created it has its own state
  • A singleton, there can only be one of this service and if it has a state, we need to ensure that there is only a copy of this service in our entire application

Using dependency injection in Angular, placing services in the providers of the modules will ensure they end up on on the root injector and thereby there will be only one copy of them created if we have this situation:

// app/task/task.module.ts

@NgModule({
declarations: [TaskComponent],
providers: [TaskService]
})
export class TaskModule {}

Earlier, we had a declaration of a TaskModule in which we provided the TaskService. Let's look at defining another module:

@NgModule({
declarations: [ProductsComponent]
providers: [ProductsService]
})
export class ProductsModule {}

Providing we import both of these modules in the root module, like so:

//app/app.module.ts

@NgModule({
imports: [TaskModule, ProductsModule]
})
export class AppModule {}

We have now created a situation where ProductsService and TaskService can be injected in the constructor of ProductsComponent or TaskComponent, thanks to ProductsModule and TaskModule both being imported into the AppModule. So far, we don't have an issue. However, were we to start using lazy loading, we have an issue on our hands. In lazy loading, the user navigates to a certain route and our module, together with its constructs, which are loaded into the bundle. If the lazy loaded module, or one of its constructs, actually injects, let's say ProductsService, it would not be the same ProductsService instance that TaskModule or ProductsModule is using and this might become a problem, especially if the state is shared. The way to solve this is to create a core module, a module that is imported by the AppModule; this would ensure that services is never subjected to the risk of being instantiated again, by mistake. So, if ProductsService is used in more than one module, especially in a lazy loaded module, it is advisable to move it to a core module. So essentially, we go from doing this:

@NgModule({
providers: [ProductsService],
})
export class ProductsModule {}

To moving our ProductService to a core module:

@NgModule({
providers: [ProductsService]
})
export class CoreModule {}

And of course, we need to add the newly created CoreModule to our root module, like so:

@NgModule({
providers: [],
imports: [CoreModule, ProductsModule, TasksModule]
})
export class AppModule {}

One can argue that if our application is small enough, creating a core module early on might be seen as somewhat of an overkill. An argument against that is that the Angular framework has a mobile first approach and that you as a developer should lazy load most of your modules, unless there is a good reason not to. This means that when you deal with services which might be shared, you should move them to a core module.

We built a data service in the previous chapter to serve a tasks dataset to populate our data table with. As we will see later in this book, the data service will be consumed by other contexts of the application. So, we will allocate it in the shared context, exposing it through our shared module:

//app/task/task.service.ts

import { Injectable } from '@angular/core';
import { Task } from '../interfaces/task';

@Injectable()
export class TaskService {
taskStore: Task[] = [];
constructor() {
const tasks = [
{
name : 'task 1',
deadline : 'Jun 20 2017 ',
pomodorosRequired : 2
},
{
name : 'task 2',
deadline : 'Jun 22 2017',
pomodorosRequired : 3
}
];

this.taskStore = tasks.map( task => {
return {
name : task.name,
deadline : new Date(task.deadline),
queued : false,
pomodorosRequired : task.pomodorosRequired
}
});
}
}

Please pay attention to how we imported the Injectable() decorator and implemented it on our service. It does not require any dependency in its constructor, so other modules depending on this service will not have any issues anyway when declaring it in its constructors. The reason is simple: it is actually a good practice to apply the @Injectable() decorator in our services by default to ensure they keep being injected seamlessly as long as they begin depending on other providers, just in case we forget to decorate them.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset