Showing posts with label Angular Service. Show all posts
Showing posts with label Angular Service. Show all posts

Monday, March 18, 2024

Injector Hierarchy and Service Instances in Angular

In the Service in Angular With Examples post we saw an example of creating service where provider metadata was provided with in the @Injectable() decorator.

@Injectable({
 providedIn: 'root',
})

But that is not the only way to register a provider in Angular, there is a complete injector hierarchy and the number of service instances created depends on how you register the provider.


Providing services at different levels

For any service you are going to use in your Angular app you must register at least one provider. The provider can be part of the service's own metadata, making that service available everywhere, or you can register providers with specific modules or components. There are the following three ways for registering a provider for the service.

1. In the @Injectable() decorator for the service itself. By default, the Angular CLI command ng generate service registers a provider with the root injector for your service by including provider metadata in the @Injectable() decorator.

@Injectable({
 providedIn: 'root',
})

When you provide the service at the root level, Angular creates a single, shared instance of the Service and injects it into any class that asks for it. As per Angular docs Using the @Injectable() providedIn property is preferable to the @NgModule() providers array because that allows Angular to optimize an app by removing the service from the compiled app if it isn't used, this process of optimization is knows as tree-shaking.

2. You can also register a provider with a specific NgModule. In that case same servcie instance is available to all components in that NgModule.

For example configuring an app-wide provider in the @NgModule() of AppModule. You will have to use the providers property of the @NgModule() decorator.

@NgModule({
  providers: [LoggerService],
  bootstrap: [AppComponent]
})
export class AppModule { }

3. You can also register a provider at the component level, in that case you get a new instance of the service with each new instance of that component.

To register a service provider at the component level you can use the providers property of the @Component() decorator.

@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css'],
  providers:  [ LoggerService ]
})
export class UserComponent implements OnInit {
 ..
 ..
}

Injector hierarchies in Angular

Based on these types of service providers there are two injector hierarchies in Angular.

  • ModuleInjector hierarchy— Configure a ModuleInjector in this hierarchy using an @NgModule() or @Injectable() annotation. A single service instance will be used at the root level or modulr level based upon the configuration used.
  • ElementInjector hierarchy— Angular creates ElementInjectors implicitly for each DOM element. When you provide services in a component, that service is available via the ElementInjector at that component instance. It will also be available at child components if you are not providing it again in the child component using the providers property.

Service provider example using @Injectable() decorator

In the post Service in Angular With Examples there is an example of creating a LoggerService which is decorated with @Injectable() decorator to provide the metadata that allows Angular to inject it into a component as a dependency.

Service provider example using @NgModule

Let’s see the same example by registering service in providers array of @NgModule.

LoggerService class

Now @Injectable decorator is not used in LoggerService class.

export class LoggerService {
  constructor() { }
  log(msg: string) { 
    console.log(msg); 
  }
  error(msg: string) { 
    console.error(msg); 
  }
}

Service is registered in the AppModule class now, refer the providers properties of @NgModule decorator.


import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { UserComponent } from './user/user.component';
import { LoggerService } from './services/logger.service';

