MOBILE-3814 chore: Add a disconnect method on waitToBeInDOM

main
Pau Ferrer Ocaña 2022-03-14 22:56:05 +01:00
parent 30d24f99e3
commit 23cfb29de0
6 changed files with 84 additions and 35 deletions

View File

@ -19,7 +19,7 @@ import { CoreUtils } from '@services/utils/utils';
import { CoreMath } from '@singletons/math'; import { CoreMath } from '@singletons/math';
import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreComponentsRegistry } from '@singletons/components-registry';
import { CoreFormatTextDirective } from './format-text'; import { CoreFormatTextDirective } from './format-text';
import { CoreEventObserver } from '@singletons/events'; import { CoreEventObserver, CoreSingleTimeEventObserver } from '@singletons/events';
import { CoreLoadingComponent } from '@components/loading/loading'; import { CoreLoadingComponent } from '@components/loading/loading';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
@ -48,6 +48,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
protected contentScrollListener?: EventListener; protected contentScrollListener?: EventListener;
protected endContentScrollListener?: EventListener; protected endContentScrollListener?: EventListener;
protected resizeListener?: CoreEventObserver; protected resizeListener?: CoreEventObserver;
protected domListener?: CoreSingleTimeEventObserver;
constructor(el: ElementRef, protected ionContent: IonContent) { constructor(el: ElementRef, protected ionContent: IonContent) {
this.element = el.nativeElement; this.element = el.nativeElement;
@ -61,7 +62,9 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
// Only if not present or explicitly falsy it will be false. // Only if not present or explicitly falsy it will be false.
this.appearOnBottom = !CoreUtils.isFalseOrZero(this.appearOnBottom); this.appearOnBottom = !CoreUtils.isFalseOrZero(this.appearOnBottom);
await CoreDomUtils.waitToBeInDOM(this.element); this.domListener = CoreDomUtils.waitToBeInDOM(this.element);
await this.domListener.promise;
await this.waitLoadingsDone(); await this.waitLoadingsDone();
await this.waitFormatTextsRendered(this.element); await this.waitFormatTextsRendered(this.element);
@ -231,6 +234,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
} }
this.resizeListener?.off(); this.resizeListener?.off();
this.domListener?.off();
} }
} }

View File

@ -18,7 +18,7 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons'; import { Translate } from '@singletons';
import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreComponentsRegistry } from '@singletons/components-registry';
import { CoreEventObserver } from '@singletons/events'; import { CoreEventObserver, CoreSingleTimeEventObserver } from '@singletons/events';
import { CoreFormatTextDirective } from './format-text'; import { CoreFormatTextDirective } from './format-text';
const defaultMaxHeight = 80; const defaultMaxHeight = 80;
@ -49,6 +49,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
protected maxHeight = defaultMaxHeight; protected maxHeight = defaultMaxHeight;
protected expandedHeight = 0; protected expandedHeight = 0;
protected resizeListener?: CoreEventObserver; protected resizeListener?: CoreEventObserver;
protected domListener?: CoreSingleTimeEventObserver;
constructor(el: ElementRef<HTMLElement>) { constructor(el: ElementRef<HTMLElement>) {
this.element = el.nativeElement; this.element = el.nativeElement;
@ -95,7 +96,8 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
* @return Promise resolved when loadings are done. * @return Promise resolved when loadings are done.
*/ */
protected async waitLoadingsDone(): Promise<void> { protected async waitLoadingsDone(): Promise<void> {
await CoreDomUtils.waitToBeInDOM(this.element); this.domListener = CoreDomUtils.waitToBeInDOM(this.element);
await this.domListener.promise;
const page = this.element.closest('.ion-page'); const page = this.element.closest('.ion-page');
@ -240,6 +242,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.resizeListener?.off(); this.resizeListener?.off();
this.domListener?.off();
} }
} }

View File

