avatarFuji Nguyen

Summary

This context provides a guide on upgrading an Angular application from version 15 to 16 and converting Angular components to standalone type using Angular CLI.

Abstract

The context discusses the process of upgrading an Angular application from version 15 to 16, which includes the necessary package.json requirements. It also introduces the concept of standalone components in Angular and explains how to use Angular CLI to automatically convert Angular components to standalone type. The author shares their experience of upgrading a project and provides a supplementary screencast for step-by-step guidance. The context also includes code snippets and a link to the upgraded Angular 16 source code on GitHub.

Bullet points

  • The context provides a guide on upgrading an Angular application from version 15 to 16.
  • The necessary package.json requirements for upgrading are discussed.
  • The concept of standalone components in Angular is introduced.
  • Angular CLI is used to automatically convert Angular components to standalone type.
  • The author shares their experience of upgrading a project.
  • A supplementary screencast is provided for step-by-step guidance.
  • Code snippets are included in the context.
  • The upgraded Angular 16 source code is available on GitHub.

Upgrading Angular 15 to 16: A Full-stack Web Development Project using Angular and .NetCore WebAPI

Introduction

Angular version 16, which was released on May 3rd, 2023, includes a new feature called Schematics for Standalone Components. This feature allows developers to improve our documentation and schematics and helps us create standalone components for your Angular applications. These components are not tied to any specific module and can be used anywhere in your application. Standalone components are useful for developing reusable UI elements or libraries.

In this blog post, I will share my experience of upgrading an Angular application from version 15 to 16, and the automated approach I took to convert all the Angular components of the Talent Management project from the Fullstack Angular 15, Bootstrap 5 & NET 7 API blog series to standalone type. You can find the upgraded Angular 16 source code of the project on GitHub.

With the new Angular CLI command ng generate @angular/core:standalone, I effortlessly transformed the entire project into standalone components in just an hour. I strongly encourage you to give it a shot. It’s definitely worth it. — Fuji Nguyen

Screencast

Since there is a lot of information to cover, I created a supplementary screencast to provide step-by-step guidance on upgrading the project.

Part 1 — Upgrading an Angular application from version 15 to 16

Before the upgrade, make sure you have

  1. Node.js versions: v16 and v18.
  2. TypeScript version 4.9.3 or later.
  3. Zone.js version 0.13.x or later.

If you are using Angular CLI, you can run the following command to update your project:

ng update @angular/core @angular/cli

At the time of this writing, there are a few 3rd party libraries that are not compatible with Angular 16 as shown in the screenshot below.

The workaround is to run with -- force option:

ng update @angular/core @angular/cli --force

Below is the package.json that lists the version of Angular and 3rd party libraries. Please notice that when running npm -i, the ng-bootstrap/ng-bootstrap may report an upstream dependency conflict. To overcome this warning, you can run npm -i --force.

