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"
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:
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
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.
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
:
services/user.service.ts
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root'})
export class UsersService {
}
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:
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!!!
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: '' });
});
}
}
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.
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:
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"
Your final app.component
should be like the following one:
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.