Angular Service Injection
Oct 11 2018 05:26pm | | Share

Angular Service Injection

By Susan Wang

What is a service?

Angular applications are built from components. With more and more components being created, an application can become very complicated. Parent and child components can communicate by passing input and output parameters. However, it is difficult for other components to communicate with each other. Therefore, angular services were invented as a helpful tool to achieve cross-component communication.

 

The wrong way to use services

Let’s first look at a common mistake made by most beginner Angular developers. In the following example, we create a service AccountService and inject it into AccountComponent.

 

AccountService:


export class AccountService {

account = [

         {accountId: 1, status: new},

         {accountId: 2, status: completed},

         {accountId: 3, status: cancelled}

];
 }

 

AccountComponent:


export class AccountComponent{

         accountSvc: AccountService;

         contructor() {

                  this.accountSvc = new AccountService();

}

}

 

It’s easy to inject the service by just creating an instance of the service in the component. In this way, each component has its own service instance and the data in the service instances is independent. This is not the correct way to use a service in Angular. Angular has its own way to manage and utilize services through the components. Once you get used to it, services are very useful, and you can use it to your advantage.

 

Hierarchical Injector

In Angular, we make services available by dependency injection. The Angular dependency injector is actually a hierarchical injector, which means if we provide the service to a component, all the child components and the child components of the child components receive the same instance of this service.

 

There are three different levels where we can provide services.

1. app.module.ts: If we provide a service in app.module.ts, the service is at the highest level and the same instance of this service is shared by the whole application.

 


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

 

import { AppComponent } from './app.component';

import { AccountService } from './services/AccountService';

 

@NgModule({

  declarations: [

    AppComponent

  ],

  imports: [],

  providers: [AccountService],

  bootstrap: [AppComponent]

})

export class AppModule { }

 

2. app.component.ts: We can also provide the service in app.component.ts. In this way, the same service instance is shared only to other components and not any other services.

 


import { Component } from '@angular/core';

import { AccountService } from './services/AccountService';

 

@Component({

  selector: 'app-root',

  templateUrl: './app.component.html',

  styleUrls: ['./app.component.css'],

  providers: [AccountService]

})

export class AppComponent {

  title = 'app';

}

 

3. Component level: The service can be provided to a component and all its child components share the same instance of the service.

 


import { Component } from '@angular/core';

import { AccountService } from './services/AccountService';

 

@Component({

  selector: 'app-dashboard',

  templateUrl: './dashboard.component.html',

  styleUrls: ['./dashboard.component.css'],

  providers: [AccountService]

})

export class DashboardComponent {

}

 

With the three options described above, we can provide services at different levels based on specific requirement.

 

Cross-component communication

After providing the service, we need to inject it into the component to actually use it. We have two components DashboardComponent and AccountComponent and we inject AccountService into both components. Here we assume AccountService is provided at the whole application level, i.e. in app.module.ts:

 


export class DashboardComponent {

  constructor(private accountSvc: AccountService) {        

  }

}

 

export class AccountComponent {

  constructor(private accountSvc: AccountService) {        

  }

}

 

Now, in both components, we can access AccountService by this.accountSvc. The AccountService is defined as,

 


export class AccountService {

accounts = [

         {accountId: 1, status: new},

         {accountId: 2, status: completed},

         {accountId: 3, status: cancelled}

];

 

addAccount() {

}

 

getAccounts() {

}

}

 

Both components can get accounts by calling this.accountSvc.getAccounts(); Other components can get accounts from AccountService in the same way. The service behaves as a data management center.

 

One tricky question: How do we notify other components if some component calls addAccount() and the accounts array has been updated? In Angular, we can use EventEmitter to emit the change event and all components which listen to the change event will get notified. In our example, we add EventEmitter in AccountService,

 


import { EventEmitter} from '@angular/core';

 

export class AccountService {

accounts = [

         {accountId: 1, status: new},

         {accountId: 2, status: completed},

         {accountId: 3, status: cancelled}

];

         accountsUpdated = new EventEmitter<any >();

 

addAccount() {

         ….

         this.accountsUpdated.emit(this.accounts);

}

 

getAccounts() {

}

}

 

Then, add the following lines to components listening to the change event,

 


export class DashboardComponent {

  constructor(private accountSvc: AccountService) {        

  }

 

  ngOnInit() {

    this.accountSvc. accountsUpdated.subscribe(

      accounts => this.accounts = accounts;

);

   this.accounts = this.accountSvc.getAccounts();

  }

}

 

export class AccountComponent {

  constructor(private accountSvc: AccountService) {        

  }

 

  ngOnInit() {

    this.accountSvc. accountsUpdated.subscribe(

      accounts => this.accounts = accounts;

);

   this.accounts = this.accountSvc.getAccounts();

  }

}

 

Great! Now both components will get notified if the accounts array in AccountService is updated. The AccountService manages all the accounts information and all components can communicate through the AccountService. Since AccountService is shared by all components and the whole application only holds one instance of the service, the accounts data will be synchronized through all components.

 

 

Angular 6 @Injectable

With Angular 6+, we can now provide application-wide services in a different way. Instead of adding services into app.module.ts, we can now config services by using @Injectable({ providedIn: ‘root’ }) decorator to indicate the services are provided at whole application level.


 

@Injectable({providedIn: 'root'})

export class MyService { ... }

 

We can also provide the service at module level. In this case, the service instance is shared within the module:

 


@Injectable({providedIn: ‘myModule})

export class MyService { ... }

 

This new syntax is optional and the traditional syntax is still valid. The advantage of using the new syntax is that it allows lazy loading services and redundant code can be removed automatically by Angular. It results in performance improvement in complex applications.

 

Conclusion

In this blog, we discussed what an Angular service is. We learned three different ways to provide services in three levels as well as how to inject services into components. We also discussed how components communicate in Angular with the help of services. Angular has its own amazing way to create and manage service instances. Do you have any experience using Angular Service? How do you like it?