@NgModule({
  declarations: [
    AppComponent,
    UserComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [LoggerService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Service provider example at component level

In an example for registering provider at component level we’ll create an Angular application that shows the existing user and has a UI to add a new User.

Angular injector hierarchy

When a new user is added and submit button is clicked that user details should be added to the list of existing users.

Angular injector and service instance

After submit button is clicked.

In the example there is a UserService class that has an array of users and a function to add new user.

import { User } from '../user/user.model';

export class UserService {
  users = [new User('Jack', 62, new Date('2005-03-25')),
  new User('Olivia', 26, new Date('2014-05-09')),
  new User('Manish', 34, new Date('2010-10-21'))] ;

  addUser(user: User){
    this.users.push(user);
  }
}

For User objects a User model class is used.

export class User {
  name : string;
  age : number;
  joinDate : Date;
  constructor(name: string, age : number, joinDate : Date) {
    this.name = name;
    this.age = age;
    this.joinDate  = joinDate;
  }
}

AppComponent class

This is the component class where UserService is configured with in the providers array. In the ngOnInit() method all the existing users are assigned to the users variable, which is an array of type User, by calling the UserService.

import { Component, OnInit } from '@angular/core';
import { User } from './user/user.model';
import { UserService } from './services/user.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'], 
  providers: [UserService]
})
export class AppComponent implements OnInit{
  users: User[] = [];
  constructor(private userService: UserService){}
  ngOnInit(){
    this.users = this.userService.users;
  }
}

app.component.html

Template has selector tags for the child components.

<div class="container">
  <div class="row">
    <div class="col-xs-12 col-md-8">
      <app-adduser></app-adduser>
      <h3>User Details</h3>
      <app-user *ngFor="let u of users" [user]="u"></app-user>
    </div>
  </div>
</div>

AddUserComponent

In the AddUserComponent which is the child component of the AppComponent, UserService is provided again. There is also a method addUser() that gets the entered value for the new User, create a User object and call the addUser() method of the userService to add the entered user to the array of existing users.

import { User } from './user.model';

@Component({
  selector: 'app-adduser',
  templateUrl: './adduser.component.html',
  styleUrls: ['./adduser.component.css'],
  providers: [UserService]
})
export class AddUserComponent {
 
  constructor(private userService: UserService){}

  addUser(name: HTMLInputElement, age: HTMLInputElement, date: HTMLInputElement){
    this.userService.addUser(new User(name.value, Number(age.value), new Date(date.value)));
    // resetting input elements
    name.value = '';
    age.value = '';
    date.value = '';
  }
}
adduser.component.html

HTML template for providing a UI to add new user. It has three fields for name, age and joining date and a submit button. There is also click event binding calling the addUser() method.

<div class="row">
  <div class="col-xs-12 col-md-8">
    <div class="form-group">
      <label for="name">Name:</label>
      <input class="form-control" placeholder="Enter name" id="name" #enteredname>
    </div>
    <div class="form-group">
      <label for="age">Age:</label>            
      <input class="form-control" placeholder="Enter age" id="age" #enteredage>
    </div>      
    <div class="form-group">
      <label for="joindate">Joining Date:</label>    
      <input class="form-control" placeholder="Enter joining date" id="joindate" #entereddate>
    </div>
    <button (click)="addUser(enteredname, enteredage, entereddate)" class="btn btn-primary">Submit</button>
  </div>
</div>
UserComponent

In the component class there is a property binding using @Input decorator. In the AppComponent template there is a for loop that binds user to this property in each iteration, that’s how user details are displayed.

import { Component, OnInit, Input } from '@angular/core';
import { User } from './user.model';

@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css']
})
export class UserComponent implements OnInit {
  @Input() user: User;
  
  ngOnInit(): void {
  }
}

user.component.html

<div class="container">
  <div class="row">
    <div class="col-xs-12 col-md-8 px-3">
      <p>{{user.name}} {{user.age}} {{user.joinDate | date:'dd/MM/yyyy'}}</p>
    </div>
  </div>
</div>

So these are all the classes and if you have got everything right then you can try to add new user. It won’t add it to the users array and you won’t see any new User details. That will happen because of the provider at the child component too. In the AddUserComponent too a provider is registered, that results in the creation of new UserService instance overriding the instance created by the provider at the AppComponent level.

If you have registered a service provider at parent component level, the created service instance is used by the child components too if they don't register their own provider. In this example since child component registers its own provider so a new service instance is created for the child component.

When you add a new user from AddUserComponent it is added to the array attached to the UserService instance of AddUserComponent. The details which are showed using the user property of UserComponent gets user from the array which is attached to the UserService instance of AppComponent. That is why new user details are not added.

You can comment the providers property in the @Component decorator of the AddUserComponent. Now only Single instance of UserService is created at the AppComponent level (Parent component), child component AddUserComponent shares the same instance.

@Component({
    selector: 'app-adduser',
    templateUrl: './adduser.component.html',
    styleUrls: ['./adduser.component.css'],
    //providers: [UserService]
  })
  export class AddUserComponent {
 ..
 ..
 }

That's all for this topic Injector Hierarchy and Service Instances in Angular. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Angular Tutorial Page


Related Topics

  1. Angular - Call One Service From Another
  2. Creating New Component in Angular
  3. Angular ngFor Directive With Examples
  4. Angular @Input and @Output Example
  5. Angular Disable Button Example

You may also like-

  1. Angular Two-Way Data Binding With Examples
  2. Angular One-Way Data Binding Using String Interpolation
  3. Angular Routing Concepts With Example
  4. Angular Access Control CanActivate Route Guard Example
  5. ConcurrentHashMap in Java
  6. BigDecimal in Java
  7. Spring MVC - Binding List of Objects Example
  8. Bubble Sort Program in Python

