How to Update Angular Projects to the Latest Version

Jurgen Van de Moere
Share

In this article, we’ll look at how to update Angular projects to the latest version.

This article is part 6 of the SitePoint Angular 2+ Tutorial on how to create a CRUD App with the Angular CLI.

  1. Part 0 — The Ultimate Angular CLI Reference Guide
  2. Part 1 — Getting our first version of the Todo application up and running
  3. Part 2 — Creating separate components to display a list of todos and a single todo
  4. Part 3 — Update the Todo service to communicate with a REST API
  5. Part 4 — Use Angular router to resolve data
  6. Part 5 — Add authentication to protect private content
  7. Part 6 — How to Update Angular Projects to the latest version.

In part 1 we learned how to get our Todo application up and running and deploy it to GitHub pages. This worked just fine but, unfortunately, the whole app was crammed into a single component.

In part 2 we examined a more modular component architecture and learned how to break this single component into a structured tree of smaller components that are easier to understand, reuse and maintain.

In part 3 we updated our application to communicate with a REST API backend using RxJS and Angular’s HTTP service.

In part 4, we introduced Angular Router and learned how the router updates our application when the browser URL changes and how we can use the router to resolve data from our backend API.

In part 5, we added authentication to our application and learned how we can protect sections from our application from unauthorized access.

Don’t worry! You don’t need to have followed part 1, 2, 3, 4 or 5 of this tutorial for 6 to make sense. You can simply grab a copy of our repo, check out the code from part 5, and use that as a starting point. This is explained in more detail below.

Up and Running

To start on our goal to update Angular, make sure you have the latest version of the Angular CLI installed. If you don’t, you can install it with the following command:

npm install -g @angular/cli@latest

If you need to remove a previous version of the Angular CLI, you can:

npm uninstall -g @angular/cli angular-cli
npm cache clean
npm install -g @angular/cli@latest

After that, you’ll need a copy of the code from part 5. This is available on GitHub. Each article in this series has a corresponding tag in the repository so you can switch back and forth between the different states of the application.

The code that we ended with in part 5 and that we start with in this article is tagged as part-5. The code that we end this article with is tagged as part-6.

You can think of tags like an alias to a specific commit ID. You can switch between them using git checkout. You can read more on that here.

So, to get up and running (with the latest version of the Angular CLI installed) we would do this:

git clone git@github.com:sitepoint-editors/angular-todo-app.git
cd angular-todo-app
git checkout part-5
npm install
ng serve

Then visit http://localhost:4200/. If all’s well, you should see the working Todo app.

Update Angular: Our Plan of Attack

In this article, 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 article, 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:

Update Angular with the Angular Update Guide

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' typescript@2.4.2 rxjs@'^5.5.2'

$ npm install typescript@2.4.2 --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 part 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 the first article, we learned how to:

  • initialize our Todo application using Angular CLI
  • create a Todo class to represent individual todos
  • create a TodoDataService service to create, update and remove todos
  • use the AppComponent component to display the user interface
  • deploy our application to GitHub pages.

In the second article, we refactored AppComponent to delegate most of its work to:

  • a TodoListComponent to display a list of todos
  • a TodoListItemComponent to display a single todo
  • a TodoListHeaderComponent to create a new todo
  • a TodoListFooterComponent to show how many todos are left.

In the third article, we learned how to:

  • create a mock REST API back end
  • store the API URL as an environment variable
  • create an ApiService to communicate with the REST API
  • update the TodoDataService to use the new ApiService
  • update the AppComponent to handle asynchronous API calls
  • create an ApiMockService to avoid real HTTP calls when running unit tests.

In the fourth article, we learned:

  • why an application may need routing
  • what a JavaScript router is
  • what Angular Router is, how it works and what it can do for you
  • how to set up Angular router and configure routes for our application
  • how to tell Angular router where to place components in the DOM
  • how to gracefully handle unknown URLs
  • how to use a resolver to let Angular router resolve data.

In the fifth article, we learned:

  • the difference between cookies and tokens
  • how to create an AuthService to implement authentication logic
  • how to create a SessionService to store session data
  • how to create a sign-in form using an Angular reactive form
  • how to create a route guard to prevent unauthorized access to parts of your application
  • how to send a user’s token as an Authorization Header in an HTTP request to your API
  • why you should never send your user’s token to a third party.

In this article on how to update Angular, 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 article 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!