When migrating an application from AngularJS (1.x) to Angular (2+), a crucial step is transitioning from the AngularJS service system to the Angular service system. AngularJS services are based on constructor functions, whereas Angular services are typically provided as classes with dependency injection (DI).
In this guide, we will walk through the process of converting an AngularJS service to an Angular service, explaining the key differences and providing examples.
1. Overview of Services in AngularJS vs Angular
In AngularJS (1.x)
In AngularJS, services can be created in a few different ways, such as using factories, services, or providers. Services are typically singleton objects that can be injected into controllers or other services.
Example of a service in AngularJS:
angular.module('app')
.service('MyService', function() {
this.getData = function() {
return 'Data from AngularJS Service';
};
});
In Angular (2+)
In Angular, services are typically classes that are decorated with the @Injectable()
decorator to specify that they can be injected into other components, services, or modules via dependency injection. Angular services are instantiated by Angular’s DI system.
Example of a service in Angular:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root', // Automatically provided at the root level
})
export class MyService {
getData() {
return 'Data from Angular Service';
}
}
2. Key Differences Between AngularJS and Angular Services
Aspect | AngularJS | Angular (2+) |
---|---|---|
Service Creation | Uses constructor functions or object literals | Defined as classes using @Injectable decorator |
Singleton | Services are singletons by default | Services are singletons by default, or based on scope (providedIn ) |
Dependency Injection | Injected using angular.module.service() or $injector | Injected via constructor and DI system (automatic) |
Module-Level Declaration | Defined at module level (angular.module() ) | Declared and provided in @Injectable or in NgModules |
Scope | Services have access to $scope , $rootScope | Services are independent and have no access to scopes |
3. Converting AngularJS Service to Angular Service
Step 1: Understand the AngularJS Service Structure
Let’s start by examining a simple AngularJS service.
javascriptCopyEdit// AngularJS Service (Old)
angular.module('app')
.service('UserService', function($http) {
this.getUser = function(id) {
return $http.get('/api/user/' + id);
};
});
Step 2: Convert to Angular Service
In Angular, the UserService
is now a class decorated with @Injectable()
, and dependency injection is done through the constructor. Angular services are generally not tied to $scope
, and they are provided in modules via Angular’s dependency injection system.
// Angular Service (New)
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root', // Singleton provided at the root of the app
})
export class UserService {
constructor(private http: HttpClient) {}
getUser(id: string): Observable<any> {
return this.http.get(`/api/user/${id}`);
}
}
Here are the changes made:
- Class-based service: The service is now a TypeScript class.
@Injectable()
decorator: This decorator allows Angular to inject the service’s dependencies. By usingprovidedIn: 'root'
, the service is available throughout the entire application.- Dependency Injection: Angular uses its DI system to inject the
HttpClient
service, which is Angular’s HTTP service. - Observable: The HTTP request now returns an
Observable
rather than a promise, which is a more modern approach in Angular.
4. Replacing $scope or $rootScope in AngularJS Services
In AngularJS, services could directly access $scope
or $rootScope
. In Angular, there is no $scope
, so we need to use Angular’s BehaviorSubject
, Subject
, or services for global state management if required.
Example of Using a Service to Manage Global State
In AngularJS, a common pattern was to manage shared data in a service and bind it to $scope
.
// AngularJS Service Example with $scope
angular.module('app')
.service('UserService', function($rootScope) {
this.getUser = function(id) {
// Simulating a data update
$rootScope.currentUser = { id: id, name: 'John Doe' };
};
});
In Angular, we can achieve the same functionality using a service with RxJS subjects.
// Angular Service Example with BehaviorSubject
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class UserService {
private currentUserSubject = new BehaviorSubject<any>(null); // Holds the current user data
currentUser$ = this.currentUserSubject.asObservable(); // Observable to subscribe to changes
constructor() {}
setUser(user: any) {
this.currentUserSubject.next(user); // Update the user data
}
}
With this approach:
- BehaviorSubject holds and emits the latest value to any subscribers.
- currentUser$ is the observable that components can subscribe to, enabling reactive state updates.
- setUser updates the state (similar to
$rootScope.currentUser
in AngularJS).
Component Usage
To consume the UserService
in a component, we can use the Angular async
pipe to automatically subscribe to the observable and update the UI when the data changes.
// Component Example using UserService
import { Component } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app-user',
template: `
<div *ngIf="userService.currentUser$ | async as user">
<h1>{{ user.name }}</h1>
</div>
`
})
export class UserComponent {
constructor(public userService: UserService) {}
}
Here, the component is automatically subscribed to currentUser$
and displays the user data when it changes.
5. Handling Error Handling and Promises
In AngularJS, services often dealt with $http
promises directly. In Angular, you generally work with Observables from the HttpClient
service, which gives you more flexibility.
AngularJS Example (with $http
promise):
angular.module('app')
.service('UserService', function($http) {
this.getUser = function(id) {
return $http.get('/api/user/' + id)
.then(response => response.data)
.catch(error => {
console.error('Error fetching user', error);
throw error;
});
};
});
Angular Example (with HttpClient
and Observable):
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class UserService {
constructor(private http: HttpClient) {}
getUser(id: string): Observable<any> {
return this.http.get(`/api/user/${id}`).pipe(
catchError((error) => {
console.error('Error fetching user', error);
return throwError(error);
})
);
}
}
In this Angular example:
- Observables are used for handling HTTP requests.
- The
catchError
operator is used to handle errors.