Chapter 6: How to Update Angular Projects to the Latest Version

In this chapter, as we update Angular, we’ll learn the following:

  • how Angular versions work
  • where to find instructions on how to update Angular
  • how to update our code from Angular 4 to Angular 5 (Angular 5 being the latest version at time of writing).

By the end of this chapter, you’ll understand:

  • the underlying meaning of specific Angular versions
  • where to find exact instructions on how to update Angular applications
  • how to figure out which code changes are required (if any) for Angular 5.

Let’s get started!

The Meaning of Angular Versions

To support a thriving ecosystem, Angular needs to be both stable and evolutionary.

On one hand, Angular aims to provide developers with maximum stability for mission-critical applications. On the other hand, it constantly needs to adapt and advance to support the latest changes in web technologies.

Therefore, the Angular team has decided to use a time-based release cycle with semantic versioning.

A time-based release cycle means that we can expect new versions of Angular (Angular 5, Angular 6, Angular 7, etc.) every couple of weeks or months.

Semantic versioning means that the version number of Angular allows us to predict whether or not it will break our application if we upgrade to it.

In essence, a semantic version looks like this: Major.Minor.Patch.

So version v1.3.8 has a major component with a value of 1, a minor component with a value of 3 and a patch component with a value of 1.

When a new version is released, the new version implicitly indicates the type of change that was made to the code.

The following rules are applied when a semantic version is increased:

  1. Each increment happens numerically with an increment of 1.

  2. When a bug is fixed and the code stays backwards compatible, the patch component is increased:

    v0.0.3 // Before bugfix
     v0.0.4 // After bugfix
     
  3. When functionality is added and the code stays backwards compatible, the minor component is increased and the patch component is reset to zero:

    v0.2.4 // Before addition of new functionality
     v0.3.0 // After addition of new functionality
     
  4. When a change is implemented that causes the code to become backwards incompatible, also known as a breaking change, the major component is increased and the minor and patch components are reset to zero:

    v7.3.5 // Before implementing backwards incompatible changes
     v8.0.0 // After implementing backwards incompatible changes
     

If you’re not familiar with semantic versioning, make sure to check out this simple guide to semantic versioning.

The Angular team combines semantic versioning with a time-based release cycle to aim at:

  • a new patch release every week
  • a new minor release every month
  • a new major release every 6 months

The release schedule is not set in stone, as there may be holidays or special events, but it’s a good indicator of what we can expect in terms of upcoming versions.

You can follow the official Angular blog and the official change log to stay up to date on the latest developments.

A huge benefit of semantic versions is that we can safely update Angular applications with patch or minor releases without having to worry about breaking our applications.

But what if there’s a new major release?

The Angular Update Guide

We already learned that a major release can come with breaking changes. So how do we know if our existing application will break or not if we update it?

One way would be to read the official change log and go through the list of changes.

A much easier way is to use the Angular Update Guide to update Angular. You select your current version of Angular and the version you wish to upgrade to and the application tells you the exact steps you need to take.

For our Angular Todo application, we wish to upgrade from Angular 4.0 to Angular 5.0.

Let’s select app complexity level Advanced so we see all the possible measures we need to take:

Angular Update Guide Results

We get a complete overview of all the steps we need to take to update our application.

How sweet is that!

Before Updating

The Before Updating list contains 12 items. None of the items apply to our Angular Todo application, so we can safely proceed to the next step.

During the Update

From the During the Update list, only the last item applies to our application. We need to update our dependencies, so let’s run the proposed commands in the root of our project:

$ npm install @angular/{animations,common,compiler,compiler-cli,core,forms,http,platform-browser,platform-browser-dynamic,platform-server,router}@'^5.0.0' [email protected] rxjs@'^5.5.2'
 
 $ npm install [email protected] --save-exact
 

Because we updated our Angular CLI to the latest version in the Up and Running section, we also update our local version:

$ npm install @angular/cli@latest --save-dev
 

