mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
BREAKING CHANGE: Angular v14 is now required to use `@ionic/angular` and `@ionic/angular-server`. Upgrade your project to Angular v14 by following the [Angular v14 update guide](https://update.angular.io/?l=3&v=13.0-14.0). The dev-preview `environmentInjector` property has been removed from `ion-tabs` and `ion-router-outlet`. Standalone component routing is now available without additional custom configuration. Remove the `environmentInjector` property from your `ion-tabs` and `ion-router-outlet` components.
204 lines
5.4 KiB
TypeScript
204 lines
5.4 KiB
TypeScript
import {
|
|
ApplicationRef,
|
|
NgZone,
|
|
Injectable,
|
|
Injector,
|
|
EnvironmentInjector,
|
|
inject,
|
|
createComponent,
|
|
InjectionToken,
|
|
ComponentRef,
|
|
} from '@angular/core';
|
|
import {
|
|
FrameworkDelegate,
|
|
LIFECYCLE_DID_ENTER,
|
|
LIFECYCLE_DID_LEAVE,
|
|
LIFECYCLE_WILL_ENTER,
|
|
LIFECYCLE_WILL_LEAVE,
|
|
LIFECYCLE_WILL_UNLOAD,
|
|
} from '@ionic/core';
|
|
|
|
import { NavParams } from '../directives/navigation/nav-params';
|
|
|
|
// TODO(FW-2827): types
|
|
|
|
@Injectable()
|
|
export class AngularDelegate {
|
|
private zone = inject(NgZone);
|
|
private applicationRef = inject(ApplicationRef);
|
|
|
|
create(
|
|
environmentInjector: EnvironmentInjector,
|
|
injector: Injector,
|
|
elementReferenceKey?: string
|
|
): AngularFrameworkDelegate {
|
|
return new AngularFrameworkDelegate(
|
|
environmentInjector,
|
|
injector,
|
|
this.applicationRef,
|
|
this.zone,
|
|
elementReferenceKey
|
|
);
|
|
}
|
|
}
|
|
|
|
export class AngularFrameworkDelegate implements FrameworkDelegate {
|
|
private elRefMap = new WeakMap<HTMLElement, ComponentRef<any>>();
|
|
private elEventsMap = new WeakMap<HTMLElement, () => void>();
|
|
|
|
constructor(
|
|
private environmentInjector: EnvironmentInjector,
|
|
private injector: Injector,
|
|
private applicationRef: ApplicationRef,
|
|
private zone: NgZone,
|
|
private elementReferenceKey?: string
|
|
) {}
|
|
|
|
attachViewToDom(container: any, component: any, params?: any, cssClasses?: string[]): Promise<any> {
|
|
return this.zone.run(() => {
|
|
return new Promise((resolve) => {
|
|
const componentProps = {
|
|
...params,
|
|
};
|
|
|
|
/**
|
|
* Ionic Angular passes a reference to a modal
|
|
* or popover that can be accessed using a
|
|
* variable in the overlay component. If
|
|
* elementReferenceKey is defined, then we should
|
|
* pass a reference to the component using
|
|
* elementReferenceKey as the key.
|
|
*/
|
|
if (this.elementReferenceKey !== undefined) {
|
|
componentProps[this.elementReferenceKey] = container;
|
|
}
|
|
|
|
const el = attachView(
|
|
this.zone,
|
|
this.environmentInjector,
|
|
this.injector,
|
|
this.applicationRef,
|
|
this.elRefMap,
|
|
this.elEventsMap,
|
|
container,
|
|
component,
|
|
componentProps,
|
|
cssClasses
|
|
);
|
|
resolve(el);
|
|
});
|
|
});
|
|
}
|
|
|
|
removeViewFromDom(_container: any, component: any): Promise<void> {
|
|
return this.zone.run(() => {
|
|
return new Promise((resolve) => {
|
|
const componentRef = this.elRefMap.get(component);
|
|
if (componentRef) {
|
|
componentRef.destroy();
|
|
this.elRefMap.delete(component);
|
|
const unbindEvents = this.elEventsMap.get(component);
|
|
if (unbindEvents) {
|
|
unbindEvents();
|
|
this.elEventsMap.delete(component);
|
|
}
|
|
}
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
export const attachView = (
|
|
zone: NgZone,
|
|
environmentInjector: EnvironmentInjector,
|
|
injector: Injector,
|
|
applicationRef: ApplicationRef,
|
|
elRefMap: WeakMap<HTMLElement, ComponentRef<any>>,
|
|
elEventsMap: WeakMap<HTMLElement, () => void>,
|
|
container: any,
|
|
component: any,
|
|
params: any,
|
|
cssClasses: string[] | undefined
|
|
): any => {
|
|
/**
|
|
* Wraps the injector with a custom injector that
|
|
* provides NavParams to the component.
|
|
*
|
|
* NavParams is a legacy feature from Ionic v3 that allows
|
|
* Angular developers to provide data to a component
|
|
* and access it by providing NavParams as a dependency
|
|
* in the constructor.
|
|
*
|
|
* The modern approach is to access the data directly
|
|
* from the component's class instance.
|
|
*/
|
|
const childInjector = Injector.create({
|
|
providers: getProviders(params),
|
|
parent: injector,
|
|
});
|
|
|
|
const componentRef = createComponent<any>(component, {
|
|
environmentInjector,
|
|
elementInjector: childInjector,
|
|
});
|
|
|
|
const instance = componentRef.instance;
|
|
const hostElement = componentRef.location.nativeElement;
|
|
if (params) {
|
|
Object.assign(instance, params);
|
|
}
|
|
if (cssClasses) {
|
|
for (const cssClass of cssClasses) {
|
|
hostElement.classList.add(cssClass);
|
|
}
|
|
}
|
|
const unbindEvents = bindLifecycleEvents(zone, instance, hostElement);
|
|
container.appendChild(hostElement);
|
|
|
|
applicationRef.attachView(componentRef.hostView);
|
|
|
|
elRefMap.set(hostElement, componentRef);
|
|
elEventsMap.set(hostElement, unbindEvents);
|
|
return hostElement;
|
|
};
|
|
|
|
const LIFECYCLES = [
|
|
LIFECYCLE_WILL_ENTER,
|
|
LIFECYCLE_DID_ENTER,
|
|
LIFECYCLE_WILL_LEAVE,
|
|
LIFECYCLE_DID_LEAVE,
|
|
LIFECYCLE_WILL_UNLOAD,
|
|
];
|
|
|
|
export const bindLifecycleEvents = (zone: NgZone, instance: any, element: HTMLElement): (() => void) => {
|
|
return zone.run(() => {
|
|
const unregisters = LIFECYCLES.filter((eventName) => typeof instance[eventName] === 'function').map((eventName) => {
|
|
const handler = (ev: any) => instance[eventName](ev.detail);
|
|
element.addEventListener(eventName, handler);
|
|
return () => element.removeEventListener(eventName, handler);
|
|
});
|
|
return () => unregisters.forEach((fn) => fn());
|
|
});
|
|
};
|
|
|
|
const NavParamsToken = new InjectionToken<any>('NavParamsToken');
|
|
|
|
const getProviders = (params: { [key: string]: any }) => {
|
|
return [
|
|
{
|
|
provide: NavParamsToken,
|
|
useValue: params,
|
|
},
|
|
{
|
|
provide: NavParams,
|
|
useFactory: provideNavParamsInjectable,
|
|
deps: [NavParamsToken],
|
|
},
|
|
];
|
|
};
|
|
|
|
const provideNavParamsInjectable = (params: { [key: string]: any }) => {
|
|
return new NavParams(params);
|
|
};
|