@ -23,6 +23,7 @@ import {
Optional, Optional,
ViewContainerRef, ViewContainerRef,
ViewChild, ViewChild,
OnDestroy,
} from '@angular/core'; } from '@angular/core';
import { IonContent } from '@ionic/angular'; import { IonContent } from '@ionic/angular';
@ -41,6 +42,7 @@ import { CoreFilterHelper } from '@features/filter/services/filter-helper';
import { CoreSubscriptions } from '@singletons/subscriptions'; import { CoreSubscriptions } from '@singletons/subscriptions';
import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreComponentsRegistry } from '@singletons/components-registry';
import { CoreCollapsibleItemDirective } from './collapsible-item'; import { CoreCollapsibleItemDirective } from './collapsible-item';
import { CoreSingleTimeEventObserver } from '@singletons/events';
/** /**
* Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective
@ -54,7 +56,7 @@ import { CoreCollapsibleItemDirective } from './collapsible-item';
@Directive({ @Directive({
selector: 'core-format-text', selector: 'core-format-text',
}) })
export class CoreFormatTextDirective implements OnChanges { export class CoreFormatTextDirective implements OnChanges, OnDestroy {
@ViewChild(CoreCollapsibleItemDirective) collapsible?: CoreCollapsibleItemDirective; @ViewChild(CoreCollapsibleItemDirective) collapsible?: CoreCollapsibleItemDirective;
@ -88,6 +90,7 @@ export class CoreFormatTextDirective implements OnChanges {
protected element: HTMLElement; protected element: HTMLElement;
protected emptyText = ''; protected emptyText = '';
protected contentSpan: HTMLElement; protected contentSpan: HTMLElement;
protected domListener?: CoreSingleTimeEventObserver;
constructor( constructor(
element: ElementRef, element: ElementRef,
@ -126,6 +129,13 @@ export class CoreFormatTextDirective implements OnChanges {
} }
} }
/**
* @inheritdoc
*/
ngOnDestroy(): void {
this.domListener?.off();
}
/** /**
* Wait until the text is fully rendered. * Wait until the text is fully rendered.
*/ */
@ -546,7 +556,8 @@ export class CoreFormatTextDirective implements OnChanges {
* @return The width of the element in pixels. * @return The width of the element in pixels.
*/ */
protected async getElementWidth(): Promise<number> { protected async getElementWidth(): Promise<number> {
await CoreUtils.ignoreErrors(CoreDomUtils.waitToBeInDOM(this.element)); this.domListener = CoreDomUtils.waitToBeInDOM(this.element);
await this.domListener.promise;
let width = this.element.getBoundingClientRect().width; let width = this.element.getBoundingClientRect().width;
if (!width) { if (!width) {
@ -700,7 +711,7 @@ export class CoreFormatTextDirective implements OnChanges {
let width: string | number; let width: string | number;
let height: string | number; let height: string | number;
await CoreDomUtils.waitToBeInDOM(iframe); await CoreDomUtils.waitToBeInDOM(iframe, 5000).promise;
if (iframe.width) { if (iframe.width) {
width = iframe.width; width = iframe.width;

View File

@ -36,7 +36,7 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreUrlUtils } from '@services/utils/url'; import { CoreUrlUtils } from '@services/utils/url';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { Platform, Translate } from '@singletons'; import { Platform, Translate } from '@singletons';
import { CoreEventFormActionData, CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreEventFormActionData, CoreEventObserver, CoreEvents, CoreSingleTimeEventObserver } from '@singletons/events';
import { CoreEditorOffline } from '../../services/editor-offline'; import { CoreEditorOffline } from '../../services/editor-offline';
import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreComponentsRegistry } from '@singletons/components-registry';
import { CoreLoadingComponent } from '@components/loading/loading'; import { CoreLoadingComponent } from '@components/loading/loading';
@ -104,6 +104,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
protected selectionChangeFunction?: () => void; protected selectionChangeFunction?: () => void;
protected languageChangedSubscription?: Subscription; protected languageChangedSubscription?: Subscription;
protected resizeListener?: CoreEventObserver; protected resizeListener?: CoreEventObserver;
protected domListener?: CoreSingleTimeEventObserver;
rteEnabled = false; rteEnabled = false;
isPhone = false; isPhone = false;
@ -249,9 +250,6 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
this.windowResized(); this.windowResized();
}, 50); }, 50);
// Start observing the target node for configured mutations
this.resizeObserver?.observe(this.element);
document.addEventListener('selectionchange', this.selectionChangeFunction = this.updateToolbarStyles.bind(this)); document.addEventListener('selectionchange', this.selectionChangeFunction = this.updateToolbarStyles.bind(this));
this.keyboardObserver = CoreEvents.on(CoreEvents.KEYBOARD_CHANGE, () => { this.keyboardObserver = CoreEvents.on(CoreEvents.KEYBOARD_CHANGE, () => {
@ -289,7 +287,8 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
* @return Promise resolved when loadings are done. * @return Promise resolved when loadings are done.
*/ */
protected async waitLoadingsDone(): Promise<void> { protected async waitLoadingsDone(): Promise<void> {
await CoreDomUtils.waitToBeInDOM(this.element); this.domListener = CoreDomUtils.waitToBeInDOM(this.element);
await this.domListener.promise;
const page = this.element.closest('.ion-page'); const page = this.element.closest('.ion-page');
@ -829,7 +828,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
const length = await this.toolbarSlides.length(); const length = await this.toolbarSlides.length();
await CoreDomUtils.waitToBeInDOM(this.toolbar.nativeElement); await CoreDomUtils.waitToBeInDOM(this.toolbar.nativeElement, 5000).promise;
const width = this.toolbar.nativeElement.getBoundingClientRect().width; const width = this.toolbar.nativeElement.getBoundingClientRect().width;
@ -1075,7 +1074,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
} }
/** /**
* Component being destroyed. * @inheritdoc
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.valueChangeSubscription?.unsubscribe(); this.valueChangeSubscription?.unsubscribe();
@ -1088,6 +1087,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
this.keyboardObserver?.off(); this.keyboardObserver?.off();
this.labelObserver?.disconnect(); this.labelObserver?.disconnect();
this.resizeListener?.off(); this.resizeListener?.off();
this.domListener?.off();
} }
} }

View File

@ -53,7 +53,7 @@ import { NavigationStart } from '@angular/router';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreComponentsRegistry } from '@singletons/components-registry';
import { CoreEventObserver } from '@singletons/events'; import { CoreEventObserver, CoreSingleTimeEventObserver } from '@singletons/events';
/* /*
* "Utils" service with helper functions for UI, DOM elements and HTML code. * "Utils" service with helper functions for UI, DOM elements and HTML code.
@ -96,36 +96,52 @@ export class CoreDomUtilsProvider {
* Wait an element to be in dom of another element. * Wait an element to be in dom of another element.
* *
* @param element Element to wait. * @param element Element to wait.
* @return Promise resolved when added. It will be rejected after a timeout of 5s. * @param timeout If defined, timeout to wait before rejecting the promise.
* @return Promise CoreSingleTimeEventObserver with a promise.
*/ */
async waitToBeInDOM( waitToBeInDOM(
element: Element, element: Element,
): Promise<void> { timeout?: number,
): CoreSingleTimeEventObserver {
let root = element.getRootNode({ composed: true }); let root = element.getRootNode({ composed: true });
if (root === document) { if (root === document) {
// Already in DOM. // Already in DOM.
return; return {
off: (): void => {
// Nothing to do here.
},
promise: Promise.resolve(),
};
} }
return new Promise((resolve, reject) => { let observer: MutationObserver | undefined;
// Disconnect observer for performance reasons. let observerTimeout: number | undefined;
const timeout = window.setTimeout(() => { if (timeout) {
observer.disconnect(); observerTimeout = window.setTimeout(() => {
reject(new Error('Waiting for DOM timeout reached')); observer?.disconnect();
}, 5000); throw new Error('Waiting for DOM timeout reached');
}, timeout);
}
const observer = new MutationObserver(() => { return {
root = element.getRootNode({ composed: true }); off: (): void => {
if (root === document) { observer?.disconnect();
observer.disconnect(); clearTimeout(observerTimeout);
clearTimeout(timeout); },
resolve(); promise: new Promise((resolve) => {
} observer = new MutationObserver(() => {
}); root = element.getRootNode({ composed: true });
if (root === document) {
observer?.disconnect();
clearTimeout(observerTimeout);
resolve();
}
});
observer.observe(document.body, { subtree: true, childList: true }); observer.observe(document.body, { subtree: true, childList: true });
}); }),
};
} }
/** /**

View File

@ -31,6 +31,21 @@ export interface CoreEventObserver {
off: () => void; off: () => void;
} }
/**
* Observer instance to stop listening to an observer.
*/
export interface CoreSingleTimeEventObserver {
/**
* Stop the observer.
*/
off: () => void;
/**
* Promise Resolved when event is done (first time).
*/
promise: Promise<void>;
}
/** /**
* Event payloads. * Event payloads.
*/ */