The Angu­lar JavaS­cript frame­work is con­tinu­ously developed and updated by Google. In order to bene­fit from newly intro­duced fea­tures and per­form­ance optim­iz­a­tions, it is cru­cial to stay up-to-date. Major ver­sions are released every six months, with one to three minor updates for each major ver­sion. Before updat­ing Angu­lar to a high­er ver­sion, ensure that the tar­geted ver­sion is an LTS (long-term sup­port) ver­sion with pro­longed product sup­port for an addi­tion­al 12 months. Angu­lar 14 is the cur­rent LTS ver­sion as of April 2023, offer­ing LTS sup­port up until Decem­ber 2nd, 2023.

Over­view of import­ant changes and fea­tures in pre­vi­ous Angu­lar versions

Angu­lar 2

  • Com­plete rewrite of Angu­lar lan­guage, change of architecture.
  • New archi­tec­ture is MVVM: Mod­el-View-View­Mod­el; in the pre­vi­ous ver­sion of Angu­lar, Angu­larJS, the archi­tec­ture was Model-View-Controller.

Angu­lar 4

  • Http­Cli­ent — an easy-to-use lib­rary for HTTP calls
  • AOT com­pil­a­tion (Ahead of Time) — a com­pil­a­tion meth­od where code com­piles itself dur­ing applic­a­tion build­ing on the server
  • JIT (Just in Time) com­piler meth­od that has been already used, com­piled the code in the browser, improves per­form­ance and lowers the size of Angu­lar applications

Angu­lar 5

  • Pro­gress­ive web apps — applic­a­tions inten­ded to work on every device, includ­ing desktop applic­a­tions and mobiles, not only browsers

Angu­lar 6

  • Focused on improve­ments for work­ing in Angular
  • Angu­lar Mater­i­al + CDK Components
  • Angu­lar Mater­i­al Starter Components
  • RxJS v6

Angu­lar 7

  • Improve­ments in Angu­lar Mater­i­al & CDK
  • Vir­tu­al Scrolling
  • Improved Access­ib­il­ity of Selects

Angu­lar 8

  • Intro­duc­tion of Ivy com­piler as an option­al compiler

Angu­lar 9

  • Ivy com­piler used exclusively
  • Sig­ni­fic­ant improve­ment in pack­age sizes and applic­a­tion performance

Angu­lar 10

  • New Date Range Pick­er (Mater­i­al UI library)
  • Warn­ings about Com­monJS imports
  • Option­al Stricter Settings

Angu­lar 11

  • Exper­i­ment­al sup­port for WebPack 5
  • TSLint moved to ESLint
  • Removed sup­port for IE 9, 10, and IE mobile completely

Angu­lar 12

  • Sup­port for IE deprecated
  • The strict mode of Angu­lar has now been enabled by default
  • ng build will default to a pro­duc­tion build

Angu­lar 13

  • View Engine sup­port removed completely
  • Sup­port for IE 11 removed completely
  • New ver­sion of RxJS

Angu­lar 14

  • Enhanced tem­plate diagnostics
  • Stream­lined Page Title Accessibility
  • Option­al Injectors

Angu­lar LTS Ver­sion Upgrade Guide

In this second part of the art­icle we want to high­light a few best prac­tices when it comes to upgrad­ing Angu­lar. The most import­ant first point of ref­er­ence is the offi­cial Angu­lar Update Guide.

It is import­ant to upgrade ver­sions of Angu­lar one by one and per­form all the checks on your applic­a­tion for each new ver­sion. Updat­ing sev­er­al ver­sions at once can pro­duce time-con­sum­ing prob­lems. How­ever, migra­tion across mul­tiple ver­sions can present unique, unex­pec­ted issues, espe­cially when it comes to lib­rar­ies that are not dis­trib­uted by Angu­lar itself. A cru­cial npm com­mand while migrat­ing is “npm out­dated,” which checks the ver­sions of installed pack­ages to see if any pack­age is cur­rently outdated.

