In this article, we will discuss the concept of Service in Angular 8. Angular service plays an important role to communicate with the backend layer of any application from the component level to send or retrieve data. That’s why Service is another backbone of any angular application. So, it is very important to understand why it is required and how to use it in any applications?
What is Angular Service?
In Angular Framework, services are always singleton objects which normally get instantiated only once during the lifetime of any application or module. Every Angular Service contains several methods that always maintains the data throughout the life of an application. It is a mechanism to share responsibilities within one or multiple components. As per the Angular Framework, we can develop any application using a nested relationship-based component. Once our components are nested, we need to manipulate some data within the different components. In this case, a service is the best way to handle this. Service is the best place where we can take data from other sources or write down some calculations. Similarly, services can be shared between multiple components as needed.
Angular has greatly simplified the concept of services since Angular 1.x. In Angular 1x, there were services, factories, providers, delegates, values, etc., and it was not always clear when to use which one. So, for that reason, Angular 8 simply changed the concept of Angular. There are simply two steps for creating services in Angular:
- Create a class with @Injectable decorator.
- Register the class with the provider or inject the class by using dependency injection.
In Angular, a service is used when a common functionality or business logic needs to be provided, written, or needs to be shared in a different name. Actually, a service is a totally reusable object. Assuming that our Angular application contains some of the components performing the logging for error tracking purposes, you will end up with an error log method in each of these components. As per the standard practice, it is a bad approach since we used an error log method multiple times in the application. If you want to change the semantics of error logging, then you will need to change the code in all these components, which will impact the whole application. So use a common service component for the error log features is always good. In this way, we can remove the error log method from all components and placed that code within a service class. Then components can use the instance of that service class to invoke the method. In Angular Framework, Service injection is one way of performing dependency injection.
Benefits of using Service
Angular services are single objects that normally get instantiated only once during the lifetime of the Angular application. This Angular service maintains data throughout the life of an application. It means data does not get replaced or refreshed and is available all the time. The main objective of the Angular service is to use shared business logic, models, or data and functions with multiple different components of an Angular application.
The main objective of using an Angular service is the Separation of Concern. An Angular service is basically a stateless object, and we can define some useful functions within an Angular service. These functions can be invoked from any component of the application elements like Components, Directives, etc. This will help us to divide the entire application into multiple small, different, logical units so that those units can be reusable.
How to define a Service
We can create a user-defined custom service as per our requirement. To create a service, we need to follow the below steps:
- First, we need to create a TypeScript file with proper naming.
- Next, create a TypeScript class with the proper name that will represent the service after a while.
- Use the @Injectable decorator at the beginning of the class name that was imported from the @angular/core packages. Basically, the purpose of the @Injectable is that user-defined service and any of its dependents can be automatically injected by the other components.
- Although, for design readability, Angular recommends that you always define the @Injectable decorator whenever you create any service.
- Now, use the Export keyword against the class objects so that this service can be injectable or reused on any other components.
NOTE
When we create our custom service available to the whole application through the use of a provider’s metadata, this provider’s metadata must be defined in the app.module.ts(main application module file) file. If you provide the service in the main module file, then it is visible to the whole application. If you provide it in any component, then only that component can use the service. By providing the service at the module level, Angular creates only one instance of the CustomService class, which can be used by all the components in an application.
- import { Injectable } from '@angular/core';
-
- @Injectable()
- export class AlertService
- {
- constructor()
- { }
-
- publish showAlert(message: string)
- {
- alert(message);
- }
- }
@Injectable()
@Injectable is actually a decorator in Angular Framework. Decorators are a proposed extension in JavaScript. In short, a decorator provides the ability for programmers to modify or use methods, classes, properties, and parameters. In angular, every Injectable class actually behaves just like a normal class. That’s why the Injectable class does not have any special lifecycle in the Angular framework. So, when we create an object of an Injectable class, the constructor of that class simply executed just like ngOnInit() of the component class. But, in the case of Injectable class, there is no chance to define destructor because, in JavaScript, there is no concept of the destructor. So, in simple work, the Injectable service class can’t be the destroyer. If we want to remove the instance of the service class, then we need to remove the reference point of dependency injection related to that class.
@Injectable decorator indicates Angular Framework that this particular class can be used with the dependency injector. @Injectable decorator is not strictly required if the class has other Angular decorators like a Component decorator, directive decorator, etc. on it or does not have any dependencies.
Most important is that any class needs to define with @Injectable() decorator so that it can be injected into the application.
- @Injectable()
- export class SampleService
- {
- constructor()
- {
- console.log('Sample service is created');
- }
- }
Similar to the .NET MVC Framework, Angular 8 also provides support for the Dependency Injection or DI. We can use the component constructor to inject the instances of the service class. Angular 8 provides the provider metadata in both Module level and Component level which can be used to perform Automatic Dependency Injection of any Injectable Service at the runtime.
What is Dependency Injection?
Dependency Injection always is one of the main benefits of Angular Framework. Due to these benefits, Angular Framework is receiving much more appreciation and acceptation among developers which can not achieve by other related client-side frameworks. With the help of this feature, we can inject any types of dependency like service, external utility module in our application module. For doing this, we do not even want to know how those dependency modules or services have been developed.
So, Angular Framework has its own mechanism for the Dependency Injection system. In this system, every angular module has its related own injector metadata values. According to that, the injector of each module is responsible for creating the dependent object reference point and then it will inject in the module we required. Actually, every dependency behaves like key-pair values where token act as a key and the instance of the object which needs to be injected act as a value. But in spite of this cool mechanism, there are some problems in the existing Dependency mechanism as below -
- Internal cache
In Angular, every dependency object created as a singleton object. So, when we inject any service class as a dependent object, then the instance of that service class created once for the entire application lifecycle.
- Namespace collision
In Angular, we can’t be injected two different service classes from two different modules with the same name. As an example, suppose we create a service called user service for our own module and we inject that service. Now, suppose we import an EmployeeModule which contains the same name service called UserService and we also want to inject that. Then that can’t be done since the same name service already injected in the application.
- Built into the framework
In Angular Framework, Dependency Injection is totally tightly coupled with Angular Modules. We can’t decouple the Dependency Injection from the Angular module as a standalone system.
In Angular Framework, Dependency Injection contains three sections like Injector, Provider & Dependency.
- Injector
The main purpose of the using Injector section is the expose an object or APIs which basically helps us to create instances of the dependent class or services.
- Provider
A Provider is basically acting as an Instructor or Commander. It basically provides instruction to the injector about the process of creating instances of the dependent objects. The provider always taken the token value as input and then map that token value with the newly created instances of the class objects.
- Dependency
Dependency is the processor type that basically identifies the nature of the created objects.
So as per the above discussion, we can perform the below tasks using the Dependency Injection in Angular Framework,
- We can create instances of the service classes in the constructor level using the provider metadata.
- We can use providers to resolve the dependencies between module-level providers and component level providers.
- The dependency injection process creates the instances of the dependent class and provides the reference of those objects when we required in the code.
- All the instances of the dependency injected objects are created as a Singletone object.
What is Provider?
So now, one question arises after the above discussion: what are these providers that injectors register at each level? A provider is an object or class that Angular uses to provide something we want to use:
- A class provider always generates or provides an instance of a class
- A factory provider generates or provides whatever returns when we run a specified function
- A value provider does not need to take up action to provide the result, it just returns a value
Sample of a Class
- export class testClass {
- public message: string = "Hello from Service Class";
- public count: number;
- constructor() {
- this.count=1;
- }
- }
Okay, that’s the class. Now, we need to use the Injectable() decorator so that Angular Framework can register that class a provider and we can use the instance of that class within the application. We’ll create a component that will serve as the root component of our application. Adding the testClass provider to this component is straightforward:
- Import testClass
- Add it to the @Component() decorator property
Add an argument of type “testClass” to the constructor
- import { Component } from "@angular/core";
- import { testClass} from "./service/testClass";
-
- @Component({
- module : module.id,
- selector : ‘test-prog’,
- template : ‘<h1>Hello {{_message}}</h1>’,
- providers : [testClass]
- })
-
- export class TestComponent
- {
- private _message:string="";
- constructor(private _testClass : testClass)
- {
- this._message = this._testClass.message;
- }
- }
Under the covers, when Angular instantiates the component, the DI system creates an injector for the component that registers the testClass provider. Angular then sees the testClass type specified in the constructor’s argument list, looks up the newly registered testClass provider, and uses it to generate an instance that it assigns to “_testClass”. The process of looking up the testClass provider and generating an instance to assign to “_testClass” is all Angular.
Inject a Service into a Module
We can inject the Angular Service in Application Level or Module Level. The provider's property of NgModule decorator gives us the ability to inject a list number of Angular services into the module level. It will provide a single instance of the Angular Service across the entire application and can share the same value within different components.
- @NgModule({
- imports: [ BrowserModule, FormsModule ],
- declarations: [ AppComponent, ParentComponent, ChildComponent ],
- bootstrap: [ AppComponent ],
- providers: [ SimpleService, EmailService ] ①
- })
- class AppModule { }
Inject a Service into a Component
We can also inject the Angular Service into the Component Level. Similarly like NgModule, providers' property of Component decorator gives us the ability to inject a list number of Angular services within that particular component. It will provide a single instance of the Angular Service across the entire component along with the child component.
- @Component({
- selector: 'parent',
- template: `...`,
- providers: [ EmailService ]
- })
- class ParentComponent {
- constructor(private service: EmailService) { }
- }
Demo 1 - Basic Service
Now in this demo, we will demonstrate how to use Injectable Service in Angular. For this purpose, we will develop an entry form related to the Student Information and on submit button click, we will pass those data into service so that it will store that data.
app.component.ts - import { Component, OnInit } from '@angular/core';
- import { StudentService } from './app.service';
-
- @Component({
- selector: 'app-root',
- templateUrl: './app.component.html',
- styleUrls : ['./custom.css']
- })
- export class AppComponent implements OnInit {
- private _model: any = {};
- private _source: Array<any>;
-
- constructor(private _service: StudentService) {
- this._source = this._service.returnStudentData();
- }
-
- ngOnInit(): void {
- }
-
- private submit(): void {
- if (this.validate()) {
- this._service.addStudentData(this._model);
- this.reset();
- }
- }
-
- private reset(): void {
- this._model = {};
- }
-
- private validate(): boolean {
- let status: boolean = true;
- if (typeof (this._model.name) === "undefined") {
- alert('Name is Blank');
- status = false;
- return;
- }
- else if (typeof (this._model.age) === "undefined") {
- alert('Age is Blank');
- status = false;
- return;
- }
- else if (typeof (this._model.city) === "undefined") {
- alert('City is Blank');
- status = false;
- return;
- }
- else if (typeof (this._model.dob) === "undefined") {
- alert('dob is Blank');
- status = false;
- return;
- }
- return status;
- }
- }
app.component.html
- <div style="padding-left: 20px">
- <h2>Student Form</h2>
- <table style="width:80%;">
- <tr>
- <td>Student Name</td>
- <td><input type="text" [(ngModel)]="_model.name" /></td>
- </tr>
- <tr>
- <td>Age</td>
- <td><input type="number" [(ngModel)]="_model.age" /></td>
- </tr>
- <tr>
- <td>City</td>
- <td><input type="text" [(ngModel)]="_model.city" /></td>
- </tr>
- <tr>
- <td>Student DOB</td>
- <td><input type="date" [(ngModel)]="_model.dob" /></td>
- </tr>
- <tr>
- <td></td>
- <td>
- <input type="button" value="Submit" (click)="submit()" />
- <input type="button" value="Reset" (click)="reset()" />
- </td>
- </tr>
- </table>
- <h3>Student Details</h3>
- <div class="ibox-content">
- <div class="ibox-table">
- <div class="table-responsive">
- <table class="responsive-table table-striped table-bordered table-hover">
- <thead>
- <tr>
- <th style="width:40%;">
- <span>Student's Name</span>
- </th>
- <th style="width:15%;">
- <span>Age</span>
- </th>
- <th style="width:25%;">
- <span>City</span>
- </th>
- <th style="width:20%;">
- <span>Date of Birth</span>
- </th>
- </tr>
- </thead>
- <tbody>
- <tr *ngFor="let item of _source; let i=index">
- <td><span>{{item.name}}</span></td>
- <td><span>{{item.age}}</span></td>
- <td><span>{{item.city}}</span></td>
- <td><span>{{item.dob}}</span></td>
- </tr>
- </tbody>
- </table>
- </div>
- </div>
- </div>
- </div>
app.service.ts
- import { Injectable } from "@angular/core";
-
- @Injectable()
- export class StudentService {
- private _studentList: Array<any> = [];
-
- constructor() {
- this._studentList = [{name:'Amit Roy', age:20, city:'Kolkata', dob:'01-01-1997'}];
- }
-
- returnStudentData(): Array<any> {
- return this._studentList;
- }
-
- addStudentData(item: any): void {
- this._studentList.push(item);
- }
- }
app.module.ts
- import { BrowserModule } from '@angular/platform-browser';
- import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
- import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
- import { AppComponent } from './app.component';
- import { StudentService } from './app.service';
-
- @NgModule({
- declarations: [
- AppComponent
- ],
- imports: [
- BrowserModule, FormsModule, ReactiveFormsModule
- ],
- providers: [StudentService],
- bootstrap: [AppComponent],
- schemas: [NO_ERRORS_SCHEMA]
- })
- export class AppModule { }
Now check the browser for the output,
Demo 2 - Inject Service into a Module
Now, in this demo, we will discuss the module level injection of any Angular service. For that purpose, we will create two-component as a parent child-related i.e. Parent Component and Child Component. And then use the selector of the parent component in our Root component. For showing the module level service instance, we use two instances of Parent component within the Root component. The code samples as below,
parent.component.ts
- import { Component, OnInit } from '@angular/core';
- import { DemoService } from './app.service';
-
- @Component({
- selector: 'parent',
- templateUrl: './parent.component.html',
- styleUrls : ['./custom.css']
- })
- export class ParentComponent implements OnInit {
-
- constructor(private demoService:DemoService){
-
- }
-
- ngOnInit(){
- }
- }
parent.component.html
- <div class="parent">
- <p>Parent Component</p>
- <div class="form-group">
- <input type="text" class="form-control" name="value" [(ngModel)]="demoService.message">
- </div>
- <child></child>
- </div>
child.component.ts
- import { Component, OnInit } from '@angular/core';
- import { DemoService } from './app.service';
-
- @Component({
- selector: 'child',
- templateUrl: './child.component.html',
- styleUrls : ['./custom.css']
- })
- export class ChildComponent implements OnInit {
- constructor(private demoService:DemoService){
- }
-
- ngOnInit(){
- }
- }
child.component.html
- <div class="child">
- <p>Child Component</p>
- {{ demoService.message }}
- </div>
app.component.ts
- import { Component, OnInit } from '@angular/core';
-
- @Component({
- selector: 'app-root',
- templateUrl: './app.component.html',
- styleUrls : ['./custom.css']
- })
- export class AppComponent implements OnInit {
-
- ngOnInit(){
-
- }
- }
app.component.html
- <div style="padding-left: 20px;padding-top: 20px; width: 500px;">
- <div class="row">
- <div class="col-xs-6">
- <h2>Parent Componet - 1</h2>
- <parent></parent>
- </div>
- <div class="col-xs-6">
- <h2>Parent Componet - 2</h2>
- <parent></parent>
- </div>
- </div>
- </div>
custom.css
- .parent{
- background-color: bisque;
- font-family: Arial, Helvetica, sans-serif;
- font-weight: bolder;
- font-size: large;
- }
-
- .child{
- background-color:coral;
- font-family: Georgia, 'Times New Roman', Times, serif;
- font-weight: bold;
- font-size: medium;
- }
app.module.ts
- import { BrowserModule } from '@angular/platform-browser';
- import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
- import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
- import { AppComponent } from './app.component';
- import { ParentComponent } from './parent.component';
- import { ChildComponent } from './child.component';
- import { DemoService } from './app.service';
-
- @NgModule({
- declarations: [
- AppComponent,ParentComponent,ChildComponent
- ],
- imports: [
- BrowserModule, FormsModule, ReactiveFormsModule
- ],
- providers: [DemoService],
- bootstrap: [AppComponent],
- schemas: [NO_ERRORS_SCHEMA]
- })
- export class AppModule { }
Now check the output into the browser,
In the above example, we see that if we input some text in any one parent component input box, it will automatically update the related nested child component along with another parent component also. This is occurred because of the DemoService is injected into the Module level. So it creates a single-tone instance of that service and that instance is available across the entire application.
Demo 3 - Inject Service into a Component
In the previous sample, we see how to inject any Angular service into Module level and how if share the same data entered into one component to all components across the module. Now, in this demo, we will demonstrate what will happen if we inject any service into the Component level. For that purpose, we just make the below changes in the parent.component.ts file,
- import { Component, OnInit } from '@angular/core';
- import { DemoService } from './app.service';
-
- @Component({
- selector: 'parent',
- templateUrl: './parent.component.html',
- styleUrls : ['./custom.css'],
- providers:[DemoService]
- })
- export class ParentComponent implements OnInit {
- constructor(private demoService:DemoService){
- }
-
- ngOnInit(){
- }
- }
Now, check the browser for the output,
In the above example, we clearly see that when we type any input in the parent 1 component, it will not automatically be shared by another instance of the parent component. Due is occurred due to the Component level injection of the DemoService.
Conclusion
In this article, we discussed another important feature of Angular Framework i.e. Angular Service. Also, we discussed the basic concept of Angular along with its benefit and how to use it in an Angular Application. Now, in the next article, we will how to handle the ajax request in an Angular Application.