Skip to main content

Angular Services and Dependency Injection Explained

Components are responsible for the data that renders into the template. Having external services to draw upon can simplify this responsibility. Plus, encapsulating extraneous is much easier to maintain.

Delegating too many responsibilities onto a single component can complicate the component class. And what if these responsibilities applied to several components? Copying and pasting such logic is extremely poor practice. Any future changes to the logic would be harder to implement and test.

Angular meant to curb this issue with services and dependency injection. Both concepts work together to provide modular functionality.

Components do not need to provide any extraneous information either. A services imports what it needs to function on behalf of the components it services. The components only need to instantiate the service. From there they service their own needs with the instantiated service instance.

As for testing and future modification, all the logic is in one place. The service instantiates from its source. Tests and modifications to the source apply anywhere the service is injected.

Introduction to Services

A service is a type of schematic available in Angular. It is generatable by the command-line interface (CLI): ng generate service [name-of-service]. Replace [name-of-service] with a preferable name. The CLI command yields the following.

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

@Injectable({
  providedIn: 'root'
})
export class LoggerService {
  constructor() { }
}

The logic of a service is distinct within its class. Angular interprets a class as an injectable service based off the @Injectable decorator. Injectable services must register with an injector.

The component instantiates a service while the injector provides that instance. Keep reading into the next section for more on injectors.

The @Injectable metadata field providedIn: ‘root’ targets the root module of the current application (app.module.ts). It registers the service with the module’s injector so that it can inject that service into any of its children.

Injectors are the building blocks of Angular’s dependency injection system. Injectors are a good place to focus your attention before continuing with services.

Injectors

An application, beginning with app.module.ts, contains a hierarchy of injectors. They exist alongside each module and component in the application tree.

The green circles indicate injectors. They provide service instances to instantiating components. Depending on which injector a service is registered with, it may or may not be available to a component.

Services registered at the root of the app (app.module.ts) are available to all components. An injector for a component may not have a certain service registered. If that is the case and the component requests its instantiation, the injector will defer to its parent. This trend continues until either reaching the root injector or the service is found.

Looking at the diagram, say that a service registers at point B’s injector. All components at point C and down will not be able to access the service registered at B’s injector. Injectors will never defer to their children for a service instance.

Dependency Injection

There are multiple ways to register a service with an application’s injectors.

The providedIn: ‘root’ metadata field of @Injectable provides the most recommended approach. This metadata field released with Angular 6.

As mentioned before, providedIn: ‘root’ registers a service with the root module injector. It is instantiable across the entire application as a result.

The novelty of providedIn: ‘root’ is tree-shaking. If the service is unused despite its registration, it gets shaken from the application at run-time. That way it does not consume any resources.

The other two ways are more direct and traditional. Granted, they do not offer tree-shaking.

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

In the third registration strategy, the providers: [] metadata exists as its own field in the @NgModule decorator. The service is instantiable from the module to the underlying component tree.

Remember that unlike with providedIn: ‘root’@NgModule registration does not offer tree-shaking. Both strategies are otherwise identical. Once a service registers with @NgModule, it consumes resources even if left unused by the application.

Services Continued

Writing an actual service comes next. To recap, services handle certain functions on behalf of an application’s components.

Services excel at handling common operations. They spare components the responsibility by doing so. It saves time not having to re-write common operations across multiple components. It is also more testable because the code is in one place. Changes only need to happen in one place without having to search elsewhere.

Use Cases

A couple examples goes a long way towards a complete understanding of services.

  • console logs
  • API requests

Both are common across most applications. Having services to handle these operations will reduce component complexity.

Console Logs

This example builds up from the base @Injectable skeleton. The skeleton is available through executing the CLI (ng generate service [name-of-service]]).

// services/logger.service.ts

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

interface LogMessage {
  message:string;
  timestamp:Date;
}

@Injectable({
  providedIn: 'root'
})
export class LoggerService {
  callStack:LogMessage[] = [];

  constructor() { }

  addLog(message:string):void {
      // prepend new log to bottom of stack
      this.callStack = [{ message, timestamp: new Date() }].concat(this.callStack);
  }

  clear():void {
      // clear stack
      this.callStack = [];
  }

  printHead():void {
      // print bottom of stack
      console.log(this.callStack[0] || null);
  }

  printLog():void {
      // print bottom to top of stack on screen
      this.callStack.reverse().forEach((logMessage) => console.log(logMessage));
  }

  getLog():LogMessage[] {
      // return the entire log as an array
      return this.callStack.reverse();
  }
}

LoggerService registers with the root module through the @Injectablemetadata. Thus it can instantiate in the app.component.html.

// app.component.ts