Sunday, March 17, 2024

Angular - Call One Service From Another

In the Service in Angular With Examples post we saw how Components in Angular should only concentrate on view-related functionality. Any application logic like fetching data through server calls, user input validation, logging to console should be delegated to a service in Angular. It doesn’t mean that all the application logic for a component has to go in a single service, you can create separate services for different functionalities and then call one service from another in Angular application. In this post we’ll see how to call one service from another in Angular.

Angular Service that needs another service

A service in Angular may have a dependency on another service, for example you may have a DataService to get data from backend, SecurityService to secure your app, LoggerService to log to log file and these classes may have interdependency.

You don’t have to do much to configure dependency between services. A service that needs another service should have a @Injectable decorator and then you can apply the same constructor injection pattern where you provide the service dependency with in the constructor.

For example if there is a UserService that needs LoggerService for logging purpose can define the dependency in its constructor as shown below-

@Injectable({
  providedIn: 'root',
})
export class UserService {
  constructor(private loggerService: LoggerService){}
  ..
  ..
}

Call one service from another Angular example

Let’s create a full example to see how to configure dependency for another service. We’ll revisit the example Angular @Input and @Output Example that way we can also show how using service classes reduce the complexity in your application by making the communication among several components easy. You don’t have to rely that much on providing custom property binding through @Input and providing custom event binding through @Output. Service class can act like a centralized class which facilitate communication among Angular components.

In the example user names are displayed and clicking any of these names displays the user details.

Initially user names are displayed.

call one service from another

On clicking name, details are displayed.

Angular service dependency on another service

user.model.ts class

First thing is to define the User class with fields Name, Age, JoinDate.

export class User {
  name : string;
  age : number;
  joinDate : Date;
  constructor(name: string, age : number, joinDate : Date) {
    this.name = name;
    this.age = age;
    this.joinDate  = joinDate;
  }
}

user.component.html

Template for displaying user names.

<div class="row">
  <div class="col-xs-12 col-md-8 px-3">
    <p style="cursor:pointer" 
      class="list-group-item" 
      (click)="onClickUser()">
      {{user.name}}
    </p>
  </div>
</div>

UserComponent class

import { Component, OnInit, Input } from '@angular/core';
import { User } from './user.model';
import { UserService } from '../services/user.service';

@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css']
})
export class UserComponent implements OnInit {
  @Input() user: User;
  @Input() index: Number;
  constructor(private userService: UserService){}
  ngOnInit(): void {
  }
  onClickUser(){
    this.userService.indexClicked.emit(this.index);
  }
}

As you can see in UserComponent there is an input property binding for user which means it will get user data from another component. In the onClickUser() method it emits the index of the clicked user.

AppComponent class

That’s where user data is fetched from the service and passed on to the UserComponent to display user names.

import { Component, OnInit } from '@angular/core';
import { User } from './user/user.model';
import { UserService } from './services/user.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'], 
})
export class AppComponent implements OnInit{
  users: User[] = [];
  constructor(private userService: UserService){}
  ngOnInit(){
    // get all the users
    this.users = this.userService.users;
  }
}

app.component.html

<div class="container">
  <div class="row">
    <div class="col-md-5">
      <div class="list-group">
        <app-user *ngFor="let u of users; let i = index" [user]="u" [index]="i"></app-user>
      </div>
    </div>
    <div class="col-md-7">
      <appuser-detail></appuser-detail>
    </div>
  </div>
</div>

As you can see in *ngFor directive user array iteration is done and each user is assigned to the [user] property and index is also assigned to the [index] property binding.

UserService class

import { User } from '../user/user.model';
import { EventEmitter, Injectable } from '@angular/core';
import { LoggerService } from './logger.service';

@Injectable({
  providedIn: 'root',
})
export class UserService {
  // dependency on Logger Service
  constructor(private loggerService: LoggerService){}
  // User detail array
  users = [new User('Jack', 62, new Date('2005-03-25')),
  new User('Olivia', 26, new Date('2014-05-09')),
  new User('Manish', 34, new Date('2010-10-21'))] ;
  
  indexClicked = new EventEmitter<Number>();

  getUserDetails(id: Number){
    this.loggerService.log("getting user data for ID- " + id);
    return this.users[id.valueOf()];
  }
}