Below we have col­lec­ted tips and best prac­tices for resolv­ing the most com­mon issues dur­ing the Angu­lar upgrade pro­cess that my team came across.

Below we have col­lec­ted tips and best prac­tices for resolv­ing the most com­mon issues dur­ing the Angu­lar upgrade pro­cess that my team came across.

1.      Util­ize the “as any” Type Cast for Angu­lar Mater­i­al Trans­ition from Ver­sion 8 to 9

If a value has been encap­su­lated in a way that it is no longer reach­able nor­mally, for example, a change in an attrib­ute mod­i­fi­er from pub­lic to private, we can still use the “as any” type cast to access it.

In Angu­lar Mater­i­al 8 for example, we could use the fol­low­ing syntax:

this.tabsGroup._tabHeader._setTabFocus(this.activeTabIndex);

This is not sup­por­ted in Angu­lar Mater­i­al 9 as tab­List­Con­tain­er is now private in its com­pon­ent. If there is not enough time to rebuild our applic­a­tion, the fol­low­ing can be used syntax:

(this.tabsGroup._tabHeader as any)._tabListContainer.nativeElement

2. Accom­mod­ate Lib­rary API Changes by Rebuild­ing Accord­ing to the Updated README (example – NGXLogger)

We will use NGX­Log­ger as an example in this case. In ver­sion 3.x.x of NGX­Log­ger, no inher­ent inter­cept­or is provided, so a cus­tom solu­tion had to be built. It was also neces­sary to rebuild the solu­tion because pre­vi­ous ver­sions of NGX­Log­ger were no longer sup­por­ted with high­er ver­sions of Angu­lar, the new ver­sion has a built-in Inter­cept­or which needs to be configured.

Example solu­tion with 3.x.x NGXLogger

import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {NGXLogInterface} from 'ngx-logger';
import {Observable} from 'rxjs';
import {environment} from '../../../environments/environment';
import {ExampleService} from '../example';

@Injectable()
export class LoggingInterceptor implements HttpInterceptor {

    constructor(private exampleService: ExampleService) {
    }

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (request.url.indexOf(environment.endpoints.messagingLog) !== -1) {
            const ngxObject: NGXLogInterface = request.body;
            const example = this.exampleService.getExample();
            ngxObject.message = `[${example ? example.exampleValue : '-'}] 
            ${ngxObject.message}`;
            const logRequest = request.clone({
                body: {...request.body}
            });
            return next.handle(logRequest);
        }
        return next.handle(request);
    }
}

Example solu­tion in 5.x.x NGX­Log­ger based on the NGX-Log­ger Documentation