import { Component, OnInit } from '@angular/core';
import { LoggerService } from './services/logger.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  logs:object[] = [];

  constructor(private logger:LoggerService) { }

  updateLog():void {
      this.logger.printHead();
      this.logs = this.logger.getLog();
  }

  logMessage(event:any, message:string):void {
      event.preventDefault();

      this.logger.addLog(`Message: ${message}`);
      this.updateLog();
  }

  clearLog():void {
      this.logger.clear();
      this.logs = [];
  }

  ngOnInit():void {
      this.logger.addLog(“View Initialized”);
      this.updateLog();
  }
}

The template HTML provides further insight into the component’s use of LoggerService.

<!-- app.component.html -->

<h1>Log Example</h1>

<form (submit)="logMessage($event, userInput.value)">
  <input #userInput placeholder="Type a message...">
  <button type="submit">SUBMIT</button>
</form>

<h3>Complete Log</h3>
<button type="button" (click)="clearLog()">CLEAR</button>
<ul>
  <li *ngFor="let log of logs; let i=index">{{ logs.length - i }} > {{ log.message }} @ {{ log.timestamp }}</li>
</ul>

This has the feel of a ToDo application. You can log messages and clear the log of messages. Imagine if all the logic from the service was shoved into AppComponent! It would have complicated the code. LoggerService keeps the log-related code encapsulated from the core AppComponent class.

Fetch Requests

Here is one more example worth playing around with. This example is possible thanks to typicode’s JSONPlaceholder1. The API is public and free to use.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

// https://jsonplaceholder.typicode.com
// public API created by typicode @ https://github.com/typicode

interface Post {
  userId:number;
  id:number;
  title:string;
  body:string;
}

@Injectable({
  providedIn: 'root'
})
export class PlaceholderService {
  constructor(private http:HttpClient) { }

  getPosts():Observable<Post[]> {
      return this.http.get('https://jsonplaceholder.typicode.com/posts');
  }

  getPost(id:number):Observable<Post> {
      return this.http.get(`https://jsonplaceholder.typicode.com/posts/${id}`);
  }
}

This is more of a stand-alone piece than a fully fleshed out example. Fetch requests tend to work better as an injectable service. The alternative is an over-complicated component. The injected class subscribes to what the PlaceholderService pre-configures.

Conclusion

Services and dependency injection are very useful together. They allow developers to encapsulate common logic and inject across multiple different components. This alone is a massive convenience for any future maintenance.

Injectors work as intermediaries. They mediate between instantiating components and a reservoir of registered services. Injectors offer these instantiable services to their branch children.

See the next few links for more information on services and dependency injection.

Comments

Popular posts from this blog

How to use Ngx-Charts in Angular ?

Charts helps us to visualize large amount of data in an easy to understand and interactive way. This helps businesses to grow more by taking important decisions from the data. For example, e-commerce can have charts or reports for product sales, with various categories like product type, year, etc. In angular, we have various charting libraries to create charts.  Ngx-charts  is one of them. Check out the list of  best angular chart libraries .  In this article, we will see data visualization with ngx-charts and how to use ngx-charts in angular application ? We will see, How to install ngx-charts in angular ? Create a vertical bar chart Create a pie chart, advanced pie chart and pie chart grid Introduction ngx-charts  is an open-source and declarative charting framework for angular2+. It is maintained by  Swimlane . It is using Angular to render and animate the SVG elements with all of its binding and speed goodness and uses d3 for the excellent math functio...

Understand Angular’s forRoot and forChild

  forRoot   /   forChild   is a pattern for singleton services that most of us know from routing. Routing is actually the main use case for it and as it is not commonly used outside of it, I wouldn’t be surprised if most Angular developers haven’t given it a second thought. However, as the official Angular documentation puts it: “Understanding how  forRoot()  works to make sure a service is a singleton will inform your development at a deeper level.” So let’s go. Providers & Injectors Angular comes with a dependency injection (DI) mechanism. When a component depends on a service, you don’t manually create an instance of the service. You  inject  the service and the dependency injection system takes care of providing an instance. import { Component, OnInit } from '@angular/core'; import { TestService } from 'src/app/services/test.service'; @Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.compon...

How to solve Puppeteer TimeoutError: Navigation timeout of 30000 ms exceeded

During the automation of multiple tasks on my job and personal projects, i decided to move on  Puppeteer  instead of the old school PhantomJS. One of the most usual problems with pages that contain a lot of content, because of the ads, images etc. is the load time, an exception is thrown (specifically the TimeoutError) after a page takes more than 30000ms (30 seconds) to load totally. To solve this problem, you will have 2 options, either to increase this timeout in the configuration or remove it at all. Personally, i prefer to remove the limit as i know that the pages that i work with will end up loading someday. In this article, i'll explain you briefly 2 ways to bypass this limitation. A. Globally on the tab The option that i prefer, as i browse multiple pages in the same tab, is to remove the timeout limit on the tab that i use to browse. For example, to remove the limit you should add: await page . setDefaultNavigationTimeout ( 0 ) ;  COPY SNIPPET The setDefaultNav...