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 { CoreComponentsRegistry } from '@singletons/components-registry';
import { CoreFormatTextDirective } from './format-text';
import { CoreEventObserver } from '@singletons/events';
import { CoreEventObserver, CoreSingleTimeEventObserver } from '@singletons/events';
import { CoreLoadingComponent } from '@components/loading/loading';
import { CoreDomUtils } from '@services/utils/dom';
@ -48,6 +48,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
protected contentScrollListener?: EventListener;
protected endContentScrollListener?: EventListener;
protected resizeListener?: CoreEventObserver;
protected domListener?: CoreSingleTimeEventObserver;
constructor(el: ElementRef, protected ionContent: IonContent) {
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.
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.waitFormatTextsRendered(this.element);
@ -231,6 +234,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
}
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 { Translate } from '@singletons';
import { CoreComponentsRegistry } from '@singletons/components-registry';
import { CoreEventObserver } from '@singletons/events';
import { CoreEventObserver, CoreSingleTimeEventObserver } from '@singletons/events';
import { CoreFormatTextDirective } from './format-text';
const defaultMaxHeight = 80;
@ -49,6 +49,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
protected maxHeight = defaultMaxHeight;
protected expandedHeight = 0;
protected resizeListener?: CoreEventObserver;
protected domListener?: CoreSingleTimeEventObserver;
constructor(el: ElementRef<HTMLElement>) {
this.element = el.nativeElement;
@ -95,7 +96,8 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
* @return Promise resolved when loadings are done.
*/
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');
@ -240,6 +242,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
*/
ngOnDestroy(): void {
this.resizeListener?.off();
this.domListener?.off();
}
}

View File

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

View File

@ -36,7 +36,7 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUtils } from '@services/utils/utils';
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 { CoreComponentsRegistry } from '@singletons/components-registry';
import { CoreLoadingComponent } from '@components/loading/loading';
@ -104,6 +104,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
protected selectionChangeFunction?: () => void;
protected languageChangedSubscription?: Subscription;
protected resizeListener?: CoreEventObserver;
protected domListener?: CoreSingleTimeEventObserver;
rteEnabled = false;
isPhone = false;
@ -249,9 +250,6 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
this.windowResized();
}, 50);
// Start observing the target node for configured mutations
this.resizeObserver?.observe(this.element);
document.addEventListener('selectionchange', this.selectionChangeFunction = this.updateToolbarStyles.bind(this));
this.keyboardObserver = CoreEvents.on(CoreEvents.KEYBOARD_CHANGE, () => {
@ -289,7 +287,8 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
* @return Promise resolved when loadings are done.
*/
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');
@ -829,7 +828,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
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;
@ -1075,7 +1074,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
}
/**
* Component being destroyed.
* @inheritdoc
*/
ngOnDestroy(): void {
this.valueChangeSubscription?.unsubscribe();
@ -1088,6 +1087,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
this.keyboardObserver?.off();
this.labelObserver?.disconnect();
this.resizeListener?.off();
this.domListener?.off();
}
}

View File

@ -53,7 +53,7 @@ import { NavigationStart } from '@angular/router';
import { filter } from 'rxjs/operators';
import { Subscription } from 'rxjs';
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.
@ -96,36 +96,52 @@ export class CoreDomUtilsProvider {
* Wait an element to be in dom of another element.
*
* @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,
): Promise<void> {
timeout?: number,
): CoreSingleTimeEventObserver {
let root = element.getRootNode({ composed: true });
if (root === document) {
// Already in DOM.
return;
return {
off: (): void => {
// Nothing to do here.
},
promise: Promise.resolve(),
};
}
return new Promise((resolve, reject) => {
// Disconnect observer for performance reasons.
const timeout = window.setTimeout(() => {
observer.disconnect();
reject(new Error('Waiting for DOM timeout reached'));
}, 5000);
let observer: MutationObserver | undefined;
let observerTimeout: number | undefined;
if (timeout) {
observerTimeout = window.setTimeout(() => {
observer?.disconnect();
throw new Error('Waiting for DOM timeout reached');
}, timeout);
}
const observer = new MutationObserver(() => {
root = element.getRootNode({ composed: true });
if (root === document) {
observer.disconnect();
clearTimeout(timeout);
resolve();
}
});
return {
off: (): void => {
observer?.disconnect();
clearTimeout(observerTimeout);
},
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;
}
/**
* 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.
*/