{
  "name": "TalentManagement",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "ng": "ng",
    "build": "npm run write:env -s && ng build",
    "start": "npm run write:env -s && ng serve",
    "deploy": "npm run write:env -s && ng deploy",
    "serve:sw": "npm run build -s && npx http-server ./dist -p 4200",
    "lint": "ng lint && stylelint \"src/**/*.scss\"",
    "test": "npm run write:env -s && ng test",
    "test:ci": "npm run write:env -s && npm run lint -s && ng run TalentManagement:test:ci",
    "e2e": "npm run write:env -s && ng e2e",
    "cypress:open": "npm run write:env -s && ng run TalentManagement:cypress-open",
    "cypress:run": "npm run write:env -s && ng run TalentManagement:cypress-run",
    "translations:extract": "ngx-translate-extract --input ./src --output ./src/translations/template.json --format=json --clean --sort",
    "docs": "hads ./docs -o",
    "write:env": "ngx-scripts env npm_package_version",
    "prettier": "prettier --write \"./src/**/*.{ts,js,html,scss}\"",
    "prettier:check": "prettier --list-different \"./src/**/*.{ts,js,html,scss}\"",
    "postinstall": "npm run prettier -s && husky install",
    "generate": "ng generate"
  },
  "dependencies": {
    "@angular/animations": "~16.0.1",
    "@angular/common": "~16.0.1",
    "@angular/compiler": "~16.0.1",
    "@angular/core": "~16.0.1",
    "@angular/forms": "~16.0.1",
    "@angular/localize": "~16.0.1",
    "@angular/platform-browser": "~16.0.1",
    "@angular/platform-browser-dynamic": "~16.0.1",
    "@angular/router": "~16.0.1",
    "@angular/service-worker": "~16.0.1",
    "@fortawesome/fontawesome-free": "^6.4.0",
    "@ng-bootstrap/ng-bootstrap": "^14.1.1",
    "@ngx-translate/core": "^15.0.0",
    "@popperjs/core": "^2.11.0",
    "@rxweb/reactive-form-validators": "^13.0.1",
    "angular-datatables": "^15.0.1",
    "angular-oauth2-oidc": "^15.0.1",
    "bootstrap": "^5.2.3",
    "datatables.net": "^1.13.4",
    "datatables.net-bs5": "^1.13.4",
    "jquery": "^3.7.0",
    "moment": "^2.29.4",
    "rxjs": "^7.8.1",
    "tslib": "^2.5.0",
    "zone.js": "~0.13.0"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~16.0.1",
    "@angular-eslint/builder": "~16.0.1",
    "@angular-eslint/eslint-plugin": "~16.0.1",
    "@angular-eslint/eslint-plugin-template": "~16.0.1",
    "@angular-eslint/schematics": "~16.0.1",
    "@angular-eslint/template-parser": "~16.0.1",
    "@angular/cli": "^16.0.1",
    "@angular/compiler-cli": "~16.0.1",
    "@angular/language-service": "~16.0.1",
    "@biesbjerg/ngx-translate-extract": "^7.0.3",
    "@biesbjerg/ngx-translate-extract-marker": "^1.0.0",
    "@cypress/schematic": "^2.0.3",
    "@ngneat/until-destroy": "^9.0.0",
    "@ngx-rocket/scripts": "^5.2.2",
    "@types/datatables.net": "^1.10.24",
    "@types/jasmine": "^4.0.0",
    "@types/jasminewd2": "^2.0.8",
    "@types/jquery": "^3.5.9",
    "@types/node": "^18.11.17",
    "@typescript-eslint/eslint-plugin": "~5.34.0",
    "@typescript-eslint/parser": "~5.34.0",
    "angular-cli-ghpages": "^1.0.3",
    "cypress": "~10.6.0",
    "eslint": "^8.3.0",
    "eslint-plugin-import": "latest",
    "eslint-plugin-jsdoc": "latest",
    "eslint-plugin-prefer-arrow": "latest",
    "hads": "^3.0.3",
    "https-proxy-agent": "^5.0.0",
    "husky": "^8.0.1",
    "jasmine-core": "~4.2.0",
    "jasmine-spec-reporter": "~7.0.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.0.0",
    "karma-junit-reporter": "^2.0.1",
    "postcss": "^8.4.5",
    "prettier": "^2.2.1",
    "pretty-quick": "^3.1.0",
    "stylelint": "~14.11.0",
    "stylelint-config-prettier": "^9.0.3",
    "stylelint-config-recommended-scss": "~7.0.0",
    "stylelint-config-standard": "~28.0.0",
    "ts-node": "^10.9.1",
    "typescript": "~5.0.4"
  },
  "prettier": {
    "singleQuote": true,
    "overrides": [
      {
        "files": "*.scss",
        "options": {
          "singleQuote": false
        }
      }
    ]
  }
}

The package.json file is used in a Node.js project to manage dependencies, scripts, and other project metadata. The name field specifies the name of the project, and the version field specifies the version of the project.

