Upgrading to Angular v6: Step by Step

11 May 2018
7 mins read

Angular v6 was released on May 3rd and now we can focus on upgrading our projects to the new version. In this post I documented my experience and steps upgrading some projects from v5 to v6. Some tips and key differences between projects created with v5 and v6 are also included.

What’s new in Angular v6

First things first, Angular team published a detailed summary on what’s new in v6 here. So before continuing reading this article, I suggest you read the official Angular blog first.

Upgrading to Angular v6

The best resource with details on how to upgrade to a new Angular version is https://update.angular.io/. Even if you are upgrading from v2 to v6, it will list all the breaking changes since v2 to v6! It is a great way to know in details what you need to change in your code.

1: Instaling latest Angular CLI

First step is to make sure you have the latest CLI available:

npm install -g @angular/cli

or (linux and macos)

sudo npm install -g @angular/cli

You should see something like this:

With the release of Angular v6, the Angular CLI is now also versioned as Angular, meaning until Angular v5, we would use Angular CLI 1.x, and now Angular CLI is also on version 6.x. It makes it much easier!

2: ng update

Needless to say, please do create a branch to update your project, as you’ll never know if all dependencies will still work after upgrading to Angular v6.

We can use ng update -d or ng update --dry-run to check what needs to be updated in our project:

So first, we will start with @angular/cli. In order for the ng update command to work inside the project, we need first to update the @angular/cli version to 6.x.

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

Next, run the ng update command for @angular/cli, then @angular/core and then for the other packages required (rxjs, @angular/material):

ng update @angular/cli
ng update @angular/core
ng update @angular/material
ng update rxjs

Some project structure files have changed from v5 to v6. There is no angular-cli.json anymore, it has been replaced by angular.json. The structure of angular.json also has changed to support multiple projects per workspace. When we run ng update @angular/cli all the required files will be updated!

3: Updating other dependencies

I also like to update the other npm depedencies used by the project during an Angular upgrade. The npm package npm-check-updates is really helpful for this task.

npm install -g npm-check-updates

Use the command ncu to check what packages have updates available. And ncu -u to update your package.json.

When changing versions of package of package.json, I personally also like to delete the node_module and run npm i again just to make sure the correct versions are availavle locally (and also update package-lock.json).

4: Updating RxJS

So, next step now is running ng serve to check if everything is ok with the code. Don’t forget to verify https://update.angular.io/ for all beaking changes.

Even though we were able to update RxJS code since Angular v5 (with RxJS v5) to use the pipeble operators, in the projects I did the upgrade to v6, I forgot to change a few places. To help us with this task, we can install rxjs-tslint to help us removing all deprecated RxJS code.

npm install -g rxjs-tslint
rxjs-5-to-6-migrate -p src/tsconfig.app.json

As a quick summary regarding the imports:

import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of;'

to:

import { BehaviorSubject, Subject, Observable, of } from 'rxjs';

Or, if you were not using RxJS pipeble operators yet:

import 'rxjs/add/observable/of';	
import 'rxjs/add/operator/catch';	
import 'rxjs/add/operator/do';	
import 'rxjs/add/operator/map';	
import 'rxjs/add/operator/mergeMap';	
import 'rxjs/add/operator/switchMap';

to

import { of } from 'rxjs';
import { map, switchMap, catchError, mergeMap } from 'rxjs/operators';

The following operators were renamed as well:

do -> tap
catch -> catchError
switch -> switchAll
finally -> finalize

And in our code, we can start using the pipeble operators.

From:

this.http.get('url')
  .do(console.log)
  .map(results => results.data)
  .subscribe(results => {
    console.log('Results', results);
  });

To:

this.http.get('url')
  .pipe(
    tap(console.log), // old 'do' operator
    map(results => results.data)
  )
  .subscribe(results => {
    console.log('Results', results);
  });

After updating your RxJS code, you might still get errors regarding RxJS from third-party dependencies. To solve this, install rxjs-compat and once the dependencies have upgraded their code as well, you can remove this package from your project.

npm install --save rxjs-compat

5: Simplifying Dependency Injection for Core Services

A new feature introduced in Angular v6 is called “tree-shakable providers”. This means we no longer need to declare services in a module by using the property providedIn, and this will allow the services to be tree-shakable, meaning if they are not being used, they will not be part of the prod bundle.

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class MyCoreService { }

I applied this feature in all core services (global scope) of my projects, but I’m still using non tree-shakable providers with services that do not have global scope.

This allowed to clean up the providers section from CoreModule.

For more information about tree-shakable providers:

6: Updating Angular Material (optional)

If you are using Angular Material in your project, don’t forget to run ng update @angular/material to update the material dependencies.

A breaking change from v5 to v6 is how we import the Material modules in our project:

From:

import {
  MdToolbarModule,
  MdIconModule,
  MdButtonModule,
  MdMenuModule,
  MdCardModule
} from '@angular/material';

To:

import { MatToolbarModule } from '@angular/material/toolbar';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { MatCardModule } from '@angular/material/card';

Now each module has its own package. This is also one of the reasons I like to create a separate module for thrid party imports as already explained in this article. It makes much easier to fix imports!

Some other things…

I do have some projects that were created since Angular v2, and after every major version update, I usually just updated package.json and fixed the breaking changes in the code and that was ok.

Since there were some project structure changes in Angular CLI v6, I also decided to migrate a project by creating a brand new project with CLI 6 and copying the src folder from the old project to the new project. Below are some of the most impacted changes.

The major difference that had some impact on the code is the baseUrl: './' from tsconfig.json. With projects created with CLI 1.x (for Angular v4 and v5), this configuration is not there by default (but inside src/tsconfig.ap.json). Moving baseUrl to the root tsconfig.json had an impact on custom paths declared in tsconfig.json and also on the path for lazy loading modules.

Before - custom path in tsconfig.json:

paths: {
  "@env/*": ["environments/*"]
}

After (single project created with CLI v6):

paths: {
  "@env/*": ["src/environments/*"]
}

And the lazy loading modules need to be declared using the relative path:

Before:

{ path: 'home', loadChildren: 'app/home/home.module#HomeModule' }

After:

{ path: 'home', loadChildren: './home/home.module#HomeModule' }

If you have nested modules, you also need to update them to use relative paths:

Before (module1-routing.module.ts):

{ path: 'foo', loadChildren: 'app/module1/module2/module2.module#Module2Module' }

After (module1-routing.module.ts):

{ path: 'foo', loadChildren: './module2/module2.module#Module2Module' }

There were a few changes in the CLI v6 commands as well.

As the majority of my professional applications use Java in the backend, the output folder (dist) from ng build is configured to a different path. Until CLI 1.x, there was a flag (output-path -op) that could be used in the ng build command (ng build -op ../other/path). With CLI v6, if you need to use a different output path, you need to update angular.json and remove the -op flag from ng build:

"architect": {
  "build": {
    "builder": "@angular-devkit/build-angular:browser",
    "options": {
      "outputPath": "../other/path",
      ...
    }
  }
}         

Please note this is not needed if you upgrade from an existing CLI 1.x project to CLI v6.x using ng update

References

Hopefully this article might help you migrating your project, and if you find anything else that is relevant, please leave a comment so we can update this article together! :)

Happy coding and happy upgrating!