From f44c17e03bfcd9f6f9375a19a8d06e9393124ac9 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Mon, 25 Feb 2019 16:43:41 -0600 Subject: [PATCH] fix(ssr): fix angular global window and document references --- angular/src/app-initialize.ts | 6 +- .../navigation/ion-router-outlet.ts | 12 +- angular/src/ionic-module.ts | 5 +- .../src/providers/action-sheet-controller.ts | 7 +- angular/src/providers/alert-controller.ts | 7 +- angular/src/providers/config.ts | 5 +- angular/src/providers/dom-controller.ts | 19 +- angular/src/providers/loading-controller.ts | 7 +- angular/src/providers/menu-controller.ts | 26 +-- angular/src/providers/modal-controller.ts | 6 +- angular/src/providers/picker-controller.ts | 7 +- angular/src/providers/platform.ts | 57 +++--- angular/src/providers/popover-controller.ts | 6 +- angular/src/providers/toast-controller.ts | 7 +- angular/src/util/overlay.ts | 8 +- angular/src/util/util.ts | 12 +- .../components/segment/test/custom/index.html | 28 --- core/src/global/ionic-global.ts | 4 +- core/src/utils/platform.ts | 185 ++++++++---------- 19 files changed, 200 insertions(+), 214 deletions(-) diff --git a/angular/src/app-initialize.ts b/angular/src/app-initialize.ts index 234c61c8b6..c580b294a4 100644 --- a/angular/src/app-initialize.ts +++ b/angular/src/app-initialize.ts @@ -3,10 +3,10 @@ import { defineCustomElements } from '@ionic/core/loader'; import { Config } from './providers/config'; import { IonicWindow } from './types/interfaces'; -export function appInitialize(config: Config) { +export function appInitialize(config: Config, doc: Document) { return (): any => { - const win: IonicWindow | undefined = window as any; - if (typeof win !== 'undefined') { + const win: IonicWindow | undefined = doc.defaultView as any; + if (win) { const Ionic = win.Ionic = win.Ionic || {}; Ionic.config = config; diff --git a/angular/src/directives/navigation/ion-router-outlet.ts b/angular/src/directives/navigation/ion-router-outlet.ts index 491d240a06..e9f9a18f09 100644 --- a/angular/src/directives/navigation/ion-router-outlet.ts +++ b/angular/src/directives/navigation/ion-router-outlet.ts @@ -82,11 +82,13 @@ export class IonRouterOutlet implements OnDestroy, OnInit { this.activateWith(context.route, context.resolver || null); } } - this.nativeEl.componentOnReady().then(() => { - if (this._swipeGesture === undefined) { - this.swipeGesture = this.config.getBoolean('swipeBackEnabled', this.nativeEl.mode === 'ios'); - } - }); + if ((this.nativeEl as any).componentOnReady) { + this.nativeEl.componentOnReady().then(() => { + if (this._swipeGesture === undefined) { + this.swipeGesture = this.config.getBoolean('swipeBackEnabled', this.nativeEl.mode === 'ios'); + } + }); + } } get isActivated(): boolean { diff --git a/angular/src/ionic-module.ts b/angular/src/ionic-module.ts index 73a7782579..6afe231c9c 100644 --- a/angular/src/ionic-module.ts +++ b/angular/src/ionic-module.ts @@ -1,4 +1,4 @@ -import { CommonModule } from '@angular/common'; +import { CommonModule, DOCUMENT } from '@angular/common'; import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core'; import { IonicConfig } from '@ionic/core'; @@ -141,7 +141,8 @@ export class IonicModule { useFactory: appInitialize, multi: true, deps: [ - ConfigToken + ConfigToken, + DOCUMENT ] } ] diff --git a/angular/src/providers/action-sheet-controller.ts b/angular/src/providers/action-sheet-controller.ts index 49226c725e..441b75a77b 100644 --- a/angular/src/providers/action-sheet-controller.ts +++ b/angular/src/providers/action-sheet-controller.ts @@ -1,4 +1,5 @@ -import { Injectable } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { Inject, Injectable } from '@angular/core'; import { ActionSheetOptions } from '@ionic/core'; import { OverlayBaseController } from '../util/overlay'; @@ -7,7 +8,7 @@ import { OverlayBaseController } from '../util/overlay'; providedIn: 'root', }) export class ActionSheetController extends OverlayBaseController { - constructor() { - super('ion-action-sheet-controller'); + constructor(@Inject(DOCUMENT) doc: any) { + super('ion-action-sheet-controller', doc); } } diff --git a/angular/src/providers/alert-controller.ts b/angular/src/providers/alert-controller.ts index b96d732de6..c69c82736f 100644 --- a/angular/src/providers/alert-controller.ts +++ b/angular/src/providers/alert-controller.ts @@ -1,4 +1,5 @@ -import { Injectable } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { Inject, Injectable } from '@angular/core'; import { AlertOptions } from '@ionic/core'; import { OverlayBaseController } from '../util/overlay'; @@ -7,7 +8,7 @@ import { OverlayBaseController } from '../util/overlay'; providedIn: 'root', }) export class AlertController extends OverlayBaseController { - constructor() { - super('ion-alert-controller'); + constructor(@Inject(DOCUMENT) doc: any) { + super('ion-alert-controller', doc); } } diff --git a/angular/src/providers/config.ts b/angular/src/providers/config.ts index 983ed8d6ea..48e9b95c3d 100644 --- a/angular/src/providers/config.ts +++ b/angular/src/providers/config.ts @@ -43,9 +43,8 @@ export class Config { export const ConfigToken = new InjectionToken('USERCONFIG'); function getConfig(): CoreConfig | null { - const win: IonicWindow | undefined = window as any; - if (typeof win !== 'undefined') { - const Ionic = win.Ionic; + if (typeof (window as any) !== 'undefined') { + const Ionic = (window as IonicWindow).Ionic; if (Ionic && Ionic.config) { return Ionic.config; } diff --git a/angular/src/providers/dom-controller.ts b/angular/src/providers/dom-controller.ts index a10680e9f0..09b00b7bb7 100644 --- a/angular/src/providers/dom-controller.ts +++ b/angular/src/providers/dom-controller.ts @@ -23,14 +23,23 @@ export class DomController { } function getQueue() { - const Ionic = (window as any).Ionic; - if (Ionic && Ionic.queue) { - return Ionic.queue; + const win = typeof (window as any) !== 'undefined' ? window : null as any; + + if (win != null) { + const Ionic = win.Ionic; + if (Ionic && Ionic.queue) { + return Ionic.queue; + } + + return { + read: (cb: any) => win.requestAnimationFrame(cb), + write: (cb: any) => win.requestAnimationFrame(cb) + }; } return { - read: (cb: any) => window.requestAnimationFrame(cb), - write: (cb: any) => window.requestAnimationFrame(cb) + read: (cb: any) => cb(), + write: (cb: any) => cb() }; } diff --git a/angular/src/providers/loading-controller.ts b/angular/src/providers/loading-controller.ts index 18bab8f83e..14c5302f06 100644 --- a/angular/src/providers/loading-controller.ts +++ b/angular/src/providers/loading-controller.ts @@ -1,4 +1,5 @@ -import { Injectable } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { Inject, Injectable } from '@angular/core'; import { LoadingOptions } from '@ionic/core'; import { OverlayBaseController } from '../util/overlay'; @@ -7,7 +8,7 @@ import { OverlayBaseController } from '../util/overlay'; providedIn: 'root', }) export class LoadingController extends OverlayBaseController { - constructor() { - super('ion-loading-controller'); + constructor(@Inject(DOCUMENT) doc: any) { + super('ion-loading-controller', doc); } } diff --git a/angular/src/providers/menu-controller.ts b/angular/src/providers/menu-controller.ts index 447f2abdd6..ffc47cb9a5 100644 --- a/angular/src/providers/menu-controller.ts +++ b/angular/src/providers/menu-controller.ts @@ -1,4 +1,5 @@ -import { Injectable } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { Inject, Injectable } from '@angular/core'; import { proxyMethod } from '../util/util'; @@ -8,13 +9,16 @@ const CTRL = 'ion-menu-controller'; }) export class MenuController { + constructor(@Inject(DOCUMENT) private doc: any) { + } + /** * Programmatically open the Menu. * @param [menuId] Optionally get the menu by its id, or side. * @return returns a promise when the menu is fully opened */ open(menuId?: string): Promise { - return proxyMethod(CTRL, 'open', menuId); + return proxyMethod(CTRL, this.doc, 'open', menuId); } /** @@ -25,7 +29,7 @@ export class MenuController { * @return returns a promise when the menu is fully closed */ close(menuId?: string): Promise { - return proxyMethod(CTRL, 'close', menuId); + return proxyMethod(CTRL, this.doc, 'close', menuId); } /** @@ -35,7 +39,7 @@ export class MenuController { * @return returns a promise when the menu has been toggled */ toggle(menuId?: string): Promise { - return proxyMethod(CTRL, 'toggle', menuId); + return proxyMethod(CTRL, this.doc, 'toggle', menuId); } /** @@ -47,7 +51,7 @@ export class MenuController { * @return Returns the instance of the menu, which is useful for chaining. */ enable(shouldEnable: boolean, menuId?: string): Promise { - return proxyMethod(CTRL, 'enable', shouldEnable, menuId); + return proxyMethod(CTRL, this.doc, 'enable', shouldEnable, menuId); } /** @@ -57,7 +61,7 @@ export class MenuController { * @return Returns the instance of the menu, which is useful for chaining. */ swipeEnable(shouldEnable: boolean, menuId?: string): Promise { - return proxyMethod(CTRL, 'swipeEnable', shouldEnable, menuId); + return proxyMethod(CTRL, this.doc, 'swipeEnable', shouldEnable, menuId); } /** @@ -66,7 +70,7 @@ export class MenuController { * If the menuId is not specified, it returns true if ANY menu is currenly open. */ isOpen(menuId?: string): Promise { - return proxyMethod(CTRL, 'isOpen', menuId); + return proxyMethod(CTRL, this.doc, 'isOpen', menuId); } /** @@ -74,7 +78,7 @@ export class MenuController { * @return Returns true if the menu is currently enabled, otherwise false. */ isEnabled(menuId?: string): Promise { - return proxyMethod(CTRL, 'isEnabled', menuId); + return proxyMethod(CTRL, this.doc, 'isEnabled', menuId); } /** @@ -87,20 +91,20 @@ export class MenuController { * @return Returns the instance of the menu if found, otherwise `null`. */ get(menuId?: string): Promise { - return proxyMethod(CTRL, 'get', menuId); + return proxyMethod(CTRL, this.doc, 'get', menuId); } /** * @return Returns the instance of the menu already opened, otherwise `null`. */ getOpen(): Promise { - return proxyMethod(CTRL, 'getOpen'); + return proxyMethod(CTRL, this.doc, 'getOpen'); } /** * @return Returns an array of all menu instances. */ getMenus(): Promise { - return proxyMethod(CTRL, 'getMenus'); + return proxyMethod(CTRL, this.doc, 'getMenus'); } } diff --git a/angular/src/providers/modal-controller.ts b/angular/src/providers/modal-controller.ts index 223b547211..e9cd99a91b 100644 --- a/angular/src/providers/modal-controller.ts +++ b/angular/src/providers/modal-controller.ts @@ -1,4 +1,5 @@ -import { ComponentFactoryResolver, Injectable, Injector } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { ComponentFactoryResolver, Inject, Injectable, Injector } from '@angular/core'; import { ModalOptions } from '@ionic/core'; import { OverlayBaseController } from '../util/overlay'; @@ -12,8 +13,9 @@ export class ModalController extends OverlayBaseController { diff --git a/angular/src/providers/picker-controller.ts b/angular/src/providers/picker-controller.ts index 39cc80c5e7..03bfffbc41 100644 --- a/angular/src/providers/picker-controller.ts +++ b/angular/src/providers/picker-controller.ts @@ -1,4 +1,5 @@ -import { Injectable } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { Inject, Injectable } from '@angular/core'; import { PickerOptions } from '@ionic/core'; import { OverlayBaseController } from '../util/overlay'; @@ -7,7 +8,7 @@ import { OverlayBaseController } from '../util/overlay'; providedIn: 'root', }) export class PickerController extends OverlayBaseController { - constructor() { - super('ion-picker-controller'); + constructor(@Inject(DOCUMENT) doc: any) { + super('ion-picker-controller', doc); } } diff --git a/angular/src/providers/platform.ts b/angular/src/providers/platform.ts index c2c3558826..ae15b8aff2 100644 --- a/angular/src/providers/platform.ts +++ b/angular/src/providers/platform.ts @@ -1,4 +1,5 @@ -import { Injectable } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { Inject, Injectable } from '@angular/core'; import { BackButtonEventDetail, Platforms, getPlatforms, isPlatform } from '@ionic/core'; import { Subject, Subscription } from 'rxjs'; @@ -12,6 +13,7 @@ export interface BackButtonEmitter extends Subject { export class Platform { private _readyPromise: Promise; + private win: any; /** * @hidden @@ -40,22 +42,24 @@ export class Platform { */ resize = new Subject(); - constructor() { + constructor(@Inject(DOCUMENT) private doc: any) { + this.win = doc.defaultView; + this.backButton.subscribeWithPriority = function(priority, callback) { return this.subscribe(ev => { ev.register(priority, callback); }); }; - proxyEvent(this.pause, document, 'pause'); - proxyEvent(this.resume, document, 'resume'); - proxyEvent(this.backButton, document, 'ionBackButton'); - proxyEvent(this.resize, window, 'resize'); + proxyEvent(this.pause, doc, 'pause'); + proxyEvent(this.resume, doc, 'resume'); + proxyEvent(this.backButton, doc, 'ionBackButton'); + proxyEvent(this.resize, this.win, 'resize'); let readyResolve: (value: string) => void; this._readyPromise = new Promise(res => { readyResolve = res; }); - if ((window as any)['cordova']) { - document.addEventListener('deviceready', () => { + if (this.win && this.win['cordova']) { + doc.addEventListener('deviceready', () => { readyResolve('cordova'); }, { once: true }); } else { @@ -106,7 +110,7 @@ export class Platform { * */ is(platformName: Platforms): boolean { - return isPlatform(window, platformName); + return isPlatform(this.win, platformName); } /** @@ -129,7 +133,7 @@ export class Platform { * ``` */ platforms(): string[] { - return getPlatforms(window); + return getPlatforms(this.win); } /** @@ -172,14 +176,14 @@ export class Platform { * [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir) */ get isRTL(): boolean { - return document.dir === 'rtl'; + return this.doc.dir === 'rtl'; } /** * Get the query string parameter */ getQueryParam(key: string): string | null { - return readQueryParam(window.location.href, key); + return readQueryParam(this.win.location.href, key); } /** @@ -193,45 +197,48 @@ export class Platform { * Returns `true` if the app is in portait mode. */ isPortrait(): boolean { - return window.matchMedia('(orientation: portrait)').matches; + return this.win.matchMedia && this.win.matchMedia('(orientation: portrait)').matches; } testUserAgent(expression: string): boolean { - return navigator.userAgent.indexOf(expression) >= 0; + const nav = this.win.navigator; + return !!(nav && nav.userAgent && nav.userAgent.indexOf(expression) >= 0); } /** * Get the current url. */ url() { - return window.location.href; + return this.win.location.href; } /** * Gets the width of the platform's viewport using `window.innerWidth`. */ width() { - return window.innerWidth; + return this.win.innerWidth; } /** * Gets the height of the platform's viewport using `window.innerHeight`. */ height(): number { - return window.innerHeight; + return this.win.innerHeight; } } -function readQueryParam(url: string, key: string) { +const readQueryParam = (url: string, key: string) => { key = key.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); const regex = new RegExp('[\\?&]' + key + '=([^&#]*)'); const results = regex.exec(url); return results ? decodeURIComponent(results[1].replace(/\+/g, ' ')) : null; -} +}; -function proxyEvent(emitter: Subject, el: EventTarget, eventName: string) { - el.addEventListener(eventName, (ev: Event | undefined | null) => { - // ?? cordova might emit "null" events - emitter.next(ev != null ? (ev as any).detail as T : undefined); - }); -} +const proxyEvent = (emitter: Subject, el: EventTarget, eventName: string) => { + if ((el as any)) { + el.addEventListener(eventName, (ev: Event | undefined | null) => { + // ?? cordova might emit "null" events + emitter.next(ev != null ? (ev as any).detail as T : undefined); + }); + } +}; diff --git a/angular/src/providers/popover-controller.ts b/angular/src/providers/popover-controller.ts index be92b537fb..ddef94f95a 100644 --- a/angular/src/providers/popover-controller.ts +++ b/angular/src/providers/popover-controller.ts @@ -1,4 +1,5 @@ -import { ComponentFactoryResolver, Injectable, Injector } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { ComponentFactoryResolver, Inject, Injectable, Injector } from '@angular/core'; import { PopoverOptions } from '@ionic/core'; import { OverlayBaseController } from '../util/overlay'; @@ -12,8 +13,9 @@ export class PopoverController extends OverlayBaseController { diff --git a/angular/src/providers/toast-controller.ts b/angular/src/providers/toast-controller.ts index ca5ede282e..ddaa048f61 100644 --- a/angular/src/providers/toast-controller.ts +++ b/angular/src/providers/toast-controller.ts @@ -1,4 +1,5 @@ -import { Injectable } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { Inject, Injectable } from '@angular/core'; import { ToastOptions } from '@ionic/core'; import { OverlayBaseController } from '../util/overlay'; @@ -7,7 +8,7 @@ import { OverlayBaseController } from '../util/overlay'; providedIn: 'root', }) export class ToastController extends OverlayBaseController { - constructor() { - super('ion-toast-controller'); + constructor(@Inject(DOCUMENT) doc: any) { + super('ion-toast-controller', doc); } } diff --git a/angular/src/util/overlay.ts b/angular/src/util/overlay.ts index 0ad9950a6d..733c899491 100644 --- a/angular/src/util/overlay.ts +++ b/angular/src/util/overlay.ts @@ -1,26 +1,26 @@ import { proxyMethod } from './util'; export class OverlayBaseController { - constructor(private ctrl: string) {} + constructor(private ctrl: string, private doc: Document) {} /** * Creates a new overlay */ create(opts?: Opts): Promise { - return proxyMethod(this.ctrl, 'create', opts); + return proxyMethod(this.ctrl, this.doc, 'create', opts); } /** * When `id` is not provided, it dismisses the top overlay. */ dismiss(data?: any, role?: string, id?: string): Promise { - return proxyMethod(this.ctrl, 'dismiss', data, role, id); + return proxyMethod(this.ctrl, this.doc, 'dismiss', data, role, id); } /** * Returns the top overlay. */ getTop(): Promise { - return proxyMethod(this.ctrl, 'getTop'); + return proxyMethod(this.ctrl, this.doc, 'getTop'); } } diff --git a/angular/src/util/util.ts b/angular/src/util/util.ts index 0012c7475d..54c123f8de 100644 --- a/angular/src/util/util.ts +++ b/angular/src/util/util.ts @@ -1,15 +1,15 @@ -export function proxyMethod(ctrlName: string, methodName: string, ...args: any[]) { - const controller = ensureElementInBody(ctrlName); +export function proxyMethod(ctrlName: string, doc: Document, methodName: string, ...args: any[]) { + const controller = ensureElementInBody(ctrlName, doc); return controller.componentOnReady() .then(() => (controller as any)[methodName].apply(controller, args)); } -export function ensureElementInBody(elementName: string) { - let element = document.querySelector(elementName); +export function ensureElementInBody(elementName: string, doc: Document) { + let element = doc.querySelector(elementName); if (!element) { - element = document.createElement(elementName); - document.body.appendChild(element); + element = doc.createElement(elementName); + doc.body.appendChild(element); } return element as HTMLStencilElement; } diff --git a/core/src/components/segment/test/custom/index.html b/core/src/components/segment/test/custom/index.html index 03793a29e6..0e4c51f34a 100644 --- a/core/src/components/segment/test/custom/index.html +++ b/core/src/components/segment/test/custom/index.html @@ -140,34 +140,6 @@ - -