The dependencies field lists the dependencies that the project relies on. Each dependency is specified as a key-value pair, where the key is the name of the package, and the value is the version range that should be used. For example, @angular/* specifies a range of ~16.0.1, which means that any version of the package that is compatible with the version 16.0.1 will be used.

After upgrading, I attempted to run ng serve, but it failed due to an incompatibility issue with the parameter suppressImplicitAnyIndexErrors in the tsconfig.json file. To resolve this issue, I removed this parameter and was then able to successfully run the project without any problems. See the screenshot below for more info.

Part 2: Upgrading Angular components to standalone type

Angular 16 contains improved documentation and schematics for standalone components. To support developers transitioning their apps to standalone APIs, the Angular team developed migration schematics and a standalone migration guide. Once you’re in your project directory run:

ng generate @angular/core:standalone

The command will prompt you to choose the type of migration as shown in the screenshot below:

I chose to Bootstrap the application using standalone APIs, which removes the need for the app.modules.ts file. The application is bootstrapped from main.ts using the AppComponent. See below for the source code of main.ts.

/*
 * Entry point of the application.
 * Only platform bootstrapping code should be here.
 * For app-specific initialization, use `app/app.component.ts`.
 */

import { enableProdMode, importProvidersFrom } from '@angular/core';

import { environment } from '@env/environment';
import { AppComponent } from './app/app.component';
import { AppRoutingModule } from './app/app-routing.module';
import { HomeModule } from './app/home/home.module';
import { ShellModule } from './app/shell/shell.module';
import { CoreModule } from '@app/core';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import { FormsModule } from '@angular/forms';
import { ServiceWorkerModule } from '@angular/service-worker';
import { BrowserModule, bootstrapApplication } from '@angular/platform-browser';
import { RouteReuseStrategy, RouterModule } from '@angular/router';
import { ApiPrefixInterceptor, ErrorHandlerInterceptor, RouteReusableStrategy, SharedModule } from '@shared';
import { HTTP_INTERCEPTORS, withInterceptorsFromDi, provideHttpClient } from '@angular/common/http';

if (environment.production) {
  enableProdMode();
}

bootstrapApplication(AppComponent, {
  providers: [
    importProvidersFrom(
      BrowserModule,
      ServiceWorkerModule.register('./ngsw-worker.js', { enabled: environment.production }),
      FormsModule,
      RouterModule,
      TranslateModule.forRoot(),
      NgbModule,
      CoreModule.forRoot(),
      SharedModule,
      ShellModule,
      HomeModule,
      AppRoutingModule
    ),
    {
      provide: HTTP_INTERCEPTORS,
      useClass: ApiPrefixInterceptor,
      multi: true,
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: ErrorHandlerInterceptor,
      multi: true,
    },
    {
      provide: RouteReuseStrategy,
      useClass: RouteReusableStrategy,
    },
    provideHttpClient(withInterceptorsFromDi()),
  ],
}).catch((err) => console.error(err));

This main.ts is the entry point of the Angular application, which bootstraps the AppComponent and initializes the application’s modules, services, and dependencies. It includes imports for various Angular modules, third-party libraries, and custom modules that define the application’s structure.

After converting the code to bootstrap the app using AppComponent, I then re-ran the command ng generate @angular/core:standalone and this time I chose the option Covert all components, directives and pipes to standalone as shown in the screenshot below.

This time, it converted all the components in the project to standalone components. Below is an example of the converted app.component.ts.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, NavigationEnd, ActivatedRoute, RouterOutlet } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { merge } from 'rxjs';
import { filter, map, switchMap } from 'rxjs/operators';

import { environment } from '@env/environment';
import { Logger, UntilDestroy, untilDestroyed } from '@shared';
import { I18nService } from '@app/i18n';
import { ToastsContainer } from './@shared/toast/toasts-container.component';

const log = new Logger('App');

