How to Update Angular Projects to the Latest Version

Jurgen Van de Moere
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!

Frequently Asked Questions (FAQs) about Updating Angular Projects

What are the prerequisites for updating my Angular project?

Before you start updating your Angular project, ensure that you have the latest version of Node.js and npm installed. You can check your versions by running node -v and npm -v in your terminal. If you don’t have them installed or if your versions are outdated, visit the official Node.js website to download and install them. Also, make sure that your Angular CLI is up-to-date. You can update it by running npm install -g @angular/cli in your terminal.

How can I update my Angular project to a specific version?

To update your Angular project to a specific version, you can use the ng update command followed by the package name and the version number. For example, if you want to update to Angular 9, you would run ng update @angular/core@9 @angular/cli@9 in your terminal. Remember to commit any changes to your project before running the update command to avoid losing any work.

What should I do if I encounter errors during the update process?

If you encounter errors during the update process, first try to understand the error message. It often contains clues about what went wrong. If you can’t resolve the issue, consider seeking help from the Angular community. Websites like StackOverflow have many experienced developers who can help you troubleshoot your problem. Remember to provide as much detail as possible about your issue, including the error message and the steps you took before encountering the error.

How can I downgrade my Angular project to a previous version?

Downgrading your Angular project to a previous version can be a bit tricky, as it often involves more than just changing the version number in your package.json file. You might also need to downgrade other dependencies and adjust your code to be compatible with the older version. If you need to downgrade, consider seeking help from the Angular community or hiring a professional developer to ensure the process goes smoothly.

How can I keep track of the changes in each Angular update?

The Angular team provides detailed release notes for each update on their official website. These notes include a summary of the changes, bug fixes, and new features introduced in the update. You can also use the ng update command with the --dry-run option to see what changes would be made to your project without actually applying them.

How can I test my Angular project after an update?

After updating your Angular project, it’s important to thoroughly test it to ensure everything still works as expected. You can use the Angular CLI’s built-in testing tools, such as Karma and Protractor, to run unit tests and end-to-end tests on your project. If you encounter any issues, refer to the error messages and the Angular documentation for guidance on how to resolve them.

How often should I update my Angular project?

The frequency of updates depends on your project’s requirements and the stability of the Angular version you’re using. However, it’s generally a good idea to stay up-to-date with the latest stable release to benefit from the latest features and improvements. Remember to thoroughly test your project after each update to ensure everything still works as expected.

Can I skip versions when updating my Angular project?

Yes, you can skip versions when updating your Angular project. However, it’s recommended to update one major version at a time to avoid potential issues. If you’re updating from a very old version, consider seeking help from the Angular community or hiring a professional developer to ensure the process goes smoothly.

What are the benefits of updating my Angular project?

Updating your Angular project allows you to benefit from the latest features, improvements, and bug fixes. It also helps ensure your project is secure and compatible with other modern technologies. However, remember to thoroughly test your project after each update to ensure everything still works as expected.

How can I automate the update process for my Angular project?

You can automate the update process for your Angular project by using continuous integration (CI) tools like Jenkins or Travis CI. These tools can automatically run the ng update command and your tests whenever you push changes to your repository. This can help ensure your project is always up-to-date and working correctly.