Hello there! I hope you’ve followed along with our tutorials on Angular components and routing. In this post, we’ll go on to another interesting concept in Angular: services.
-
Angular
Beginner’s Guide to Angular: Components
Divya Dev
-
Angular
Beginner’s Guide to Angular: Routing
Divya Dev
If Angular components are the presentation layer of our application, what will be responsible for actually fetching real data and executing business logic? This is exactly where Angular services come in. The role of an Angular service is to fetch, organise, and eventually share data, models, and business logic across components.
Before we dive into the technical details of an Angular service, let’s understand more about its features. This will help you understand which part of the code needs to be placed inside a component, and which part needs to be inside an Angular service.
Here are some important facts about services:
A service is defined with the @Injectable
decorator. This tells Angular that the service can be injected into components or other services. We’ll talk more about injecting services later.
Services are a place for holding all your business logic and sharing it across components. This makes your application more scalable and maintainable. Often, services are the right spot for interacting with the back-end as well. For example, if you need to make AJAX calls, methods for completing the call can be made inside a service.
Services are singleton classes. You will only have a single instance of a specific service running in your Angular application.
What Is a Service?
Services in Angular are objects that are instantiated only once in the application’s lifetime. Data received and maintained by a service can be used across the application. This means that components can fetch data from a service at any time. Dependency injection is used to introduce services inside components.
Let’s try to understand how to create a service and use it in an Angular component. You can find the complete source code for this project in our GitHub repo.
Once you have the source code, navigate to the project directory and install the required dependencies using npm install
. After the dependencies have been installed, start the application by typing the following command:
ng serve
You should have the application running on https://localhost:4200/.
The overall folder structure of our project will be as follows.
src --app ----components ------employee.component.css ------employee.component.html ------employee.component.ts ----services ------employee.service.spec.ts ------employee.service.ts ------employeeDetails.service.ts --app.routing.module.ts --app.component.css --app.component.html --app.component.spec.ts --app.component.ts --app.module.ts --assets --index.html --tsconfig.json
1. Building the Skeleton of the Service
There are two ways of creating a service in Angular:
- Manually create folders and files inside the project.
- Use the
ng g service <path/service_name>
command to create a service automatically. When you use this approach, you will automatically get a .service.ts and a .service.spec.ts file in the chosen directory.
ng g service components/employee
2. Creating the Service
Now that the .service.ts file has been created in your project structure, it’s time to fill the contents of the service. To do this, you must decide on what the service needs to do. Remember, you can have multiple services, each to perform a specific business operation. In our case, we are going to use employee.service.ts to return a static list of roles to any component that uses it.
Enter the following code in employee.service.ts.
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class EmployeeService { role = [ {'id':'1', 'type':'admin'}, {'id':'2', 'type':'engineer'}, {'id':'3', 'type':'sales'}, {'id':'4', 'type':'human_resources'} ] getRole(){ return this.role; } }
This service just returns a static list of roles to the application. Let’s decode the service, line by line.
- We import
Injectable
from the@angular/core
library. This is crucial because our services will be used or injected into components. The@Injectable
directive allows us to identify services. - Next, we apply the
@Injectable
decorator. TheprovidedIn
property of@Injectable
specifies where the injectors will be available. Most of the time,root
is assigned as its value. This means the service can be injected at the application level. The other options areany
,platform
,null
, orType<any>
. - We create a class component, with the name
EmployeeService
. This class has a methodgetRole
, which returns a static array of objects.
3. Create a Component
As mentioned before, services in Angular are used to hold the business logic of the application. In order to show data to the viewer, we need a presentation layer. That’s where the traditional class-based Angular components come in, created using the decorator @Component
.
You can learn more about Angular components in my previous post in this series. It will help you understand Angular components and create your own component. Create the file employee.component.ts and add the following code to it:
import { Component, OnInit } from '@angular/core'; import { EmployeeService } from '../services/employee.service'; @Component({ selector: 'employee', templateUrl: './employee.component.html' }) export class EmployeeComponent implements OnInit { role: any; constructor(private employeeService: EmployeeService) { } ngOnInit(): void { this.role = this.employeeService.getRole() } }
Let’s break it down:
- Import the
@Component
decorator and invoke it. We specify'employee'
as the selector, and we provide a template URL pointing to the HTML describing the view of the component. - Declare the component class and specify that it implements
OnInit
. As a result, we can define anngOnInit
event handler which will be called when the component gets created. - In order to use our service, it has to be declared inside the constructor. In our case, you will see
private employeeService: EmployeeService
in the constructor. With this step, we will make the service accessible across the component. - Since our goal is to load the roles when the employee component is created, we fetch the data inside
ngOnInit
.
Can this get any simpler? Since the service is a singleton class, it can be reused across multiple components without any performance penalty.
4. Creating a View
Now that we have data in our component, let’s build a simple employee.component.html file to iterate through the roles and display them. Below, we use *ngFor
to iterate through roles, and display only the type to the user.
<h3>Data from employee.service</h3> <ul> <li *ngFor = "let role of roles">{{role.type}}</li> </ul>
5. Running the Project
We only have one more step before the project gets up and running. We need to make sure that the employee.component.ts file is included in our list of declarations, inside the @NgModule
directive.
As seen below, EmployeeComponent
is added into the app.module.ts file.
//app.module.ts import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { EmployeeComponent } from './components/employee.component'; @NgModule({ declarations: [ AppComponent, EmployeeComponent ], imports: [ BrowserModule, AppRoutingModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Interestingly, we have not added the service in our list of providers, yet we are able to use the service successfully. Why? Because we have specified that the service is to be provided at the application’s root level (i.e. with the providedIn: 'root'
parameter). However, keep reading to understand more about a scenario where we do need to mention a service in the providers
array of @NgModule
.
Also, we need to add the employee
element into the app.component.html file.
<h1> Tutorial: Angular Services </h1> <employee></employee> <router-outlet></router-outlet>
If we run our app so far, it will look like this:
6. Fetching Data Dynamically From a Service
Now, we are going to fetch data specific to our employee.component.ts.
Let’s create a new service to fetch data from an API.
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable() export class EmployeDetailsService { fetchEmployeeDetailsURL = 'https://reqres.in/api/users?page=2' constructor(private http: HttpClient) { } fetchEmployeeDetails = () => { return this.http.get(this.fetchEmployeeDetailsURL); } }
Now, let’s understand our code line by line.
- Since we want to fetch data through an AJAX call, it is important to import
HttpClient
. If you are new toHttpClient
, you can learn more about it in another post in this series. - In our
EmployeeDetailsService
, we are not specifying theprovideIn
parameter. This means we need to do an additional step to let the entire application know about our injectable service. You’ll learn about this in the next step. -
HttpClient
is itself an injectable service. Declare it in the constructor so it will be injected into the component. In thefetchEmployeeDetails
method, we’ll use theHttpClient.get
method for fetching data from a URL.
7. Registering the Service in app.module
Unlike our first service, it is crucial for us to register the EmployeeDetailsService
in app.module.ts since we have not declared the injectable at root level. Here’s the updated app.module.ts file:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { HttpClientModule } from '@angular/common/http'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { EmployeeComponent } from './components/employee.component'; import { EmployeDetailsService } from './services/employeeDetails.service'; @NgModule({ declarations: [ AppComponent, EmployeeComponent ], imports: [ BrowserModule, AppRoutingModule, HttpClientModule ], providers: [ EmployeDetailsService], bootstrap: [AppComponent] }) export class AppModule { }
If you’re following closely, you might have noticed two important changes:
- In our
app.module.ts
file, we need to includeEmployeDetailsService
in the list ofProviders
. - We need to import
HttpClientModule
from@angular/common/http
.HttpClientModule
has to be included in our list ofimports
.
That’s it—we are now ready to make use of EmployeeDetailsService
in our component.
8. Fetching Dynamic Data
In order to accommodate the new service, we are going to make a few changes in our component.
Add a Button to Load the Data
First, we’ll add a button to our view. When we click this button, the data will be loaded via an AJAX call. Here’s the updated employee.component.html file:
<h3>Data from employee.service</h3> <ul> <li *ngFor = "let role of roles">{{role.type}}</li> </ul> <button (click)="loadEmployeeDetails()">Load Employee Details</button> <ul> <li *ngFor = "let employee of employeeDetails">{{employee.email}}</li> </ul>
Subscribe to the Getter Function
Next, subscribe to the getter function in the EmployeDetailsService
. To achieve this, we will be adding EmployeDetailsService
to our constructor in employee.component.ts:
import { Component, OnInit } from '@angular/core'; import { EmployeeService } from '../services/employee.service'; import { EmployeDetailsService } from '../services/employeeDetails.service'; @Component({ selector: 'employee', templateUrl: './employee.component.html' }) export class EmployeeComponent implements OnInit { roles: any; employeeDetails: any; constructor(private employeeService: EmployeeService, private employeeDetailsService: EmployeDetailsService) { } ngOnInit(): void { this.roles = this.employeeService.getRole() } loadEmployeeDetails = () => { this.employeeDetailsService.fetchEmployeeDetails() .subscribe((response:any)=>{ this.employeeDetails = response.data; }) } }
With this change, and on clicking the LoadEmployeeDetails
button, we would see the following view.
Conclusion
There you go! We have gradually built an Angular service that can deal with static and dynamic data. Now, you should be able to build your very own Angular services and use them to fetch data through AJAX calls. And you can even implement your business logic in a more reusable fashion.