@UntilDestroy()
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  standalone: true,
  imports: [RouterOutlet, ToastsContainer],
})
export class AppComponent implements OnInit, OnDestroy {
  constructor(
    private router: Router,
    private activatedRoute: ActivatedRoute,
    private titleService: Title,
    private translateService: TranslateService,
    // do not remove the analytics injection, even if the call in ngOnInit() is removed
    // this injection initializes page tracking through the router
    private i18nService: I18nService
  ) {}

  ngOnInit() {
    // Setup logger
    if (environment.production) {
      Logger.enableProductionMode();
    }

    log.debug('init');

    // Setup translations
    this.i18nService.init(environment.defaultLanguage, environment.supportedLanguages);

    const onNavigationEnd = this.router.events.pipe(filter((event) => event instanceof NavigationEnd));

    // Change page title on navigation or language change, based on route data
    merge(this.translateService.onLangChange, onNavigationEnd)
      .pipe(
        map(() => {
          let route = this.activatedRoute;
          while (route.firstChild) {
            route = route.firstChild;
          }
          return route;
        }),
        filter((route) => route.outlet === 'primary'),
        switchMap((route) => route.data),
        untilDestroyed(this)
      )
      .subscribe((event) => {
        const title = event['title'];
        if (title) {
          this.titleService.setTitle(this.translateService.instant(title));
        }
      });
  }

  ngOnDestroy() {
    this.i18nService.destroy();
  }
}

This is the source code for the root component of the Angular application, AppComponent. The AppComponent class defines the HTML template and styles for the root component, which includes a router outlet and a toast container component. Notice that the standalone: true which indicates this is a standalone component.

Source Code

You can find the upgraded Angular 16 source code of the project on GitHub.

https://github.com/workcontrolgit/TalentManagement-Client-Angular16

If you want to get the source code for the backend Net7 Web API and the Token Service Duende Identity Server with Admin UI, please visit the Fullstack Angular 15, Bootstrap 5 & NET 7 API: Project Demo and Tutorial.

Angular 16 Demo

Demo screenshot: Homepage with a listing of login accounts

I have leveraged the power of GitHub Actions to set up a demo of the Angular 16 client on the GitHub Page. Additionally, I have seamlessly integrated the backend ApiResource and Token Service in Azure. To witness the demo in action, simply click the link provided below:

Demo Link

Demo screenshot: Pagination, Filter, and Detail Modal Popup from the Employee menu

This integration between GitHub Actions and Azure ensures a smooth and efficient deployment process, allowing you to experience the Angular client along with the associated backend services. Take this opportunity to explore the demo and discover the seamless collaboration between the front-end and back-end components. Follow the link to access the demo and witness the power of this combined solution.

Recommended Contents

  1. Load Angular Component into Bootstrap Modal | Tutorial
  2. Implement Toast with Bootstrap 5 in Angular 15 or 16 | Tutorial
  3. Fullstack Angular 15, Bootstrap 5 & NET 7 API: Project Demo and Tutorial
  4. Seven Object-Oriented Programming Jokes
  5. What is package.json in the Angular app?

Summary

Photo by Thomas Peham on Unsplash

In this blog, we explored upgrading Angular from version 15 to 16, including the necessary package.json requirements. We also learned how to use Angular CLI to automatically convert Angular components to standalone type, making them more flexible and loosely coupled.

When I get bitten by insects, one part of my brain is like “be smart, leave it alone”. The other part is like…

“Scratch that” ☹

The standalone component concept in Angular reminds me of user controls in the old .NET Framework’s Web Forms. User control is self-contained and can be called from many ASP.NET web pages, which is similar to how a standalone component can be used across multiple Angular modules. I find this approach to be more loosely coupled and flexible, and I really appreciate it in Angular.

Thanks for reading! Hope you found it useful. Want more? Please follow me and become a member on Medium for more articles. With your support, I’ll keep creating awesome content for you. Have a great day ahead! — Fuji Nguyen

Angular
Angular 16
Upgrade
Recommended from ReadMedium