diff --git a/angular/src/index.ts b/angular/src/index.ts index 7b790c5d68..4ba2a2ce35 100644 --- a/angular/src/index.ts +++ b/angular/src/index.ts @@ -13,6 +13,7 @@ export { VirtualHeader } from './directives/virtual-header'; export { VirtualFooter } from './directives/virtual-footer'; /* Providers */ +export { AngularDelegate } from './providers/angular-delegate'; export { ActionSheetController } from './providers/action-sheet-controller'; export { AlertController } from './providers/alert-controller'; export { Events } from './providers/events'; diff --git a/angular/src/module.ts b/angular/src/module.ts index bb5d862465..75cf6660bf 100644 --- a/angular/src/module.ts +++ b/angular/src/module.ts @@ -25,6 +25,7 @@ import { VirtualHeader } from './directives/virtual-header'; import { VirtualFooter } from './directives/virtual-footer'; /* Providers */ +import { AngularDelegate } from './providers/angular-delegate'; import { ActionSheetController } from './providers/action-sheet-controller'; import { AlertController } from './providers/alert-controller'; import { Events, setupProvideEvents } from './providers/events'; @@ -71,10 +72,6 @@ import { ToastController } from './providers/toast-controller'; imports: [ CommonModule, ], - providers: [ - ModalController, - PopoverController, - ], schemas: [ CUSTOM_ELEMENTS_SCHEMA ] @@ -84,6 +81,9 @@ export class IonicAngularModule { return { ngModule: IonicAngularModule, providers: [ + ModalController, + PopoverController, + AngularDelegate, AlertController, ActionSheetController, Events, diff --git a/angular/src/providers/angular-delegate.ts b/angular/src/providers/angular-delegate.ts new file mode 100644 index 0000000000..a3779b35ec --- /dev/null +++ b/angular/src/providers/angular-delegate.ts @@ -0,0 +1,52 @@ +import { + ApplicationRef, + ComponentFactoryResolver, + Injectable, + Injector, +} from '@angular/core'; + +import { FrameworkDelegate } from '@ionic/core'; + + +@Injectable() +export class AngularDelegate implements FrameworkDelegate { + + private elRefMap = new WeakMap(); + + constructor( + private cfr: ComponentFactoryResolver, + private injector: Injector, + private appRef: ApplicationRef + ) {} + + attachViewToDom(container: any, component: any, data?: any, cssClasses?: string[]): Promise { + + const componentFactory = this.cfr.resolveComponentFactory(component); + const hostElement = document.createElement(componentFactory.selector); + if (data) { + Object.assign(hostElement, data); + } + + const childInjector = Injector.create([], this.injector); + const componentRef = componentFactory.create(childInjector, [], hostElement); + for (const clazz of cssClasses) { + hostElement.classList.add(clazz); + } + + container.appendChild(hostElement); + + this.appRef.attachView(componentRef.hostView); + this.elRefMap.set(hostElement, componentRef); + return Promise.resolve(hostElement); + } + + removeViewFromDom(_container: any, component: any): Promise { + const mountingData = this.elRefMap.get(component); + if (mountingData) { + mountingData.componentRef.destroy(); + this.elRefMap.delete(component); + } + return Promise.resolve(); + } +} + diff --git a/angular/src/providers/modal-controller.ts b/angular/src/providers/modal-controller.ts index ba656e31af..2d569d532e 100644 --- a/angular/src/providers/modal-controller.ts +++ b/angular/src/providers/modal-controller.ts @@ -1,10 +1,20 @@ import { Injectable } from '@angular/core'; import { ModalOptions } from '@ionic/core'; import { OverlayBaseController } from '../util/overlay'; +import { AngularDelegate } from '..'; @Injectable() export class ModalController extends OverlayBaseController { - constructor() { + constructor( + private angularDelegate: AngularDelegate, + ) { super('ion-modal-controller'); } + + create(opts?: ModalOptions): Promise { + return super.create({ + ...opts, + delegate: this.angularDelegate + }); + } } diff --git a/core/src/components/modal/modal.tsx b/core/src/components/modal/modal.tsx index f54f823520..c97dbe574b 100644 --- a/core/src/components/modal/modal.tsx +++ b/core/src/components/modal/modal.tsx @@ -33,7 +33,7 @@ export class Modal implements OverlayInterface { @Prop({ context: 'config' }) config: Config; @Prop() overlayId: number; - @Prop({ mutable: true }) delegate: FrameworkDelegate; + @Prop() delegate: FrameworkDelegate; /** * The color to use from your Sass `$colors` map. @@ -175,7 +175,7 @@ export class Modal implements OverlayInterface { ...getClassMap(this.cssClass), 'ion-page': true }; - return attachComponent(container, this.component, classes, data) + return attachComponent(this.delegate, container, this.component, classes, data) .then(el => this.usersElement = el) .then(() => present(this, 'modalEnter', iosEnterAnimation, mdEnterAnimation, undefined)); } diff --git a/core/src/components/popover/popover.tsx b/core/src/components/popover/popover.tsx index 8d12053366..a51b1603ed 100644 --- a/core/src/components/popover/popover.tsx +++ b/core/src/components/popover/popover.tsx @@ -31,7 +31,7 @@ export class Popover implements OverlayInterface { @Prop({ connect: 'ion-animation-controller' }) animationCtrl: HTMLIonAnimationControllerElement; @Prop({ context: 'config' }) config: Config; - @Prop({ mutable: true }) delegate: FrameworkDelegate; + @Prop() delegate: FrameworkDelegate; @Prop() overlayId: number; /** @@ -185,7 +185,7 @@ export class Popover implements OverlayInterface { ...getClassMap(this.cssClass), 'popover-viewport': true }; - return attachComponent(container, this.component, classes, data) + return attachComponent(this.delegate, container, this.component, classes, data) .then(el => this.usersElement = el) .then(() => present(this, 'popoverEnter', iosEnterAnimation, mdEnterAnimation, this.ev)); } diff --git a/core/src/index.d.ts b/core/src/index.d.ts index 45240e0470..3a2b5fa096 100644 --- a/core/src/index.d.ts +++ b/core/src/index.d.ts @@ -109,7 +109,7 @@ export { PlatformConfig } from './global/platform-configs'; export * from './components'; export { DomController, RafCallback } from './global/dom-controller'; -export { FrameworkDelegate, FrameworkMountingData } from './utils/dom-framework-delegate'; +export { FrameworkDelegate } from './utils/dom-framework-delegate'; export { OverlayEventDetail } from './utils/overlays'; export interface Config { diff --git a/core/src/utils/dom-framework-delegate.ts b/core/src/utils/dom-framework-delegate.ts index c1a454f683..71274f37cf 100644 --- a/core/src/utils/dom-framework-delegate.ts +++ b/core/src/utils/dom-framework-delegate.ts @@ -1,41 +1,5 @@ export interface FrameworkDelegate { - attachViewToDom(elementOrContainerToMountTo: any, elementOrComponentToMount: any, propsOrDataObj?: any, classesToAdd?: string[], escapeHatch?: any): Promise; - removeViewFromDom(elementOrContainerToUnmountFrom: any, elementOrComponentToUnmount: any, escapeHatch?: any): Promise; -} - - -export interface FrameworkMountingData { - element: HTMLElement; - component: any; - data: any; -} - -export class DomFrameworkDelegate implements FrameworkDelegate { - - attachViewToDom(parentElement: HTMLElement, tagOrElement: string | HTMLElement, data: any = {}, classesToAdd: string[] = []): Promise { - return new Promise((resolve) => { - const usersElement = (typeof tagOrElement === 'string' ? document.createElement(tagOrElement) : tagOrElement); - Object.assign(usersElement, data); - - if (classesToAdd.length) { - for (const clazz of classesToAdd) { - usersElement.classList.add(clazz); - } - } - - parentElement.appendChild(usersElement); - - resolve({ - element: usersElement, - data: data, - component: tagOrElement - }); - }); - } - - removeViewFromDom(parentElement: HTMLElement, childElement: HTMLElement): Promise { - parentElement.removeChild(childElement); - return Promise.resolve(); - } + attachViewToDom(container: any, component: any, propsOrDataObj?: any, cssClasses?: string[]): Promise; + removeViewFromDom(container: any, component: any): Promise; } diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts index 7486fd4a0f..7177ed5c03 100644 --- a/core/src/utils/overlays.ts +++ b/core/src/utils/overlays.ts @@ -1,5 +1,5 @@ import { EventEmitter } from '@stencil/core'; -import { Animation, AnimationBuilder, Config, CssClassMap } from '..'; +import { Animation, AnimationBuilder, Config, CssClassMap, FrameworkDelegate } from '..'; let lastId = 1; @@ -137,7 +137,10 @@ export function autoFocus(containerEl: HTMLElement): HTMLElement|null { return null; } -export function attachComponent(container: Element, component: string|HTMLElement, cssClasses: CssClassMap, data: any): Promise { +export function attachComponent(delegate: FrameworkDelegate, container: Element, component: string|HTMLElement, cssClasses: CssClassMap, data: any): Promise { + if (delegate) { + return delegate.attachViewToDom(container, component, data, Object.keys(cssClasses)); + } const el = (typeof component === 'string') ? document.createElement(component) : component; Object.assign(el, data); Object.keys(cssClasses).forEach(c => el.classList.add(c));