From 2b7c94d368adc51de871322ea1e4dcb3df74f5fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20K=C3=B6hler?= <74658813+SebastianKohler@users.noreply.github.com> Date: Tue, 16 Jan 2024 08:58:10 +0200 Subject: [PATCH] fix: legacy setup code from Angular 15 to 17 migration This commit attempts to fix legacy setup code from the Angular 15 to 17 migration. Since Angular 17 uses standalone mode instead of modules as default and the Angular documentation for v17 presumes standalone mode, it is difficult to percieve what the proper Angular 17 setup should be for modules based apps with both server side rendering and i18n enabled. This commit: - Changes `HttpClientModule` to `provideHttpClient(withFetch())` (recommended for SSR apps: https://angular.io/api/common/http/provideHttpClient). - Changes the polyfills config in `angular.json` and deletes the `src/polyfills.ts` file. - Renames the app component from `DigitalEditionApp` to the default `AppComponent`. - Removes the check for `document.readyState` and the `DOMContentLoaded` event listener before bootstrapping in `main.ts`. Most of the changes are based on what the source code of a new modules based Angular 17 app with SSR and i18n enabled looks like (generated with `ng new --routing --ssr --no-standalone`). --- CHANGELOG.md | 4 +++ angular.json | 9 ++++-- server.ts | 2 +- src/app/app.component.spec.ts | 8 +++--- src/app/app.component.ts | 2 +- src/app/app.module.ts | 10 +++---- src/app/app.server.module.ts | 6 ++-- src/main.server.ts | 2 +- src/main.ts | 12 ++------ src/polyfills.ts | 53 ----------------------------------- src/zone-flags.ts | 6 ---- tsconfig.app.json | 4 +-- tsconfig.json | 2 +- tsconfig.spec.json | 4 --- 14 files changed, 32 insertions(+), 92 deletions(-) delete mode 100644 src/polyfills.ts delete mode 100644 src/zone-flags.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 52acd33..a5243c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this ## [Unreleased] +### Fixed + +- Legacy setup code from Angular 15 to 17 migration. Angular 17 introduced several changes to the `app.module.\*`, `main.server.ts`, `main.ts`, `server.ts` and `tsconfig.\*` files. During the original v15 -> v17 update of the app all of these changes were not implemented. This fix attempts to align the app with the setup of a new modules based Angular 17 app that has SSR and i18n enabled. + ## [1.2.0] – 2024-01-10 diff --git a/angular.json b/angular.json index 0aca148..2d200cf 100644 --- a/angular.json +++ b/angular.json @@ -30,7 +30,9 @@ "outputPath": "dist/app/browser", "index": "src/index.html", "main": "src/main.ts", - "polyfills": "src/polyfills.ts", + "polyfills": [ + "zone.js" + ], "tsConfig": "tsconfig.app.json", "inlineStyleLanguage": "scss", "i18nDuplicateTranslation": "error", @@ -168,7 +170,10 @@ "builder": "@angular-devkit/build-angular:karma", "options": { "main": "src/test.ts", - "polyfills": "src/polyfills.ts", + "polyfills": [ + "zone.js", + "zone.js/testing" + ], "tsConfig": "tsconfig.spec.json", "karmaConfig": "karma.conf.js", "inlineStyleLanguage": "scss", diff --git a/server.ts b/server.ts index e8993e7..0d687b1 100644 --- a/server.ts +++ b/server.ts @@ -10,7 +10,7 @@ import * as express from 'express'; import { existsSync } from 'fs'; import { join } from 'path'; -import { AppServerModule } from './src/main.server'; +import AppServerModule from './src/main.server'; import { environment } from './src/environments/environment'; // The Express app is exported so that it can be used by serverless Functions. diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 5b99ee5..51b7b65 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,19 +1,19 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; -import { DigitalEditionApp } from './app.component'; +import { AppComponent } from './app.component'; -describe('DigitalEditionApp', () => { +describe('AppComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [DigitalEditionApp], + declarations: [AppComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA], }).compileComponents(); }); it('should create the app', () => { - const fixture = TestBed.createComponent(DigitalEditionApp); + const fixture = TestBed.createComponent(AppComponent); const app = fixture.componentInstance; expect(app).toBeTruthy(); }); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 03c12f1..d1f5992 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -13,7 +13,7 @@ import { isBrowser } from '@utility-functions'; templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) -export class DigitalEditionApp implements OnDestroy, OnInit { +export class AppComponent implements OnDestroy, OnInit { appIsStarting: boolean = true; collectionID: string = ''; collectionSideMenuInitialUrlSegments: UrlSegment[]; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index c46b19e..6f84e46 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,12 +1,12 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { HttpClientModule } from '@angular/common/http'; +import { provideHttpClient, withFetch } from '@angular/common/http'; import { BrowserModule, Title } from '@angular/platform-browser'; import { RouteReuseStrategy } from '@angular/router'; import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; import { AppRoutingModule } from './app-routing.module'; -import { DigitalEditionApp } from './app.component'; +import { AppComponent } from './app.component'; import { CollectionSideMenuComponent } from '@components/menus/collection-side/collection-side-menu.component'; import { MainSideMenuComponent } from '@components/menus/main-side/main-side-menu.component'; import { TopMenuComponent } from '@components/menus/top/top-menu.component'; @@ -14,7 +14,7 @@ import { TopMenuComponent } from '@components/menus/top/top-menu.component'; @NgModule({ declarations: [ - DigitalEditionApp + AppComponent ], imports: [ BrowserModule, @@ -25,16 +25,16 @@ import { TopMenuComponent } from '@components/menus/top/top-menu.component'; } ), AppRoutingModule, - HttpClientModule, CommonModule, CollectionSideMenuComponent, MainSideMenuComponent, TopMenuComponent ], providers: [ + provideHttpClient(withFetch()), Title, { provide: RouteReuseStrategy, useClass: IonicRouteStrategy } ], - bootstrap: [DigitalEditionApp] + bootstrap: [AppComponent] }) export class AppModule {} diff --git a/src/app/app.server.module.ts b/src/app/app.server.module.ts index dc24044..6d3c280 100644 --- a/src/app/app.server.module.ts +++ b/src/app/app.server.module.ts @@ -1,9 +1,10 @@ import { NgModule } from '@angular/core'; +import { provideHttpClient, withFetch } from '@angular/common/http'; import { ServerModule } from '@angular/platform-server'; import { IonicServerModule } from '@ionic/angular-server'; import { AppModule } from './app.module'; -import { DigitalEditionApp } from './app.component'; +import { AppComponent } from './app.component'; @NgModule({ @@ -12,6 +13,7 @@ import { DigitalEditionApp } from './app.component'; ServerModule, IonicServerModule, ], - bootstrap: [DigitalEditionApp], + providers: [provideHttpClient(withFetch())], + bootstrap: [AppComponent], }) export class AppServerModule {} diff --git a/src/main.server.ts b/src/main.server.ts index d7c01cd..f1273f5 100644 --- a/src/main.server.ts +++ b/src/main.server.ts @@ -1 +1 @@ -export { AppServerModule } from './app/app.server.module'; +export { AppServerModule as default } from './app/app.server.module'; diff --git a/src/main.ts b/src/main.ts index 96deb66..4bd9e02 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,13 +11,5 @@ if (environment.production) { enableProdMode(); } -function bootstrap() { - platformBrowserDynamic().bootstrapModule(AppModule) - .catch(err => console.log(err)); -}; - -if (document.readyState === 'complete') { - bootstrap(); -} else { - document.addEventListener('DOMContentLoaded', bootstrap); -} +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/src/polyfills.ts b/src/polyfills.ts deleted file mode 100644 index 429bb9e..0000000 --- a/src/polyfills.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * This file includes polyfills needed by Angular and is loaded before the app. - * You can add your own extra polyfills to this file. - * - * This file is divided into 2 sections: - * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. - * 2. Application imports. Files imported after ZoneJS that should be loaded before your main - * file. - * - * The current setup is for so-called "evergreen" browsers; the last versions of browsers that - * automatically update themselves. This includes recent versions of Safari, Chrome (including - * Opera), Edge on the desktop, and iOS and Chrome on mobile. - * - * Learn more in https://angular.io/guide/browser-support - */ - -/*************************************************************************************************** - * BROWSER POLYFILLS - */ - -/** - * By default, zone.js will patch all possible macroTask and DomEvents - * user can disable parts of macroTask/DomEvents patch by setting following flags - * because those flags need to be set before `zone.js` being loaded, and webpack - * will put import in the top of bundle, so user need to create a separate file - * in this directory (for example: zone-flags.ts), and put the following flags - * into that file, and then add the following code before importing zone.js. - * import './zone-flags'; - * - * The flags allowed in zone-flags.ts are listed here. - * - * The following flags will work for all browsers. - * - * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame - * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick - * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames - * - * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js - * with the following flag, it will bypass `zone.js` patch for IE/Edge - * - * (window as any).__Zone_enable_cross_context_check = true; - * - */ - -/*************************************************************************************************** - * Zone JS is required by default for Angular itself. - */ -import 'zone.js'; // Included with Angular CLI. - - -/*************************************************************************************************** - * APPLICATION IMPORTS - */ diff --git a/src/zone-flags.ts b/src/zone-flags.ts deleted file mode 100644 index c84245f..0000000 --- a/src/zone-flags.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Prevents Angular change detection from - * running with certain Web Component callbacks - */ -// eslint-disable-next-line no-underscore-dangle -(window as any).__Zone_disable_customElements = true; diff --git a/tsconfig.app.json b/tsconfig.app.json index 5a2fbb0..ec15b4b 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -4,12 +4,12 @@ "compilerOptions": { "outDir": "./out-tsc/app", "types": [ + "node", "@angular/localize" ] }, "files": [ - "src/main.ts", - "src/polyfills.ts" + "src/main.ts" ], "include": [ "src/**/*.d.ts" diff --git a/tsconfig.json b/tsconfig.json index 7ba2613..6d505d9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,11 +18,11 @@ "importHelpers": true, "target": "ES2022", "module": "ES2022", + "useDefineForClassFields": false, "lib": [ "ES2022", "dom" ], - "useDefineForClassFields": false, "allowSyntheticDefaultImports": true, "strictPropertyInitialization": false, "paths": { diff --git a/tsconfig.spec.json b/tsconfig.spec.json index 27b5f92..c63b698 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -8,10 +8,6 @@ "@angular/localize" ] }, - "files": [ - "src/test.ts", - "src/polyfills.ts" - ], "include": [ "src/**/*.spec.ts", "src/**/*.d.ts"