Let's image we have an Angular web application. Under the AppComponent, we have three children: About, User and UserDetail. Inside AboutComponent and UserDetailComponent, we want to log the data to the console, and the logic of this method is totally same for two components. So, instead of implementing the that logic in two component and create two duplicated method, we create a new file called LogService containing the log data method, and inside AboutComponent and UserDetailComponent, we can call that method and pass any required parameters. Similar for UserComponent, we just create a new file called UserService containing the logic for getting user data from backend, and inside UserComponent or any other places that want to fetch the data, we just need to call the implemented method from UserService and no need to duplicate any line of codes. And that what we call a service. Generally, service is a class containing one or more methods and provide them for the other parts of the system.
So now, let build a log service. Just choose a fitting place inside app folder and create a new file named logging.service.ts:
logging.service.tsimport { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class LoggingService {
logData(value: string): void {
console.log('Logging data from LoggingService:' + value);
}
}
And use it in AboutComponent:
about.component.tsimport { Component, OnInit } from '@angular/core';
import { LoggingService } from '../logging.service';
@Component({
selector: 'app-about',
templateUrl: './about.component.html',
styleUrls: ['./about.component.scss']
})
export class AboutComponent implements OnInit {
constructor() { }
ngOnInit(): void {
const loggingService: LoggingService = new LoggingService();
loggingService.logData('AboutComponent')
}
}
This will work, there is no error about the above lines of code, but this is not how we use a service in Angular. Imagine that in the future, we decide to add a parameter to the constructor of the service, we have to come back and change anywhere we have used this service and add a new argument. So, Angular provide a much better way of getting access to our services so that we do not need to create the instances manually. And it is Dependency Injection.
Dependency is something a class of ours will depend on, for example out AboutComponent depends on LoggingService because we want to call a method of that service inside our component. And Dependency Injection automatically injects an instance of that dependency into some place we want it to, in this case, our component when we inform Angular that we require such an instance.
So, we will modify our code a little bit:
about.component.tsimport { Component, OnInit } from '@angular/core';
import { LoggingService } from '../logging.service';
@Component({
selector: 'app-about',
templateUrl: './about.component.html',
styleUrls: ['./about.component.scss'],
providers: [LoggingService],
})
export class AboutComponent implements OnInit {
constructor(private loggingService: LoggingService) { }
ngOnInit(): void {
this.loggingService.logData('AboutComponent')
}
}
So we all know that Angular is responsible for creating our components. We are placing our component selectors in our templates, and when Angular comes across these selectors, it gives us the instances of the components. Since Angular is responsible for instantiating our components, it need to know how to do it correctly. So if we define in the constructor that we need something, Angular will recognize and it tries to give that to us, in this case, the LoggingService. Now Angular know what we want, but it has not know how yet. So we have to "provide" it, simply mean we tell Angular how to create it by passing an array of the type of what we want to to be able to get provided, again, the LoggingService. Now, with all of that, when Angular analyzes our component, it would know that we require the LoggingService and be able to give us that, and then it construct the component, it sees that we need to have an instance of the LoggingService and it will know how to give us that instance. And in anywhere in this component, we can access the LoggingService properties. This way is better than creating service instance manually, and of course, there are some other advantages that I will show you.
Hierarchical Injector
When we provide a service in some place of our application, Angular will be able to create an instance of that service for that component, and all its child components, and of course, the child components of the child components. They all receive a same instance of the service.
- AppModule: The same instance of the service can be used in all components, all directives and all other services.
- AppComponent: AppComponent and all other components will have the same instance of the service, but not for directives or other services.
- Other component: The same instance of the service is only available for that component and all its children.
If you are using Angular 6 or higher, you can use a different way to provide the services for our application. Instead of adding a service class to the providers array, now we can use the @Injectable() decorator to define which level we want to provide our services.
logging.service.tsimport { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class LoggingService {
logData(value: string): void {
console.log('Logging data from LoggingService:' + value);
}
}
providedIn:'root': provide the service in application-level, similar with AppModule level
From Angular 9, we have two more scopes:
providedIn:'platform': when your project has one or more applications, then all of them can use the same instance of that service.
providedIn:'any': all eagerly loaded modules will share a same instance of the service. Other lazy loaded modules will have its own unique instance.
An important thing is that if we provide the same service, the higher level instance will be overwritten by the lower one.
And because all component can use the same instance of a service, we can use a service to create cross-component communication, which I will shown you in the next section.