To verify that our application runs correctly, we run:

$ ng serve
 

If ng serve fails to start, try deleting your node_modules directory and package-lock.json file and run npm install to recreate a clean node_modules directory and package-lock.json file.

After the Update

The After the Update list contains four items, of which the first and the last apply to our application:

  • switch from HttpModule to HttpClientModule
  • import RxJS operators from rxjs/operators and use the RxJS pipe operator

Let’s tackle them one by one.

Switching from HttpModule to HttpClientModule

The Angular Update Guide tells us that we should switch from from HttpModule to HttpClientModule.

If we inspect the Angular version 5.0.0 release notes, we learn that Angular 4.3 and later ships with a new HttpClient that automatically handles JSON responses and supports HTTP Interceptors.

It states that, to update our code, we must replace HttpModule with HttpClientModule, inject the HttpClient service and remove all map(res => res.json()) calls because the new HttpClient automatically parses JSON responses.

Let’s open src/app/app.module.ts and replace HttpModule:

// ...
 import { HttpModule } from '@angular/http';
 
 @NgModule({
   declarations: [
     // ...
   ],
   imports: [
     // ...
     HttpModule,
   ],
   providers: [
     // ...
   ],
   bootstrap: [AppComponent]
 })
 export class AppModule {
 }
 

with HttpClientModule:

// ...
 import { HttpClientModule } from '@angular/common/http';
 
 @NgModule({
   declarations: [
     // ...
   ],
   imports: [
     // ...
     HttpClientModule,
   ],
   providers: [
     // ...
   ],
   bootstrap: [AppComponent]
 })
 export class AppModule {
 }
 

Next, we must use the HttpClient service instead of the Http service and remove all map(res => res.json()) calls in our code because the new HttpClient automatically parses the responses for us.

In Chaprter 3, we centralized all HTTP related code in a service called ApiService, and we now reap the benefits of that approach.

As a result, we only have to update one file, so let’s open up src/app/api.service.ts and replace:

import {
   Http,
   Headers,
   RequestOptions,
   Response
 } from '@angular/http';
 
 // ...
 
 @Injectable()
 export class ApiService {
 
   constructor(
     private http: Http,
     private session: SessionService
   ) {
   }
 
   public signIn(username: string, password: string) {
     return this.http
       .post(API_URL + '/sign-in', {
         username,
         password
       })
       .map(response => response.json())
       .catch(this.handleError);
   }
 
   public getAllTodos(): Observable<Todo[]> {
     const options = this.getRequestOptions();
     return this.http
       .get(API_URL + '/todos', options)
       .map(response => {
         const todos = response.json();
         return todos.map((todo) => new Todo(todo));
       })
       .catch(this.handleError);
   }
 
   public createTodo(todo: Todo): Observable<Todo> {
     const options = this.getRequestOptions();
     return this.http
       .post(API_URL + '/todos', todo, options)
       .map(response => {
         return new Todo(response.json());
       })
       .catch(this.handleError);
   }
 
   public getTodoById(todoId: number): Observable<Todo> {
     const options = this.getRequestOptions();
     return this.http
       .get(API_URL + '/todos/' + todoId, options)
       .map(response => {
         return new Todo(response.json());
       })
       .catch(this.handleError);
   }
 
   public updateTodo(todo: Todo): Observable<Todo> {
     const options = this.getRequestOptions();
     return this.http
       .put(API_URL + '/todos/' + todo.id, todo, options)
       .map(response => {
         return new Todo(response.json());
       })
       .catch(this.handleError);
   }
 
   public deleteTodoById(todoId: number): Observable<null> {
     const options = this.getRequestOptions();
     return this.http
       .delete(API_URL + '/todos/' + todoId, options)
       .map(response => null)
       .catch(this.handleError);
   }
 
   private handleError(error: Response | any) {
     console.error('ApiService::handleError', error);
     return Observable.throw(error);
   }
 
   private getRequestOptions() {
     const headers = new Headers({
       'Authorization': 'Bearer ' + this.session.accessToken
     });
     return new RequestOptions({ headers });
   }
 }
 

