Create a simple CRUD app

Angular 15

Dependency Injection

Use Angular Dependency Injection system to create your own custom Services, clean your component and move the logic into a separated class, also known as "Angular service"

2022-12-28
8 min read
Difficulty
angular
typescript

TOPICS

Dependency Injection for super newbies

Dependency Injecton (aka DI) is a mechanism that allow you to inject a class (also known as "Angular Service") in any component, directive, pipe or other Injectable classes rather than create an instance using the new keyword. Angular uses it extensively.

Some of the most common use cases of DI are:

  1. create a class that contains reusable utilities and routines that you can use everywhere in your project
  2. share data across components / routes
  3. delegate / move the business logic from components to a separated class

How to create an injectable class

In order to create an injectable class, also know as "Service", you simply need to decorate it with the @Injectable decorator:

@Injectable()
class UtilityService {}

Now you need to make it available in the DI system by providing it and there are several ways to define it. Anyway, since it's a tutorial for beginners we won't delve into the concept too much. What you need to know now is that the simplest way to make it globally available is using the providedIn decorator property.

@Injectable({
  providedIn: 'root'
})
class UtilityService {}

By using the providedIn: 'root' property we are saying to Angular:

"Ehi Angular, provides this class/service in the application root level so it can be injected everywhere"

So, when we provide the service at the root level, Angular creates a single shared instance of that service. It acts like a SingleTon (even if it's not) but the advantage is that this trick provides an easy way to share data across routes, components or other services.

DI is a very complex engine and provide severals other interesting features.
Read more in the Angular Documentation

How to Inject a service

The common way to inject a depencendy is by declaring it in a class constructor. For example:

@Component({
  selector: 'any-component'
})
class AnyComponent {
  constructor(private service: UtilityService) {}
}

Ok, we are now ready to use it in our little project.

Create UsersService

Our goal is just move and delegate all the business logic from the component we have created in the previous chapters to a service, in order to "clean" our code base.

Create a new folder services and place a new file users.service.ts into it.

Your app folder should look like the following:

Now we can create an empty service in users.service.ts:

AngularTypeScript
services/user.service.ts
import { Injectable } from '@angular/core';

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

Move logic from Components to Services

Now we can move all the code contained in app.component class in the new users.service file.

You can see how I did this process in the following animated GIF (its size more than 2Mb so wait a moment if it's not visible ):

Anyway, your service should look like the following:

IMPORTANT

As you can see in the script below, you don't need to call the init() method in the service constructor.
The UI (our AppComponent) should be responsible to decide when invoking it!!!

AngularTypeScript
services/user.service.ts
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { NgForm } from '@angular/forms';
import { User } from '../model/user';

@Injectable({ providedIn: 'root'})
export class UsersService {
  users: User[] = [];
  URL = 'http://localhost:3000';

  constructor(private http: HttpClient) { }

  init() {
    this.http.get<User[]>(this.URL + '/users')
      .subscribe(res => {
        this.users = res;
      });
  }

  deleteHandler(userToRemove: User) {
    this.http.delete(`${this.URL}/users/${userToRemove.id}`)
      .subscribe(() => {
        this.users = this.users.filter(u => u.id !== userToRemove.id);
      });
  }

  saveHandler(f: NgForm) {
    const user = f.value as User;

    this.http.post<User>(`${this.URL}/users/`, user)
      .subscribe((dbUser) => {
        this.users = [...this.users, dbUser]
        f.reset({ gender: '' });
      });
  }
}

Update app.component

Your component class should be empty now.
You only need to inject the new UsersService and invoke its init() method in order to load all users.

AngularTypeScript
app.component.ts
export class AppComponent {
  constructor(public usersService: UsersService) {
    usersService.init();
  }
}

Don't forget to import UserService in AppComponent:

import { UsersService } from './services/users.service';

However, although your data are loaded you still don't see anything on screen and you'll probably get a lot of errors:

In fact, you still need to update the HTML template and refer to the service methods and properties. For example:

  1. you cannot invoke the deleteHandler method from the template since it doesn't exist anymore in the component class:
<i class="fa fa-trash fa-2x pull-right" (click)="deleteHandler(u)"></i> 

but you should invoke the service method as shown below:

<i class="fa fa-trash fa-2x pull-right" (click)="usersService.deleteHandler(u)"></i> 

And do the same for the saveHandler method invoked when the form is submitted.

So we need to update the following part:

<form
  class="card card-body mt-3"
  #f="ngForm"
  (submit)="saveHandler(f)"

to the following:

<form
  class="card card-body mt-3"
  #f="ngForm"
  (submit)="usersService.saveHandler(f)"

But why do we still don't display any data on screen?

Because we also need to update the ngFor to consume data from the service instead of the component.

So we cannot get the users collection from the component as we did before:

*ngFor="let u of users" class="list-group-item"

but we should consume it from the service:

*ngFor="let u of usersService.users" class="list-group-item"

FINAL SOURCE CODE

Your final app.component should be like the following one:

AngularTypeScript
app.component.ts
import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { NgForm } from '@angular/forms';
import { User } from './model/user';
import { UsersService } from './services/users.service';

@Component({
  selector: 'app-root',
  template: `
    <div class="container">
      <h1>Users</h1>

      <form
        class="card card-body mt-3"
        #f="ngForm"
        (submit)="usersService.saveHandler(f)"
        [ngClass]="{
          'male': f.value.gender === 'M',
          'female': f.value.gender === 'F'
        }"
      >
        <input
          type="text"
          [ngModel]
          name="label"
          placeholder="Add user name"
          class="form-control"
          required
          #labelInput="ngModel"
          [ngClass]="{'is-invalid': labelInput.invalid && f.dirty}"
        >

        <select
          [ngModel]
          name="gender"
          class="form-control"
          required
          #genderInput="ngModel"
          [ngClass]="{'is-invalid': genderInput.invalid && f.dirty}"
        >
          <option value="">Select option</option>
          <option value="M">M</option>
          <option value="F">F</option>
        </select>

  
        <button
          class="btn"
          [disabled]="f.invalid"
          [ngClass]="{
            'btn-success': f.valid,
            'btn-danger': f.invalid
          }"
        >Save</button>
      </form>

      <hr>


      <ul class="list-group">
        <li
          *ngFor="let u of usersService.users" class="list-group-item"
          [ngClass]="{
            'male': u.gender === 'M', 
            'female': u.gender === 'F'
          }"
        >
          <i
            class="fa fa-3x"
            [ngClass]="{
            'fa-mars': u.gender === 'M',
            'fa-venus': u.gender === 'F'
          }"
          ></i>

          {{u.label}}

          <i class="fa fa-trash fa-2x pull-right" 
             (click)="usersService.deleteHandler(u)"></i>
        </li>
      </ul>
    </div>
  `,
  styles: [`
    .male { background-color: #36caff; }
    .female { background-color: pink; }
    .card { transition: all 0.5s }
  `]
})
export class AppComponent {
  constructor(public usersService: UsersService) {
    usersService.init();
  }
}

That's all! Your example should work as before but now your UI is handled by the component and the logic by the service.

In the next chapter you can learn how to handle some specific scenarios.

WHAT'S NEXT

ADS: MY LATEST VIDEO COURSE <br />(italian only)ADS: MY LATEST VIDEO COURSE <br />(italian only)
ADS: MY LATEST VIDEO COURSE
(italian only)

Keep updated about latest content
videos, articles, tips and news
BETA