diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ff1b3d21b..a1993eae8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,7 +56,9 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 20 + # Angular 22 starters require Node ^22.22.3 || ^24.15.0 || >=26.0.0. + # All other current/legacy starters in this matrix run on Node 22 too. + node-version: 22 cache: npm cache-dependency-path: | build/${{ matrix.framework }}/ionic.starter.json diff --git a/angular-standalone/base/.eslintrc.json b/angular-standalone/base/.eslintrc.json deleted file mode 100644 index 9d48db47b..000000000 --- a/angular-standalone/base/.eslintrc.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "root": true, - "ignorePatterns": ["projects/**/*"], - "overrides": [ - { - "files": ["*.ts"], - "parserOptions": { - "project": ["tsconfig.json"], - "createDefaultProgram": true - }, - "extends": [ - "plugin:@angular-eslint/recommended", - "plugin:@angular-eslint/template/process-inline-templates" - ], - "rules": { - "@angular-eslint/component-class-suffix": [ - "error", - { - "suffixes": ["Page", "Component"] - } - ], - "@angular-eslint/component-selector": [ - "error", - { - "type": "element", - "prefix": "app", - "style": "kebab-case" - } - ], - "@angular-eslint/directive-selector": [ - "error", - { - "type": "attribute", - "prefix": "app", - "style": "camelCase" - } - ] - } - }, - { - "files": ["*.html"], - "extends": ["plugin:@angular-eslint/template/recommended"], - "rules": {} - } - ] -} diff --git a/angular-standalone/base/angular.json b/angular-standalone/base/angular.json index 9b72354a6..e23c4e7de 100644 --- a/angular-standalone/base/angular.json +++ b/angular-standalone/base/angular.json @@ -16,16 +16,14 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:application", + "builder": "@angular/build:application", "options": { "outputPath": { "base": "www", "browser": "" }, "index": "src/index.html", - "polyfills": [ - "src/polyfills.ts" - ], + "polyfills": [], "tsConfig": "tsconfig.app.json", "inlineStyleLanguage": "scss", "assets": [ @@ -74,7 +72,7 @@ "defaultConfiguration": "production" }, "serve": { - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular/build:dev-server", "configurations": { "production": { "buildTarget": "app:build:production" @@ -89,32 +87,20 @@ "defaultConfiguration": "development" }, "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", + "builder": "@angular/build:extract-i18n", "options": { "buildTarget": "app:build" } }, "test": { - "builder": "@angular-devkit/build-angular:karma", + "builder": "@angular/build:unit-test", "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - { - "glob": "**/*", - "input": "src/assets", - "output": "assets" - } - ], - "styles": ["src/global.scss", "src/theme/variables.scss"], - "scripts": [] + "buildTarget": "::development", + "setupFiles": ["src/test-setup.ts"] }, "configurations": { "ci": { - "progress": false, "watch": false } } diff --git a/angular-standalone/base/eslint.config.js b/angular-standalone/base/eslint.config.js new file mode 100644 index 000000000..2b4e5da28 --- /dev/null +++ b/angular-standalone/base/eslint.config.js @@ -0,0 +1,31 @@ +// @ts-check +const tseslint = require("typescript-eslint"); +const angular = require("angular-eslint"); + +module.exports = tseslint.config( + { + files: ["**/*.ts"], + ignores: ["projects/**/*"], + extends: [...angular.configs.tsRecommended], + processor: angular.processInlineTemplates, + rules: { + "@angular-eslint/component-class-suffix": [ + "error", + { suffixes: ["Page", "Component"] }, + ], + "@angular-eslint/component-selector": [ + "error", + { type: "element", prefix: "app", style: "kebab-case" }, + ], + "@angular-eslint/directive-selector": [ + "error", + { type: "attribute", prefix: "app", style: "camelCase" }, + ], + }, + }, + { + files: ["**/*.html"], + extends: [...angular.configs.templateRecommended], + rules: {}, + } +); diff --git a/angular-standalone/base/karma.conf.js b/angular-standalone/base/karma.conf.js deleted file mode 100644 index 611c27fba..000000000 --- a/angular-standalone/base/karma.conf.js +++ /dev/null @@ -1,44 +0,0 @@ -// Karma configuration file, see link for more information -// https://karma-runner.github.io/1.0/config/configuration-file.html - -module.exports = function (config) { - config.set({ - basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage'), - require('@angular-devkit/build-angular/plugins/karma') - ], - client: { - jasmine: { - // you can add configuration options for Jasmine here - // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html - // for example, you can disable the random execution with `random: false` - // or set a specific seed with `seed: 4321` - }, - clearContext: false // leave Jasmine Spec Runner output visible in browser - }, - jasmineHtmlReporter: { - suppressAll: true // removes the duplicated traces - }, - coverageReporter: { - dir: require('path').join(__dirname, './coverage/app'), - subdir: '.', - reporters: [ - { type: 'html' }, - { type: 'text-summary' } - ] - }, - reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false, - restartOnFileChange: true - }); -}; diff --git a/angular-standalone/base/package.json b/angular-standalone/base/package.json index 81c0d135d..58ac34917 100644 --- a/angular-standalone/base/package.json +++ b/angular-standalone/base/package.json @@ -13,45 +13,28 @@ }, "private": true, "dependencies": { - "@angular/animations": "20.3.25", - "@angular/common": "20.3.25", - "@angular/compiler": "20.3.25", - "@angular/core": "20.3.25", - "@angular/forms": "20.3.25", - "@angular/platform-browser": "20.3.25", - "@angular/platform-browser-dynamic": "20.3.25", - "@angular/router": "20.3.25", - "@ionic/angular": "^8.0.0", + "@angular/common": "22.0.1", + "@angular/compiler": "22.0.1", + "@angular/core": "22.0.1", + "@angular/forms": "22.0.1", + "@angular/platform-browser": "22.0.1", + "@angular/router": "22.0.1", + "@ionic/angular": "8.8.9-dev.11781098612.122c6758", "ionicons": "^7.0.0", "rxjs": "~7.8.0", - "tslib": "^2.3.0", - "zone.js": "~0.15.0" + "tslib": "^2.3.0" }, "devDependencies": { - "@angular-devkit/build-angular": "20.3.28", - "@angular-eslint/builder": "20.7.0", - "@angular-eslint/eslint-plugin": "20.7.0", - "@angular-eslint/eslint-plugin-template": "20.7.0", - "@angular-eslint/schematics": "20.7.0", - "@angular-eslint/template-parser": "20.7.0", - "@angular/cli": "20.3.28", - "@angular/compiler-cli": "20.3.25", - "@angular/language-service": "20.3.25", - "@ionic/angular-toolkit": "^12.0.0", - "@types/jasmine": "~5.1.0", - "@typescript-eslint/eslint-plugin": "^8.18.0", - "@typescript-eslint/parser": "^8.18.0", + "@angular/build": "22.0.1", + "@angular/cli": "22.0.1", + "@angular/compiler-cli": "22.0.1", + "@angular/language-service": "22.0.1", + "@ionic/angular-toolkit": "^12.3.0", + "angular-eslint": "22.0.0", "eslint": "^9.16.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsdoc": "^48.2.1", - "eslint-plugin-prefer-arrow": "1.2.2", - "jasmine-core": "~5.1.0", - "jasmine-spec-reporter": "~5.0.0", - "karma": "~6.4.0", - "karma-chrome-launcher": "~3.2.0", - "karma-coverage": "~2.2.0", - "karma-jasmine": "~5.1.0", - "karma-jasmine-html-reporter": "~2.1.0", - "typescript": "~5.9.0" + "jsdom": "^26.0.0", + "typescript": "~6.0.0", + "typescript-eslint": "^8.61.0", + "vitest": "^4.0.8" } } diff --git a/angular-standalone/base/src/main.ts b/angular-standalone/base/src/main.ts index db355ecf1..2031c98eb 100644 --- a/angular-standalone/base/src/main.ts +++ b/angular-standalone/base/src/main.ts @@ -1,5 +1,6 @@ +import { provideZonelessChangeDetection } from '@angular/core'; import { bootstrapApplication } from '@angular/platform-browser'; -import { RouteReuseStrategy, provideRouter, withPreloading, PreloadAllModules } from '@angular/router'; +import { RouteReuseStrategy, provideRouter, withComponentInputBinding, withPreloading, PreloadAllModules } from '@angular/router'; import { IonicRouteStrategy, provideIonicAngular } from '@ionic/angular/standalone'; import { routes } from './app/app.routes'; @@ -7,8 +8,9 @@ import { AppComponent } from './app/app.component'; bootstrapApplication(AppComponent, { providers: [ + provideZonelessChangeDetection(), { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, provideIonicAngular(), - provideRouter(routes, withPreloading(PreloadAllModules)), + provideRouter(routes, withPreloading(PreloadAllModules), withComponentInputBinding()), ], }); diff --git a/angular-standalone/base/src/polyfills.ts b/angular-standalone/base/src/polyfills.ts deleted file mode 100644 index e2cc08075..000000000 --- a/angular-standalone/base/src/polyfills.ts +++ /dev/null @@ -1,55 +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; - * - */ - -import './zone-flags'; - -/*************************************************************************************************** - * Zone JS is required by default for Angular itself. - */ -import 'zone.js'; // Included with Angular CLI. - - -/*************************************************************************************************** - * APPLICATION IMPORTS - */ diff --git a/angular-standalone/base/src/test-setup.ts b/angular-standalone/base/src/test-setup.ts new file mode 100644 index 000000000..adcbbc9c2 --- /dev/null +++ b/angular-standalone/base/src/test-setup.ts @@ -0,0 +1,16 @@ +// Polyfills for running unit tests under jsdom (the default Vitest environment). +// Ionic components such as ion-menu and ion-split-pane query `window.matchMedia`, +// which jsdom does not implement. +if (!window.matchMedia) { + window.matchMedia = (query: string): MediaQueryList => + ({ + matches: false, + media: query, + onchange: null, + addListener: () => undefined, + removeListener: () => undefined, + addEventListener: () => undefined, + removeEventListener: () => undefined, + dispatchEvent: () => false, + }) as MediaQueryList; +} diff --git a/angular-standalone/base/src/test.ts b/angular-standalone/base/src/test.ts deleted file mode 100644 index 51bb0206a..000000000 --- a/angular-standalone/base/src/test.ts +++ /dev/null @@ -1,14 +0,0 @@ -// This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import 'zone.js/testing'; -import { getTestBed } from '@angular/core/testing'; -import { - BrowserDynamicTestingModule, - platformBrowserDynamicTesting -} from '@angular/platform-browser-dynamic/testing'; - -// First, initialize the Angular testing environment. -getTestBed().initTestEnvironment( - BrowserDynamicTestingModule, - platformBrowserDynamicTesting(), -); diff --git a/angular-standalone/base/src/zone-flags.ts b/angular-standalone/base/src/zone-flags.ts deleted file mode 100644 index c84245fd3..000000000 --- a/angular-standalone/base/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/angular-standalone/base/tsconfig.app.json b/angular-standalone/base/tsconfig.app.json index 82d91dc4a..374cc9d29 100644 --- a/angular-standalone/base/tsconfig.app.json +++ b/angular-standalone/base/tsconfig.app.json @@ -6,8 +6,7 @@ "types": [] }, "files": [ - "src/main.ts", - "src/polyfills.ts" + "src/main.ts" ], "include": [ "src/**/*.d.ts" diff --git a/angular-standalone/base/tsconfig.json b/angular-standalone/base/tsconfig.json index 3fc44e546..946561a83 100644 --- a/angular-standalone/base/tsconfig.json +++ b/angular-standalone/base/tsconfig.json @@ -2,7 +2,6 @@ { "compileOnSave": false, "compilerOptions": { - "baseUrl": "./", "outDir": "./dist/out-tsc", "forceConsistentCasingInFileNames": true, "esModuleInterop": true, @@ -14,11 +13,11 @@ "sourceMap": true, "declaration": false, "experimentalDecorators": true, - "moduleResolution": "node", + "moduleResolution": "bundler", "importHelpers": true, "target": "es2022", "module": "es2020", - "lib": ["es2018", "dom"], + "lib": ["es2022", "dom"], "skipLibCheck": true, "useDefineForClassFields": false }, diff --git a/angular-standalone/base/tsconfig.spec.json b/angular-standalone/base/tsconfig.spec.json index 092345b02..323910146 100644 --- a/angular-standalone/base/tsconfig.spec.json +++ b/angular-standalone/base/tsconfig.spec.json @@ -4,15 +4,12 @@ "compilerOptions": { "outDir": "./out-tsc/spec", "types": [ - "jasmine" + "vitest/globals" ] }, - "files": [ - "src/test.ts", - "src/polyfills.ts" - ], "include": [ "src/**/*.spec.ts", - "src/**/*.d.ts" + "src/**/*.d.ts", + "src/test-setup.ts" ] } diff --git a/angular-standalone/official/blank/ionic.starter.json b/angular-standalone/official/blank/ionic.starter.json index e28c7e4ac..87b690f45 100644 --- a/angular-standalone/official/blank/ionic.starter.json +++ b/angular-standalone/official/blank/ionic.starter.json @@ -7,6 +7,6 @@ "www" ], "scripts": { - "test": "npm run lint && npm run build && npm run test -- --configuration=ci --browsers=ChromeHeadless" + "test": "npm run lint && npm run build && npm run test -- --configuration=ci" } } diff --git a/angular-standalone/official/list/ionic.starter.json b/angular-standalone/official/list/ionic.starter.json index 9e35de2ef..9d399db0b 100644 --- a/angular-standalone/official/list/ionic.starter.json +++ b/angular-standalone/official/list/ionic.starter.json @@ -7,6 +7,6 @@ "www" ], "scripts": { - "test": "npm run lint && npm run build && npm run test -- --configuration=ci --browsers=ChromeHeadless" + "test": "npm run lint && npm run build && npm run test -- --configuration=ci" } } diff --git a/angular-standalone/official/list/src/app/home/home.page.html b/angular-standalone/official/list/src/app/home/home.page.html index bbdc656f4..0e49dd03e 100644 --- a/angular-standalone/official/list/src/app/home/home.page.html +++ b/angular-standalone/official/list/src/app/home/home.page.html @@ -20,7 +20,7 @@ - @for (message of getMessages(); track message) { + @for (message of messages; track message.id) { } diff --git a/angular-standalone/official/list/src/app/home/home.page.ts b/angular-standalone/official/list/src/app/home/home.page.ts index 9aa2ddb19..148f88654 100644 --- a/angular-standalone/official/list/src/app/home/home.page.ts +++ b/angular-standalone/official/list/src/app/home/home.page.ts @@ -1,9 +1,8 @@ - import { Component, inject } from '@angular/core'; import { RefresherCustomEvent, IonHeader, IonToolbar, IonTitle, IonContent, IonRefresher, IonRefresherContent, IonList } from '@ionic/angular/standalone'; import { MessageComponent } from '../message/message.component'; -import { DataService, Message } from '../services/data.service'; +import { DataService } from '../services/data.service'; @Component({ selector: 'app-home', @@ -13,15 +12,11 @@ import { DataService, Message } from '../services/data.service'; }) export class HomePage { private data = inject(DataService); - constructor() {} + protected readonly messages = this.data.getMessages(); refresh(ev: any) { setTimeout(() => { (ev as RefresherCustomEvent).detail.complete(); }, 3000); } - - getMessages(): Message[] { - return this.data.getMessages(); - } } diff --git a/angular-standalone/official/list/src/app/message/message.component.html b/angular-standalone/official/list/src/app/message/message.component.html index e5128308d..b530dc791 100644 --- a/angular-standalone/official/list/src/app/message/message.component.html +++ b/angular-standalone/official/list/src/app/message/message.component.html @@ -1,4 +1,4 @@ -@if (message) { +@if (message(); as message) { {{ message.fromName }} {{ message.date }} - @if (isIos()) { + @if (isIos) {