The Angular JavaScript framework is continuously developed and updated by Google. In order to benefit from newly introduced features and performance optimizations, it is crucial to stay up-to-date. Major versions are released every six months, with one to three minor updates for each major version. Before updating Angular to a higher version, ensure that the targeted version is an LTS (long-term support) version with prolonged product support for an additional 12 months. Angular 14 is the current LTS version as of April 2023, offering LTS support up until December 2nd, 2023.
Overview of important changes and features in previous Angular versions
Angular 2
- Complete rewrite of Angular language, change of architecture.
- New architecture is MVVM: Model-View-ViewModel; in the previous version of Angular, AngularJS, the architecture was Model-View-Controller.
Angular 4
- HttpClient — an easy-to-use library for HTTP calls
- AOT compilation (Ahead of Time) — a compilation method where code compiles itself during application building on the server
- JIT (Just in Time) compiler method that has been already used, compiled the code in the browser, improves performance and lowers the size of Angular applications
Angular 5
- Progressive web apps — applications intended to work on every device, including desktop applications and mobiles, not only browsers
Angular 6
- Focused on improvements for working in Angular
- Angular Material + CDK Components
- Angular Material Starter Components
- RxJS v6
Angular 7
- Improvements in Angular Material & CDK
- Virtual Scrolling
- Improved Accessibility of Selects
Angular 8
- Introduction of Ivy compiler as an optional compiler
Angular 9
- Ivy compiler used exclusively
- Significant improvement in package sizes and application performance
Angular 10
- New Date Range Picker (Material UI library)
- Warnings about CommonJS imports
- Optional Stricter Settings
Angular 11
- Experimental support for WebPack 5
- TSLint moved to ESLint
- Removed support for IE 9, 10, and IE mobile completely
Angular 12
- Support for IE deprecated
- The strict mode of Angular has now been enabled by default
- ng build will default to a production build
Angular 13
- View Engine support removed completely
- Support for IE 11 removed completely
- New version of RxJS
Angular 14
- Enhanced template diagnostics
- Streamlined Page Title Accessibility
- Optional Injectors
Angular LTS Version Upgrade Guide
In this second part of the article we want to highlight a few best practices when it comes to upgrading Angular. The most important first point of reference is the official Angular Update Guide.
It is important to upgrade versions of Angular one by one and perform all the checks on your application for each new version. Updating several versions at once can produce time-consuming problems. However, migration across multiple versions can present unique, unexpected issues, especially when it comes to libraries that are not distributed by Angular itself. A crucial npm command while migrating is “npm outdated,” which checks the versions of installed packages to see if any package is currently outdated.
Below we have collected tips and best practices for resolving the most common issues during the Angular upgrade process that my team came across.
Below we have collected tips and best practices for resolving the most common issues during the Angular upgrade process that my team came across.
1. Utilize the “as any” Type Cast for Angular Material Transition from Version 8 to 9
If a value has been encapsulated in a way that it is no longer reachable normally, for example, a change in an attribute modifier from public to private, we can still use the “as any” type cast to access it.
In Angular Material 8 for example, we could use the following syntax:
this.tabsGroup._tabHeader._setTabFocus(this.activeTabIndex);
This is not supported in Angular Material 9 as tabListContainer is now private in its component. If there is not enough time to rebuild our application, the following can be used syntax:
(this.tabsGroup._tabHeader as any)._tabListContainer.nativeElement
2. Accommodate Library API Changes by Rebuilding According to the Updated README (example – NGXLogger)
We will use NGXLogger as an example in this case. In version 3.x.x of NGXLogger, no inherent interceptor is provided, so a custom solution had to be built. It was also necessary to rebuild the solution because previous versions of NGXLogger were no longer supported with higher versions of Angular, the new version has a built-in Interceptor which needs to be configured.
Example solution 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 solution in 5.x.x NGXLogger based on the NGX-Logger Documentation
@NgModule({
declarations: [
],
imports: [
LoggerModule.forRoot(
{
level: NgxLoggerLevel.DEBUG,
serverLoggingUrl: environment.endpoints.messagingLog,
serverLogLevel: NgxLoggerLevel.INFO
},
{
serverProvider: {
provide: TOKEN_LOGGER_SERVER_SERVICE,
useClass: ExampleCustomLoggerService
}
}
),
ExampleCustomLoggerService
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. Updating Package Names for Compatibility with Angular
In Angular 11 it was possible to use the following syntax:
import {isNullOrUndefined} from 'util';
As of Angular 12 the name of the package has changed, all we needed to do was renaming the package using this syntax:
import {isNullOrUndefined} from 'is-what';
4. Facilitating the Transition from TSLint to ESLint with Helpful Tools
TSLint is a tool used for static code analysis and its main focus is typescript linting. In order to avoid bifurcation between TSLint and ESLint, TSLint was depracated and then support for it was completely removed in Angular 11. ESLint is a tool for both JavaScript and typescript.
This Git provides a collection of useful simplifying the move from TSLint to ESLint without having to do it manually: https://github.com/typescript-eslint/tslint-to-eslint-config
5. Adjusting Lifecycle Hooks for Child Component Operations in Angular 8 and Later
In Angular 7 and earlier versions, there was a high probability that operations on a child component would work inside ngOnInit. In Angular 8 and later versions ngOnInit is executed significantly earlier that ngAfterViewInit, which can lead to errors when trying to operate on child components inside it. If we want to perform operations on child components using lifecycle hooks in Angular 8 and later versions, we need to be mindful of the changes and use ngAfterViewInit instead.
6. Adapting to Changes in @ContentChild and @ContentChildren Decorators in Angular 9
In Angular 9 the @ContentChild and @ContentChildren decorators are not able to get their own host node, meaning the following syntax 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 injecting the dependency into the element like this:
@Directive({
selector: '[exampleDirective]'
})
export class ExampleDirective {
constructor(private readonly elementRef: ElementRef) {}
}
7. Solving Uncommon and Complex Angular Upgrade Issues
If you ever run into more challenging issues, with no solution based on your current knowledge and extensive online research, a useful lifehack is to go through the GitHub commits for the respective library to find out what might be wrong. Time consuming, but it should pay off. Rather time consuming, but it often pays off.