mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
feat(angular): add standalone provideIonicAngular (#27996)
This commit is contained in:
@@ -37,7 +37,6 @@ export class NavDelegate {
|
||||
ref: ElementRef,
|
||||
environmentInjector: EnvironmentInjector,
|
||||
injector: Injector,
|
||||
// TODO FW-4766: Remove AngularDelegate
|
||||
angularDelegate: AngularDelegate,
|
||||
protected z: NgZone
|
||||
) {
|
||||
|
||||
@@ -10,7 +10,6 @@ export class NavDelegate extends NavDelegateBase {
|
||||
ref: ElementRef,
|
||||
environmentInjector: EnvironmentInjector,
|
||||
injector: Injector,
|
||||
// TODO FW-4766: Remove AngularDelegate
|
||||
angularDelegate: AngularDelegate,
|
||||
z: NgZone
|
||||
) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -15,7 +15,6 @@ export class IonNav extends NavDelegateBase {
|
||||
ref: ElementRef,
|
||||
environmentInjector: EnvironmentInjector,
|
||||
injector: Injector,
|
||||
// TODO FW-4766: Remove AngularDelegate
|
||||
angularDelegate: AngularDelegate,
|
||||
z: NgZone
|
||||
) {
|
||||
|
||||
51
packages/angular/standalone/src/providers/ionic-angular.ts
Normal file
51
packages/angular/standalone/src/providers/ionic-angular.ts
Normal 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);
|
||||
};
|
||||
};
|
||||
23
packages/angular/test/apps/ng14/src/main-standalone.ts
Normal file
23
packages/angular/test/apps/ng14/src/main-standalone.ts
Normal 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 })
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -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');
|
||||
});
|
||||
})
|
||||
@@ -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');
|
||||
});
|
||||
})
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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) },
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<ul>
|
||||
<li>Keyboard Height: <span id="keyboard-height">{{ keyboardHeight }}</span></li>
|
||||
</ul>
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
17
packages/angular/test/base/src/main-standalone.ts
Normal file
17
packages/angular/test/base/src/main-standalone.ts
Normal 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 })
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -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() });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user