with

import {
   HttpClient,
   HttpErrorResponse,
   HttpHeaders
 } from '@angular/common/http';
 
 // ...
 
 @Injectable()
 export class ApiService {
 
   constructor(
     private http: HttpClient,
     private session: SessionService
   ) {
   }
 
   public signIn(username: string, password: string) {
     return this.http
       .post(API_URL + '/sign-in', {
         username,
         password
       })
       .catch(this.handleError);
   }
 
   public getAllTodos(): Observable<Todo[]> {
     const options = this.getRequestOptions();
     return this.http
       .get(API_URL + '/todos', options)
       .map(response => {
         const todos = <any[]> response;
         return todos.map((todo) => new Todo(todo));
       })
       .catch(this.handleError);
   }
 
   public createTodo(todo: Todo): Observable<Todo> {
     const options = this.getRequestOptions();
     return this.http
       .post(API_URL + '/todos', todo, options)
       .map(response => {
         return new Todo(response);
       })
       .catch(this.handleError);
   }
 
   public getTodoById(todoId: number): Observable<Todo> {
     const options = this.getRequestOptions();
     return this.http
       .get(API_URL + '/todos/' + todoId, options)
       .map(response => {
         return new Todo(response);
       })
       .catch(this.handleError);
   }
 
   public updateTodo(todo: Todo): Observable<Todo> {
     const options = this.getRequestOptions();
     return this.http
       .put(API_URL + '/todos/' + todo.id, todo, options)
       .map(response => {
         return new Todo(response);
       })
       .catch(this.handleError);
   }
 
   public deleteTodoById(todoId: number): Observable<null> {
     const options = this.getRequestOptions();
     return this.http
       .delete(API_URL + '/todos/' + todoId, options)
       .map(response => null)
       .catch(this.handleError);
   }
 
   // ...
 }
 

We replace the old classes from HttpModule with their new counterparts from HttpClientModule.

More specifically, we replace:

  • import { Http, Headers, RequestOptions, Response } from '@angular/http'; with import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
  • line 81: Response with HttpErrorResponse
  • line 90: Headers with HttpHeaders
  • line 93: return new RequestOptions({ headers }); with return { headers };

If we run:

$ ng serve
 

and navigate our browser to http://localhost:4200, we see that our application still works as expected, but now uses the HttpClientModule behind the scenes.

Time to tackle item 2: import RxJS operators from rxjs/operators and use the RxJS pipe operator.

Using the RxJS Pipe Operator

Angular 5 was updated to use RxJS 5.5.2 or later.

As of version 5.5, RxJS ships with pipeable operators. The official documentation says:

A pipeable operator is any function that returns a function with the signature: <T, R>(source: Observable<T>) => Observable<R>

You pull in any operator you need from one spot, under rxjs/operators (plural!). It’s also recommended to pull in the Observable creation methods you need directly as shown below with range:

import { range } from >'rxjs/observable/range';
 import { map, filter, scan } from >'rxjs/operators';
 
 const source$ = range(0, 10);
 
 source$.pipe(
  filter(x => x % 2 === 0),
  map(x => x + x),
  scan((acc, x) => acc + x, 0)
 )
 .subscribe(x => console.log(x))
 

Although this sounds complicated, it essentially means that where we previously used chained methods:

source$
   .operatorOne()
   .operatorTwo()
   .subscribe()
 

we should now import operators from rxjs/operators and use the .pipe() method to apply them:

source$
   .pipe(
     operatorOne(),
     operatorTwo()
   )
   .subscribe()
 

The main benefits of pipeable operators are:

  1. they are tree-shakeable, allowing tools to reduce our application bundle size by removing unused code
  2. they are plain functions so we can easily create our own custom pipeable operators.

The .pipe() method reduces the impact on our code to a minimum.