This is the class that has users stored in the array. There is a property indexClicked of type EventEmitter that emits a number. In the UserComponent class index of the selected user is emitted using this property. Method getUserDetails() returns user instance from the array for the passed index.

UserService has a dependency on LoggerService which is specified in the constructor. In the getUserDetails() method loggerService is used to log a message.

LoggerService class

Though it is better to use @Injectable decorator with provider for LoggerService too, just to show another way of providing service, provider for this service is registered in AppModule.

export class LoggerService {

  constructor() { }
  log(msg: string) { 
    console.log(msg); 
  }
  error(msg: string) { 
    console.error(msg); 
  }
}

AppModule class

@NgModule({
  declarations: [
    AppComponent,
    UserComponent, 
    UserDetailComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [LoggerService],
  bootstrap: [AppComponent]
})
export class AppModule { }

UserDetailComponent class

This component has the property bindings for showing user details for the selected user.

import { Component, OnInit } from '@angular/core';
import { UserService } from '../services/user.service';
import { User } from './user.model';

@Component({
    selector: 'appuser-detail',
    templateUrl: './userdetail.component.html'
  })
export class UserDetailComponent {
  id: Number;
  user: User;
  constructor(private userService: UserService){
    this.userService.indexClicked.subscribe(
      (index: Number) => {
        this.id = index;
        console.log(this.id);
        this.user = this.userService.getUserDetails(this.id);
      }
    )
  }
}

In the UserService there is a property indexClicked that emits event. In this component we subscribe to that property and get the index. Using that index userService.getUserDetails() method is called by passing the index as argument.

userdetail.component.html

<div *ngIf="user">
  <div class="mt-4 p-5 bg-light">
    <h2>User Details</h2>
    <div class="row">
      <div class="col-xs-5 px-3">
        <label>Name: </label> {{ user.name }}      
      </div>
      <div class="col-xs-4 px-3">
        <label>Age: </label> {{ user.age }}
      </div>
      <div class="col-xs-4 px-3">
        <label>Joining Date: </label> {{ user.joinDate | date:'dd/MM/yyyy'}}
      </div>
    </div>
  </div>
</div>

Here *ngIf directive is used to show the details only when there is user data to be displayed.

That's all for this topic Angular - Call One Service From Another. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Angular Tutorial Page


Related Topics

  1. Injector Hierarchy and Service Instances in Angular
  2. Angular Routing Concepts With Example
  3. Angular Disable Button Example
  4. Angular ngStyle Directive With Examples
  5. Angular Two-Way Data Binding With Examples

You may also like-

  1. Angular Attribute Binding With Examples
  2. Angular Application Bootstrap Process
  3. Nested Route (Child Route) in Angular
  4. Highlight Currently Selected Menu Item Angular Routing Example
  5. CopyOnWriteArrayList in Java
  6. Literals in Java
  7. List in Python With Examples
  8. Spring Transaction Management JDBC Example Using @Transactional Annotation

Tuesday, March 14, 2023

Service in Angular With Examples

A good design always advocates “Separation of concerns”, in Angular too that is desirable thus it is recommended that a Component should have only view-related functionality. Any application logic like fetching data through server calls, user input validation, logging to console should be delegated to a service in Angular.


Benefits of using Services in Angular

By delegating such tasks to services you have the following advantages.

  • Having lean components (concentrating only on enabling user experience).
  • Application logic in service classes that can be made available to any component thus promoting reusability.
Service in Angular

How to create a service in Angular

You can create an Angular service using following command.

ng generate service service_name

or you can use the short form of it.

ng g s service_name

For example if you want to create a service class logger inside services folder in your app directory.

ng g s services/logger

This will create logger.service.ts TypeScript file with in services folder.

Angular service example

Lets create the same logger service example. If you want to create your service classes with in a services folder then run the following command to create logger service.

ng g s services/logger

Angular CLI creates a logger.service.ts file and also do the following-

  1. Adds @Injectable decorator
  2. Registers a provider with the root injector
    @Injectable({
      providedIn: 'root'
    })
    
    

Add code for logging to console in the service class.

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

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

  constructor() { }
  log(msg: string) { 
    console.log(msg); 
  }
  error(msg: string) { 
    console.error(msg); 
  }
}

Component class

Let’s create a component user.component.ts where we’ll use this LoggerService.

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

