Understanding Services and Dependency Injection in Angular
How do Services and Dependency Injection work?
Angular distinguishes components from services to increase modularity and reusability. By separating a component’s view-related functionality from other kinds of processing, you can make your component classes lean and efficient.
Sometimes sharing data among the components becomes highly complex with Input, Output and EventEmitters when the distance between the components is significant. So, we use services to manage this cross-component communication in an elegant way.
Angular interprets a class as a service based on the @Injectable
decorator. We also have to define our service in the provider for Angular to start using it with the concept of Dependency Injection or DI.
We can either provide the service on the component itself which will then have the same instance of that service for that particular component and its child components until defined and overridden again in a child component.
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [AccountsService]
})
Another way is to define it in the provider's array in app.module.ts so that a global instance of that service is available throughout the application until defined and overridden again in a component.
@NgModule({
...
imports: [...],
providers: [AccountsService],
...
})
Before we dig deeper into services let’s understand Dependency Injection (also called DI in short).
As per Angular documentation:
Dependencies are services or objects that a class needs to perform its function. Dependency injection, or DI, is a design pattern in which a class requests dependencies from external sources rather than creating them.
Angular’s DI framework provides dependencies to a class upon instantiation. Use Angular DI to increase flexibility and modularity in your applications.
A service can register with any injector along the component tree. You insert the service as a provider in the @Component
metadata field: providers: []
. The service is available to the component and its children.
Another way, the providers: []
metadata exists as its own field in the @NgModule
decorator. The service is instantiable from the module to the underlying component tree.
Let’s try to understand the use of services and DI with an example.
Below is the screenshot of our application which shows 1 form to add a user activity account which consists of username and activity.
UserActivity class looks like this:
export class UserActivity{username: string;
activity: string;constructor(name, status) {
this.username = name;
this.activity = status;
}}
On entering information and triggering the (click) event of Add Account button, the user activity gets added to the below list (kept 2 user activities by default in the list).
Breakdown of the app: 1 root app component, 1 new account component to insert the new form information and the button, and 1 account component to display the user activity list.
If we observe, on entering the information in the new account component we need to communicate the same to its sibling account component. This all can be achieved without the use of services, but it will require a lot of handling and data travel like we need to store the new user information and then emit the same via an event emitter at the click of a button. This event will reach the parent app component which is further subscribed, stored and passed as a property to its other child ie. the account component. Where the account component will store this new data and display it.
All this data travel, creation of local objects, event emitters, and methods can be reduced if we create one service say AccountsService and let it act as the central place for managing the user activity list and the logic to push the new user activity to the existing list.
In the below code, all we have to do is create a class AccountsService. Injectable decorator, it’s up to you to define it or not. Generally, it is used when you are injecting one service into another, but Angular suggests by convention to use it on all the services.
I have declared my service in the app.module.ts providers. Now in the constructor of any component, I can directly inject the instance of my service following the concept of DI, as explained above.
Now we can push the new user account by calling the service addUserActivity() and passing on the data with a click of the button whose (click) event is bound to onCreateAccount().
Now at the root AppComponent, we inject our service again and store the userActivity list in a local property which is passed to the account component that further displays the list of user activities.
We use the ngFor directive to loop through the local userActivity list and pass the information of each item further to the account component as implemented below:
Further in the account component, we can bind the account and id which we can further bind to the HTML using string interpolation.
import { Component, Input } from '@angular/core';
import { UserActivity } from '../model/useractivity.model';@Component({
selector: 'app-account',
templateUrl: './account.component.html',
styleUrls: ['./account.component.css']
})
export class AccountComponent {@Input() account: UserActivity;
@Input() id: number;constructor() {}}