feat(angular): add standalone provideIonicAngular (#27996)

This commit is contained in:
Liam DeBeasi
2023-08-15 14:32:27 -05:00
committed by GitHub
parent c65e08dcd1
commit ca53682684
18 changed files with 210 additions and 21 deletions

View File

@@ -37,7 +37,6 @@ export class NavDelegate {
ref: ElementRef,
environmentInjector: EnvironmentInjector,
injector: Injector,
// TODO FW-4766: Remove AngularDelegate
angularDelegate: AngularDelegate,
protected z: NgZone
) {

View File

@@ -10,7 +10,6 @@ export class NavDelegate extends NavDelegateBase {
ref: ElementRef,
environmentInjector: EnvironmentInjector,
injector: Injector,
// TODO FW-4766: Remove AngularDelegate
angularDelegate: AngularDelegate,
z: NgZone
) {

View File

@@ -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,

View File

@@ -15,7 +15,6 @@ export class IonNav extends NavDelegateBase {
ref: ElementRef,
environmentInjector: EnvironmentInjector,
injector: Injector,
// TODO FW-4766: Remove AngularDelegate
angularDelegate: AngularDelegate,
z: NgZone
) {

View File

@@ -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);
};
};

View File

@@ -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 })
],
});
}

View File

@@ -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');
});
})

View File

@@ -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');
});
})

View File

@@ -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 {
}

View File

@@ -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',

View File

@@ -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) },
]
},
];

View File

@@ -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;

View File

@@ -0,0 +1,4 @@
<div>
<button id="open-modal" (click)="openModal()">Open Modal</button>
<button id="open-popover" (click)="openPopover($event)">Open Popover</button>
</div>

View File

@@ -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: '<div class="ion-padding">Dialog Content</div>',
standalone: true,
})
class DialogComponent {
}

View File

@@ -0,0 +1,4 @@
<ul>
<li>Keyboard Height: <span id="keyboard-height">{{ keyboardHeight }}</span></li>
</ul>

View File

@@ -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');
}
}

View File

@@ -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 })
],
});
}

View File

@@ -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() });
}