@Component({
  selector: 'app-user',
  templateUrl: './',
  styleUrls: ['./user.component.css']
})
export class UserComponent implements OnInit {
  users: User[];
  constructor(private loggerService: LoggerService) { 
    // Adding user instances in the users array
    this.users = [new User('Jack', 62, new Date('2005-03-25')),
                  new User('Olivia', 26, new Date('2014-05-09')),
                  new User('Manish', 34, new Date('2010-10-21'))] ;

    this.loggerService.log(`Total ${this.users.length} users in array.`);
  }

  ngOnInit(): void {
  }
}

In the constructor of the component you can see that an instance of LoggerService is created which is injected by the Angular (see the next section for more details about dependency injection). You also need to import the logger.service class.

Once the service instance is available that can be used to log a message, that’s what the following statement does in the code.

this.loggerService.log(`Total ${this.users.length} users in array.`);

Template (HTML file)

Let’s quickly create user.component.html file too which shows all the users in a table.

<div class="container">
  <h2>User Details</h2>
  <table class="table table-sm table-bordered m-t-4">
    <tr>
      <th>Name</th>
      <th>Age</th>
      <th>Joining Date</th>
    </tr>
    <tr *ngFor="let user of users">
      <td>{{user.name}}</td>
      <td>{{user.age}}</td>
      <td>{{user.joinDate | date:'dd/MM/yyyy'}}</td>
    </tr>
  </table>
</div>

Now when you run it, you can see the message in the console.

Angular Service example

Service and dependency injection in Angular

You would have noticed in the example that Component class has a dependency on a LoggerService instance but that is not created explicitly. So you don’t see code as given below-

export class UserComponent implements OnInit {
  users: User[];
  loggerService: LoggerService; 
  constructor() { 
    this.loggerService = new LoggerService();
    ..
  }
..
}

Angular uses the concept of dependency injection to inject the dependencies. DI is a coding pattern in which class dependencies are injected rather than class creating them itself. In Angular, the DI framework provides declared dependencies to a class when that class is instantiated. The whole DI process in Angular can be explained in simple steps as follows-

  1. Create and register a dependency with Angular.
  2. Configure how the dependency instance is created and injected.
  3. Inject the dependency where needed.

1. Create and register a dependency with Angular

In case of providing Service as dependency first step is to create an injectable service class. To define a class as a service in Angular, decorate the class with @Injectable() decorator to provide the metadata that allows Angular to inject it into a component as a dependency.

2. Configure how the dependency instance is created and injected

Using @Injectable() decorator you just mark a service that can be injected. You also need to configure an Angular dependency injector with a provider of that service to actually inject a service.

The injector is responsible for creating service instances and injecting them into component classes. But you don't need to create injector yourself that is done by Angular itself and the first injector Angular creates is the 'root injector' that it creates during the bootstrap process.

A provider tells an injector how to create the service. You must configure an injector with a provider before that injector can create a service.

If we take our Service class there the @Injectable decorator is used like-

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

@Injectable() decorator has the ‘providedIn’ metadata option, where provider of the decorated service class is specified as root injector.

When a service is provided at the root level, Angular creates a single, shared instance of the Service and injects it into any class that asks for it. So LoggerService is a singleton service and the same instance will be injected where it is asked for.

You can register a provider using other ways too. Check this post for other options- Injector Hierarchy and Service Instances in Angular

3. Inject the dependency

When you need a Service in a Component you ask for Service to be injected, rather than creating new instance yourself. You can tell Angular to inject a dependency in a component's constructor by specifying a constructor parameter with the dependency type.

That is why constructor in UserComponent has LoggerService type as parameter.

constructor(private loggerService: LoggerService)

That's all for this topic Service in Angular With Examples. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Angular Tutorial Page


Related Topics

  1. Injector Hierarchy and Service Instances in Angular
  2. Angular - Call One Service From Another
  3. Angular Project Structure With File Description
  4. Creating New Component in Angular
  5. How to Add Bootstrap to Angular Application

You may also like-

  1. Angular CanActivateChild Guard to protect Child Routes
  2. Angular @Component Decorator
  3. Angular Custom Property Binding Using @Input Decorator
  4. Angular ngSwitch Directive With Examples
  5. Installing Anaconda Distribution On Windows
  6. @FunctionalInterface Annotation in Java
  7. Java ReentrantLock With Examples
  8. Spring WebFlux Example Using Annotation-Based Programming - Spring Web Reactive