From ca536826846a19536d2b8267f56aa83d37a8cd19 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Tue, 15 Aug 2023 14:32:27 -0500 Subject: [PATCH] feat(angular): add standalone provideIonicAngular (#27996) --- .../src/directives/navigation/nav-delegate.ts | 1 - .../src/directives/navigation/nav-delegate.ts | 1 - packages/angular/standalone/src/index.ts | 2 +- .../standalone/src/navigation/nav-delegate.ts | 1 - .../standalone/src/providers/ionic-angular.ts | 51 +++++++++++++++++++ .../test/apps/ng14/src/main-standalone.ts | 23 +++++++++ .../standalone/overlay-controllers.spec.ts | 17 +++++++ .../base/e2e/src/standalone/providers.spec.ts | 11 ++++ .../base/src/app/app-standalone.component.ts | 11 ++++ .../app-standalone/app.component.ts | 9 ---- .../standalone/app-standalone/app.routes.ts | 2 + .../src/app/standalone/nav/nav.component.ts | 4 +- .../overlay-controllers.component.html | 4 ++ .../overlay-controllers.component.ts | 36 +++++++++++++ .../providers/providers.component.html | 4 ++ .../providers/providers.component.ts | 15 ++++++ .../angular/test/base/src/main-standalone.ts | 17 +++++++ packages/angular/test/base/src/main.ts | 22 ++++++-- 18 files changed, 210 insertions(+), 21 deletions(-) create mode 100644 packages/angular/standalone/src/providers/ionic-angular.ts create mode 100644 packages/angular/test/apps/ng14/src/main-standalone.ts create mode 100644 packages/angular/test/base/e2e/src/standalone/overlay-controllers.spec.ts create mode 100644 packages/angular/test/base/e2e/src/standalone/providers.spec.ts create mode 100644 packages/angular/test/base/src/app/app-standalone.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/overlay-controllers/overlay-controllers.component.html create mode 100644 packages/angular/test/base/src/app/standalone/overlay-controllers/overlay-controllers.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/providers/providers.component.html create mode 100644 packages/angular/test/base/src/app/standalone/providers/providers.component.ts create mode 100644 packages/angular/test/base/src/main-standalone.ts diff --git a/packages/angular/common/src/directives/navigation/nav-delegate.ts b/packages/angular/common/src/directives/navigation/nav-delegate.ts index 009c2f17da..58429961db 100644 --- a/packages/angular/common/src/directives/navigation/nav-delegate.ts +++ b/packages/angular/common/src/directives/navigation/nav-delegate.ts @@ -37,7 +37,6 @@ export class NavDelegate { ref: ElementRef, environmentInjector: EnvironmentInjector, injector: Injector, - // TODO FW-4766: Remove AngularDelegate angularDelegate: AngularDelegate, protected z: NgZone ) { diff --git a/packages/angular/src/directives/navigation/nav-delegate.ts b/packages/angular/src/directives/navigation/nav-delegate.ts index d8c6d1231d..9d61cd5d65 100644 --- a/packages/angular/src/directives/navigation/nav-delegate.ts +++ b/packages/angular/src/directives/navigation/nav-delegate.ts @@ -10,7 +10,6 @@ export class NavDelegate extends NavDelegateBase { ref: ElementRef, environmentInjector: EnvironmentInjector, injector: Injector, - // TODO FW-4766: Remove AngularDelegate angularDelegate: AngularDelegate, z: NgZone ) { diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts index a1cdeac984..4f012c4cd3 100644 --- a/packages/angular/standalone/src/index.ts +++ b/packages/angular/standalone/src/index.ts @@ -4,7 +4,7 @@ export { IonPopover } from './overlays/popover'; export { IonRouterOutlet } from './navigation/router-outlet'; export { IonRouterLink, IonRouterLinkWithHref } from './navigation/router-link-delegate'; export { IonNav } from './navigation/nav-delegate'; - +export { provideIonicAngular } from './providers/ionic-angular'; export { ActionSheetController, AlertController, diff --git a/packages/angular/standalone/src/navigation/nav-delegate.ts b/packages/angular/standalone/src/navigation/nav-delegate.ts index 62ad35144a..410f45e635 100644 --- a/packages/angular/standalone/src/navigation/nav-delegate.ts +++ b/packages/angular/standalone/src/navigation/nav-delegate.ts @@ -15,7 +15,6 @@ export class IonNav extends NavDelegateBase { ref: ElementRef, environmentInjector: EnvironmentInjector, injector: Injector, - // TODO FW-4766: Remove AngularDelegate angularDelegate: AngularDelegate, z: NgZone ) { diff --git a/packages/angular/standalone/src/providers/ionic-angular.ts b/packages/angular/standalone/src/providers/ionic-angular.ts new file mode 100644 index 0000000000..8dd0053cc0 --- /dev/null +++ b/packages/angular/standalone/src/providers/ionic-angular.ts @@ -0,0 +1,51 @@ +import { DOCUMENT } from '@angular/common'; +import { APP_INITIALIZER } from '@angular/core'; +import type { Provider } from '@angular/core'; +import { + AngularDelegate, + ConfigToken, + ModalController, + PopoverController, + provideComponentInputBinding, +} from '@ionic/angular/common'; +import { initialize } from '@ionic/core/components'; +import type { IonicConfig } from '@ionic/core/components'; + +export const provideIonicAngular = (config?: IonicConfig): Provider[] => { + /** + * TODO FW-4967 + * Use makeEnvironmentProviders once Angular 14 support is dropped. + * This prevents provideIonicAngular from being accidentally referenced in an @Component. + */ + return [ + { + provide: ConfigToken, + useValue: config, + }, + { + provide: APP_INITIALIZER, + useFactory: initializeIonicAngular, + multi: true, + deps: [ConfigToken, DOCUMENT], + }, + provideComponentInputBinding(), + AngularDelegate, + ModalController, + PopoverController, + ]; +}; + +const initializeIonicAngular = (config: IonicConfig, doc: Document) => { + return () => { + /** + * By default Ionic Framework hides elements that + * are not hydrated, but in the CE build there is no + * hydration. + * TODO FW-2797: Remove when all integrations have been + * migrated to CE build. + */ + doc.documentElement.classList.add('ion-ce'); + + initialize(config); + }; +}; diff --git a/packages/angular/test/apps/ng14/src/main-standalone.ts b/packages/angular/test/apps/ng14/src/main-standalone.ts new file mode 100644 index 0000000000..18e84fb933 --- /dev/null +++ b/packages/angular/test/apps/ng14/src/main-standalone.ts @@ -0,0 +1,23 @@ +import { importProvidersFrom } from '@angular/core'; +import { bootstrapApplication } from '@angular/platform-browser'; +import { RouteReuseStrategy } from '@angular/router'; +import { provideIonicAngular, IonicRouteStrategy } from '@ionic/angular/standalone'; + +import { AppComponentStandalone } from './app/app-standalone.component'; +import { AppRoutingModule } from './app/app-routing.module'; + +import { routes } from './app/app.routes'; + +export const bootstrapStandalone = () => { + bootstrapApplication(AppComponentStandalone, { + providers: [ + { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, + /** + * provideRouter is not available in Angular 14, so + * we fallback to using AppRoutingModule + */ + importProvidersFrom(AppRoutingModule), + provideIonicAngular({ keyboardHeight: 12345 }) + ], + }); +} diff --git a/packages/angular/test/base/e2e/src/standalone/overlay-controllers.spec.ts b/packages/angular/test/base/e2e/src/standalone/overlay-controllers.spec.ts new file mode 100644 index 0000000000..e48982c7fe --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/overlay-controllers.spec.ts @@ -0,0 +1,17 @@ +describe('Overlay Controllers', () => { + beforeEach(() => { + cy.visit('/standalone/overlay-controllers'); + }) + + it('should present a modal', () => { + cy.get('button#open-modal').click(); + + cy.get('ion-modal app-dialog-content').should('be.visible'); + }); + + it('should present a popover', () => { + cy.get('button#open-popover').click(); + + cy.get('ion-popover app-dialog-content').should('be.visible'); + }); +}) diff --git a/packages/angular/test/base/e2e/src/standalone/providers.spec.ts b/packages/angular/test/base/e2e/src/standalone/providers.spec.ts new file mode 100644 index 0000000000..71aca1c635 --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/providers.spec.ts @@ -0,0 +1,11 @@ +describe('Providers', () => { + beforeEach(() => { + cy.visit('/standalone/providers'); + }) + + it('provideIonicAngular should initialize Ionic and set config correctly', () => { + cy.ionPageVisible('app-providers'); + + cy.get('#keyboard-height').should('have.text', '12345'); + }); +}) diff --git a/packages/angular/test/base/src/app/app-standalone.component.ts b/packages/angular/test/base/src/app/app-standalone.component.ts new file mode 100644 index 0000000000..e39f6d3661 --- /dev/null +++ b/packages/angular/test/base/src/app/app-standalone.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + standalone: true, + imports: [RouterModule] +}) +export class AppComponentStandalone { +} diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts index 280de0d8c7..e105848648 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.component.ts @@ -1,15 +1,6 @@ import { Component } from '@angular/core'; import { RouterModule } from '@angular/router'; import { IonRouterOutlet } from '@ionic/angular/standalone'; -/** - * This temporary code initialized Ionic and ensures components are visible. - * TODO FW-4766 Can be removed when ticket is implemented - */ -import { initialize } from '@ionic/core/components'; -initialize(); - -document.querySelector('html')!.classList.add('ion-ce') - @Component({ selector: 'app-root-standalone', diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index c43c86dd0f..fb027e34f9 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -12,6 +12,8 @@ export const routes: Routes = [ { path: 'back-button', loadComponent: () => import('../back-button/back-button.component').then(m => m.BackButtonComponent) }, { path: 'router-link', loadComponent: () => import('../router-link/router-link.component').then(m => m.RouterLinkComponent) }, { path: 'nav', loadComponent: () => import('../nav/nav.component').then(m => m.NavComponent) }, + { path: 'providers', loadComponent: () => import('../providers/providers.component').then(m => m.ProvidersComponent) }, + { path: 'overlay-controllers', loadComponent: () => import('../overlay-controllers/overlay-controllers.component').then(m => m.OverlayControllersComponent) }, ] }, ]; diff --git a/packages/angular/test/base/src/app/standalone/nav/nav.component.ts b/packages/angular/test/base/src/app/standalone/nav/nav.component.ts index e056feca74..b969f7683c 100644 --- a/packages/angular/test/base/src/app/standalone/nav/nav.component.ts +++ b/packages/angular/test/base/src/app/standalone/nav/nav.component.ts @@ -8,9 +8,7 @@ import { PageOneComponent } from './page-one.component'; selector: 'app-nav', templateUrl: './nav.component.html', standalone: true, - imports: [IonNav], - // TODO FW-4766: Remove AngularDelegate from providers - providers: [AngularDelegate] + imports: [IonNav] }) export class NavComponent { component = PageOneComponent; diff --git a/packages/angular/test/base/src/app/standalone/overlay-controllers/overlay-controllers.component.html b/packages/angular/test/base/src/app/standalone/overlay-controllers/overlay-controllers.component.html new file mode 100644 index 0000000000..b05f9b2530 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/overlay-controllers/overlay-controllers.component.html @@ -0,0 +1,4 @@ +
+ + +
diff --git a/packages/angular/test/base/src/app/standalone/overlay-controllers/overlay-controllers.component.ts b/packages/angular/test/base/src/app/standalone/overlay-controllers/overlay-controllers.component.ts new file mode 100644 index 0000000000..5c20a02bd9 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/overlay-controllers/overlay-controllers.component.ts @@ -0,0 +1,36 @@ +import { Component } from '@angular/core'; +import { ModalController, PopoverController } from '@ionic/angular/standalone'; + +@Component({ + selector: 'app-overlay-controllers', + templateUrl: './overlay-controllers.component.html', + standalone: true, +}) +export class OverlayControllersComponent { + constructor(private modalCtrl: ModalController, private popoverCtrl: PopoverController) {} + + async openModal() { + const modal = await this.modalCtrl.create({ + component: DialogComponent + }); + + await modal.present(); + } + + async openPopover(ev: MouseEvent) { + const popover = await this.popoverCtrl.create({ + component: DialogComponent, + event: ev + }); + + await popover.present(); + } +} + +@Component({ + selector: 'app-dialog-content', + template: '
Dialog Content
', + standalone: true, +}) +class DialogComponent { +} diff --git a/packages/angular/test/base/src/app/standalone/providers/providers.component.html b/packages/angular/test/base/src/app/standalone/providers/providers.component.html new file mode 100644 index 0000000000..c0bb11bca7 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/providers/providers.component.html @@ -0,0 +1,4 @@ + + diff --git a/packages/angular/test/base/src/app/standalone/providers/providers.component.ts b/packages/angular/test/base/src/app/standalone/providers/providers.component.ts new file mode 100644 index 0000000000..8dcb9017c1 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/providers/providers.component.ts @@ -0,0 +1,15 @@ +import { Component } from '@angular/core'; +import { Config } from '@ionic/angular/standalone'; + +@Component({ + selector: 'app-providers', + templateUrl: './providers.component.html', + standalone: true, +}) +export class ProvidersComponent { + keyboardHeight?: number; + + constructor(private config: Config) { + this.keyboardHeight = config.get('keyboardHeight'); + } +} diff --git a/packages/angular/test/base/src/main-standalone.ts b/packages/angular/test/base/src/main-standalone.ts new file mode 100644 index 0000000000..d2dfd98d8f --- /dev/null +++ b/packages/angular/test/base/src/main-standalone.ts @@ -0,0 +1,17 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { RouteReuseStrategy, provideRouter } from '@angular/router'; +import { provideIonicAngular, IonicRouteStrategy } from '@ionic/angular/standalone'; + +import { AppComponentStandalone } from './app/app-standalone.component'; + +import { routes } from './app/app.routes'; + +export const bootstrapStandalone = () => { + bootstrapApplication(AppComponentStandalone, { + providers: [ + { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, + provideRouter(routes), + provideIonicAngular({ keyboardHeight: 12345 }) + ], + }); +} diff --git a/packages/angular/test/base/src/main.ts b/packages/angular/test/base/src/main.ts index e6659e9a13..5c0debfa14 100644 --- a/packages/angular/test/base/src/main.ts +++ b/packages/angular/test/base/src/main.ts @@ -8,8 +8,20 @@ if (environment.production) { enableProdMode(); } -document.addEventListener('DOMContentLoaded', () => { - platformBrowserDynamic() - .bootstrapModule(AppModule) - .catch(err => console.error(err)); -}); +const isLazy = window.location.href.includes('lazy'); + +if (isLazy) { + document.addEventListener('DOMContentLoaded', () => { + platformBrowserDynamic() + .bootstrapModule(AppModule) + .catch(err => console.error(err)); + }); +} else { + /** + * Importing standalone and lazy modules in the same + * file creates side effects where manually generated components + * such as ion-modal do not get bootstrapped correctly. Using + * a dynamic import avoids this. + */ + import('./main-standalone').then((module) => { module.bootstrapStandalone() }); +}