@NgModule({
    declarations: [

    ],
    imports: [

        LoggerModule.forRoot(
            {
                level: NgxLoggerLevel.DEBUG,
                serverLoggingUrl: environment.endpoints.messagingLog,
                serverLogLevel: NgxLoggerLevel.INFO
            },
            {
                serverProvider: {
                    provide: TOKEN_LOGGER_SERVER_SERVICE, 
                    useClass: ExampleCustomLoggerService
                }
            }
        ),

Example­Cus­tom­Log­gerSer­vice

import {Inject, Injectable} from '@angular/core';
import {INGXLoggerMetadata, NGXLoggerServerService} from 'ngx-logger';
import {HttpBackend, HttpRequest} from '@angular/common/http';
import {ExampleService} from '../example';
import {WINDOW} from '../window.provider';
import {InterceptorHeaderValue} from '../../shared/static-values/interceptor-header-value';
import {environment} from '../../../environments/environment';

@Injectable()
export class ExampleCustomLoggerService extends NGXLoggerServerService {

    constructor(private exampleService: exampleService, 
    @Inject(WINDOW) private window: Window, public httpBackend: HttpBackend) {
        super(httpBackend);
    }

    /**
     * Customise the data sent to the API
     *
     * @param metadata the data provided by NGXLogger
     * @returns the data that will be sent to the API in the body
     */
    customiseRequestBody(metadata: INGXLoggerMetadata): any {
        const body = {...metadata};

        const ngxObjectMessage = body.message;
        const example = this.exampleService.getExample();
        body.message = `[${example ? example.exampleValue: '-'}] ${ngxObjectMessage}`;

        return body;
    }

    protected override alterHttpRequest(httpRequest: HttpRequest<any>): 
    HttpRequest<any> {
        // Alter httpRequest by adding auth token to header

        const hostInfo = this.getHostInfo();
        let apiReq;
        if (httpRequest.headers.has(InterceptorHeaderValue.InterceptorSkipContext)) {
            apiReq = httpRequest.clone({url: `${hostInfo.protocol}//${hostInfo.host}` +
            `:${hostInfo.port}${hostInfo.pathname}${httpRequest.url}`});
        } else {
            apiReq = httpRequest.clone({url: `${hostInfo.protocol}//${hostInfo.host}` +
            `:${hostInfo.port}/${environment.endpoints.root}/${httpRequest.url}`});
        }

        return apiReq;
    }

    getHostInfo(): any {
        return {
            host: this.window.location.hostname,
            port: this.window.location.port,
            protocol: this.window.location.protocol,
            pathname: this.window.location.pathname
        };
    }

}

3.      Updat­ing Pack­age Names for Com­pat­ib­il­ity with Angular

In Angu­lar 11 it was pos­sible to use the fol­low­ing syntax:

import {isNullOrUndefined} from 'util';

As of Angu­lar 12 the name of the pack­age has changed, all we needed to do was renam­ing the pack­age using this syntax:

import {isNullOrUndefined} from 'is-what';

4.      Facil­it­at­ing the Trans­ition from TSLint to ESLint with Help­ful Tools

TSLint is a tool used for stat­ic code ana­lys­is and its main focus is typescript lint­ing. In order to avoid bifurc­a­tion between TSLint and ESLint, TSLint was depra­cated and then sup­port for it was com­pletely removed in Angu­lar 11. ESLint is a tool for both JavaS­cript and typescript.

This Git provides a col­lec­tion of use­ful sim­pli­fy­ing the move from TSLint to ESLint without hav­ing to do it manu­ally: https://github.com/typescript-eslint/tslint-to-eslint-config

5.      Adjust­ing Life­cycle Hooks for Child Com­pon­ent Oper­a­tions in Angu­lar 8 and Later

In Angu­lar 7 and earli­er ver­sions, there was a high prob­ab­il­ity that oper­a­tions on a child com­pon­ent would work inside ngOnIn­it. In Angu­lar 8 and later ver­sions ngOnIn­it is executed sig­ni­fic­antly earli­er that ngAfter­ViewIn­it, which can lead to errors when try­ing to oper­ate on child com­pon­ents inside it. If we want to per­form oper­a­tions on child com­pon­ents using life­cycle hooks in Angu­lar 8 and later ver­sions, we need to be mind­ful of the changes and use ngAfter­ViewIn­it instead.

6.      Adapt­ing to Changes in @ContentChild and @ContentChildren Dec­or­at­ors in Angu­lar 9

In Angu­lar 9 the @ContentChild and @ContentChildren dec­or­at­ors are not able to get their own host node, mean­ing the fol­low­ing syn­tax will not work anymore:

 @Directive({
 selector: '[exampleDirective]'
 })
 export class ExampleDirective {
 @ContentChild(ExampleDirective, { static: true, read: ElementRef })
  exampleElementRef: ElementRef;
  
  constructor(){}
 }

What can be done instead it is inject­ing the depend­ency into the ele­ment like this:

@Directive({
 selector: '[exampleDirective]'
 })
 export class ExampleDirective {
   constructor(private readonly elementRef: ElementRef) {}

 }

7.      Solv­ing Uncom­mon and Com­plex Angu­lar Upgrade Issues

If you ever run into more chal­len­ging issues, with no solu­tion based on your cur­rent know­ledge and extens­ive online research, a use­ful life­hack is to go through the Git­Hub com­mits for the respect­ive lib­rary to find out what might be wrong. Time con­sum­ing, but it should pay off. Rather time con­sum­ing, but it often pays off.