We have two items in our application that need to be refactored: our ApiService and TodosComponent.

First, let’s open up src/app/api.service.ts to update our ApiService:

// import operators from rxjs/operators
 import { map } from 'rxjs/operators';
 
 // ...
 
 @Injectable()
 export class ApiService {
 
   constructor(
     private http: HttpClient,
     private session: SessionService
   ) {
   }
 
   // ...
 
   // update .map() to .pipe(map())
   public getAllTodos(): Observable<Todo[]> {
     const options = this.getRequestOptions();
     return this.http
       .get(API_URL + '/todos', options)
       .pipe(
         map(response => {
           const todos = <any[]> response;
           return todos.map((todo) => new Todo(todo));
         })
       )
       .catch(this.handleError);
   }
 
   // update .map() to .pipe(map())
   public createTodo(todo: Todo): Observable<Todo> {
     const options = this.getRequestOptions();
     return this.http
       .post(API_URL + '/todos', todo, options)
       .pipe(
         map(response => {
           return new Todo(response);
         })
       )
       .catch(this.handleError);
   }
 
   // update .map() to .pipe(map())
   public getTodoById(todoId: number): Observable<Todo> {
     const options = this.getRequestOptions();
     return this.http
       .get(API_URL + '/todos/' + todoId, options)
       .pipe(
         map(response => {
           return new Todo(response);
         })
       )
       .catch(this.handleError);
   }
 
   // update .map() to .pipe(map())
   public updateTodo(todo: Todo): Observable<Todo> {
     const options = this.getRequestOptions();
     return this.http
       .put(API_URL + '/todos/' + todo.id, todo, options)
       .pipe(
         map(response => {
           return new Todo(response);
         })
       )
       .catch(this.handleError);
   }
 }
 

We import the map pipeable operator from rxjs/operators and update all occurrences from .map(fn) to .pipe(map(fn)).

Next, let’s open up src/app/todos/todos.component.ts to apply the same changes to TodosComponent:

// import operators from rxjs/operators
 import { map } from 'rxjs/operators';
 
 // ...
 
 @Component({
   selector: 'app-todos',
   templateUrl: './todos.component.html',
   styleUrls: ['./todos.component.css']
 })
 export class TodosComponent implements OnInit {
 
   // ...  
 
   // update .map() to .pipe(map())
   public ngOnInit() {
     this.route.data
       .pipe(
         map((data) => data['todos'])
       )
       .subscribe(
         (todos) => {
           this.todos = todos;
         }
       );
   }
 
   // ...
 
 }
 

Again, we import the map pipeable operator from rxjs/operators and update .map(fn) to .pipe(map(fn)).

That’s it! The chained operators in our application have been replaced by pipeable operators, just as the Angular Update Guide instructed us to.

If we navigate our browser to http://localhost:4200, we see that our application still works perfectly.

To verify that we are really running Angular 5, we can open up the element inspector:

Angular Version

Angular adds an ng-version attribute to app-root with a value of the version it’s running. We see ng-version="5.2.9", indicating that we’re running Angular 5.2.9.

Mission accomplished! Our application has been successfully upgraded to Angular 5.2.9.

We covered quite a lot, so let’s recap what we’ve learned.

Summary

In this chapter, we learned:

  • how Angular versions work
  • what a semantic version number means
  • how semantic versioning can protect us from blindly introducing breaking changes into our application
  • how the Angular Update Guide can help us find detailed instructions on how to update Angular
  • how to replace HttpModule with HttpClientModule
  • how to update our RxJS code with pipeable operators
  • how the ng-version attribute lets us verify which version of Angular we’re running.

In upcoming versions, Angular CLI will introduce the ng update command to help update Angular applications. As soon as more details are available, we’ll provide you with a follow-up article on how this new command can make our lives even easier.

Until then, you can use this chpater as a guide on how to update Angular applications to the latest version.

All code from this article is available on GitHub.

Have a great one!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset