import { ElementUtils } from '@dooboostore/core-web'; import { OnChangeAttrRender, OtherData } from '../lifecycle/OnChangeAttrRender'; import { OnCreateRenderData, OnCreateRenderDataParams } from '../lifecycle/OnCreateRenderData'; import { RawSet } from '../rawsets/RawSet'; import { ConstructorType } from '@dooboostore/core'; import { OnCreatedThisChild } from '../lifecycle/OnCreatedThisChild'; import { DomRenderNoProxy } from '../decorators/DomRenderNoProxy'; import { OnInitRender } from '../lifecycle/OnInitRender'; import { ConvertUtils } from '@dooboostore/core'; import { OnDestroyRender, OnDestroyRenderParams } from '../lifecycle/OnDestroyRender'; import { OnDrThisUnBind } from '../lifecycle/dr-this/OnDrThisUnBind'; import { OnDrThisBind } from '../lifecycle/dr-this/OnDrThisBind'; import type { Render } from '../rawsets/Render'; import { ReflectUtils } from '@dooboostore/core'; import { DomRenderConfig } from '../configs/DomRenderConfig'; import { Subject } from '@dooboostore/core'; import { MessageOperator } from '@dooboostore/core'; import type { Subscription } from '@dooboostore/core'; import { OnRawSetRendered, OnRawSetRenderedOtherData } from "../lifecycle/OnRawSetRendered"; import {OnChildRawSetRendered} from "../lifecycle/OnChildRawSetRendered"; export const ATTRIBUTE_METADATA_KEY = Symbol('attribute'); export const QUERY_METADATA_KEY = Symbol('query'); export const EVENT_METADATA_KEY = Symbol('event'); export interface AttributeMetadata { propertyKey: string | symbol; name: string; converter?: (value: any | string | null) => any; isMethod: boolean; } export interface QueryMetadata { selector: string; propertyKey: string | symbol; isMethod: boolean; convert?: (e: Element) => any; onDestroy?: (componentInstance: any, element: Element) => void; refreshTime?: number; // milliseconds refreshRawSetRendered?: boolean; } export interface EventMetadata { query: string; eventName: string; methodName: string | symbol; onDestroy?: (componentInstance: any, element: Element) => void; refreshTime?: number; // milliseconds refreshRawSetRendered?: boolean; } export function attribute(nameOrOptions?: string | { name?: string, converter?: (value: any | string | null) => any }): (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor) => void { return (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor) => { const attributes = ReflectUtils.getMetadata(ATTRIBUTE_METADATA_KEY, target.constructor) || []; let name: string; let converter: ((value: any | string | null) => any) | undefined; if (typeof nameOrOptions === 'string') { name = nameOrOptions; } else if (nameOrOptions) { name = nameOrOptions.name || String(propertyKey); converter = nameOrOptions.converter; } else { name = String(propertyKey); } const attributeMetadata: AttributeMetadata = { propertyKey: propertyKey, name: name, converter: converter, isMethod: !!descriptor }; attributes.push(attributeMetadata); ReflectUtils.defineMetadata(ATTRIBUTE_METADATA_KEY, attributes, target.constructor); }; } export function query(selectorOrOptions: string | { selector: string, convert?: (e: Element) => any, onDestroy?: (componentInstance: any, element: Element) => void, refreshIntervalTime?: number, refreshRawSetRendered?: boolean }): (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor) => void { return (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor) => { const queries = ReflectUtils.getMetadata(QUERY_METADATA_KEY, target.constructor) || []; let selector: string; let convert: ((e: Element) => any) | undefined; let onDestroy: ((componentInstance: any, element: Element) => void) | undefined; let refreshTime: number | undefined; let refreshRawSetRendered: boolean | undefined; if (typeof selectorOrOptions === 'string') { selector = selectorOrOptions; } else { selector = selectorOrOptions.selector; convert = selectorOrOptions.convert; onDestroy = selectorOrOptions.onDestroy; refreshTime = selectorOrOptions.refreshIntervalTime; refreshRawSetRendered = selectorOrOptions.refreshRawSetRendered; } const queryMetadata: QueryMetadata = { selector: selector, propertyKey: propertyKey, isMethod: !!descriptor, convert: convert, onDestroy: onDestroy, refreshTime: refreshTime, refreshRawSetRendered }; queries.push(queryMetadata); ReflectUtils.defineMetadata(QUERY_METADATA_KEY, queries, target.constructor); }; } export function event(options: { query: string, name: string, onDestroy?: (componentInstance: any, element: Element) => void, refreshTime?: number, refreshRawSetRendered?: boolean }): MethodDecorator { return (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => { const events = ReflectUtils.getMetadata(EVENT_METADATA_KEY, target.constructor) || []; const eventMetadata: EventMetadata = { query: options.query, eventName: options.name, methodName: propertyKey, onDestroy: options.onDestroy, refreshTime: options.refreshTime, refreshRawSetRendered: options.refreshRawSetRendered }; events.push(eventMetadata); ReflectUtils.defineMetadata(EVENT_METADATA_KEY, events, target.constructor); }; } export type ComponentBaseConfig = { onlyParentType?: ConstructorType[] | ConstructorType, createChildrenDebounce?: number, onRawSetRenderedOtherDataDebounce?: number onChildRawSetRenderedOtherDataDebounce?: number }; export type ChildrenSet = { instance: any, data: OnCreateRenderDataParams }; export class ComponentBase implements OnChangeAttrRender, OnCreateRenderData, OnCreatedThisChild, OnDrThisBind, OnDrThisUnBind, OnInitRender, OnDestroyRender, OnRawSetRendered, OnChildRawSetRendered { @DomRenderNoProxy private _rawSet?: RawSet; @DomRenderNoProxy private _children: ChildrenSet[] = []; @DomRenderNoProxy private _render: Render | undefined; @DomRenderNoProxy private _attribute?: T; @DomRenderNoProxy private _boundEventListeners: { element: Element, eventName: string, listener: (e: Event) => void }[] = []; @DomRenderNoProxy private _queryBindings: { element: Element, metadata: QueryMetadata }[] = []; @DomRenderNoProxy private _eventBindings: { element: Element, metadata: EventMetadata, listener: (e: Event) => void }[] = []; @DomRenderNoProxy private _refreshTimers: Map = new Map(); @DomRenderNoProxy private childrenSetSubject = new Subject(); @DomRenderNoProxy private createChildrenDebounceSubscription?: Subscription | undefined; @DomRenderNoProxy private onRawSetRenderedOtherDataSubject = new Subject(); @DomRenderNoProxy private onChildRawSetRenderedOtherDataSubject = new Subject(); @DomRenderNoProxy private onRawSetRenderedOtherDataSubjectSubscription?: Subscription | undefined; @DomRenderNoProxy private onChildRawSetRenderedOtherDataSubjectSubscription?: Subscription | undefined; constructor(private _config?: C) { // console.log('ComponentBase created', this); // setTimeout(()=>{ // console.log('ComponentBase created', this.constructor.name, this._rawSet?.uuid); // }, 1000) } get componentConfig(): C | undefined { return this._config; } get render(): Render | undefined { return this._render; } get rawSet(): RawSet | undefined { return this._rawSet; } get domRenderConfig(): DomRenderConfig | undefined { return this.rawSet?.dataSet.config; } private _queryElements(selector: string, rawSet:RawSet): Element[] { const e = this.getElement(); // console.log('queryElement-->', e); if (e) { return ElementUtils.querySelectorAll(e, selector); } else { let startNode = rawSet.point?.start; let endNode = rawSet.point?.end; // console.log('-----', startNode, endNode) // console.log('-----', startNode?.isConnected, endNode?.isConnected) if (rawSet.point?.start && startNode && !startNode.isConnected && startNode instanceof HTMLElement && startNode.hasAttribute('id')) { rawSet.point.start = startNode = rawSet.dataSet.config.window.document.querySelector('#' + startNode.id) as HTMLMetaElement } if (rawSet.point?.end && endNode && !endNode.isConnected && endNode instanceof HTMLElement && endNode.hasAttribute('id')) { rawSet.point.end = endNode = rawSet.dataSet.config.window.document.querySelector('#' + endNode.id) as HTMLMetaElement } if (startNode && endNode) { // console.log('query!!', startNode, endNode); return ElementUtils.querySelectorAll({ start: startNode as Element, end: endNode as Element }, selector); } } return []; } private _cleanupEventListeners() { for (const { element, eventName, listener } of this._boundEventListeners) { element.removeEventListener(eventName, listener); } this._boundEventListeners = []; } private _cleanupQueryBindings() { for (const { element, metadata } of this._queryBindings) { if (metadata.onDestroy) { metadata.onDestroy(this, element); } this._clearRefreshTimer(metadata); } this._queryBindings = []; } private _cleanupEventBindings() { for (const { element, metadata, listener } of this._eventBindings) { element.removeEventListener(metadata.eventName, listener); if (metadata.onDestroy) { metadata.onDestroy(this, element); } this._clearRefreshTimer(metadata); } this._eventBindings = []; } private _applyQueryBinding(queryInfo: QueryMetadata, elements: Element[], newElementsOnly: Element[] = elements) { const { propertyKey, isMethod, convert } = queryInfo; // Track bindings for cleanup elements.forEach(element => { this._queryBindings.push({ element, metadata: queryInfo }); }); // Apply convert function if provided const processedElements = convert ? elements.map(convert) : elements; const processedNewElements = convert ? newElementsOnly.map(convert) : newElementsOnly; if (isMethod) { const method = (this as any)[propertyKey]; if (typeof method === 'function') { const paramTypes = ReflectUtils.getParameterTypes(this, propertyKey); if (paramTypes && paramTypes.length > 0 && paramTypes[0] === Array) { // For array methods, pass only new elements method.call(this, processedNewElements, {all: processedElements}); } else { // For single element methods, pass first new element if (processedNewElements.length > 0) { method.call(this, processedNewElements[0], {all: processedElements, newAll: processedNewElements}); } } } } else { // For properties, always set all elements (not just new ones) const propertyType = ReflectUtils.getType(this, propertyKey); if (propertyType === Array) { (this as any)[propertyKey] = processedElements; } else { (this as any)[propertyKey] = processedElements[0] || null; } } } private _setupQueryDecorators(rawSet: RawSet) { if (!rawSet) return; const queries = ReflectUtils.getMetadata(QUERY_METADATA_KEY, this.constructor); // console.group('_setupQueryDecorators') // console.log('queries', queries); if (queries && Array.isArray(queries)) { for (const queryInfo of queries) { const elements = this._queryElements(queryInfo.selector, rawSet); // console.log('------elements', elements); this._applyQueryBinding(queryInfo, elements); // Setup refresh timer if specified this._setupRefreshTimer(queryInfo); } } // console.groupEnd(); } private _updateQueryBinding(queryInfo: QueryMetadata, rawSet: RawSet, forceUpdate = false) { const newElements = this._queryElements(queryInfo.selector, rawSet); // Get current bindings for this query const currentBindings = this._queryBindings.filter(binding => binding.metadata === queryInfo); const currentElements = currentBindings.map(binding => binding.element); // Check if elements have changed const elementsChanged = forceUpdate || !this._arraysEqual(currentElements, newElements); if (elementsChanged) { let addedElements: Element[] = []; if (forceUpdate) { // Force update: cleanup all old bindings, treat all as new currentBindings.forEach(({ element, metadata }) => { if (metadata.onDestroy) { metadata.onDestroy(this, element); } }); addedElements = newElements; } else { // Smart update: only cleanup removed elements, find added elements const removedElements = currentElements.filter(element => !newElements.includes(element)); addedElements = newElements.filter(element => !currentElements.includes(element)); removedElements.forEach(element => { if (queryInfo.onDestroy) { queryInfo.onDestroy(this, element); } }); } // Remove old bindings this._queryBindings = this._queryBindings.filter(binding => binding.metadata !== queryInfo); // Apply new bindings, but pass only added elements to methods this._applyQueryBinding(queryInfo, newElements, addedElements); } } private _updateQueryDecorators(rawSet: RawSet) { if (!rawSet) return; const queries = ReflectUtils.getMetadata(QUERY_METADATA_KEY, this.constructor); if (queries && Array.isArray(queries)) { for (const queryInfo of queries) { this._updateQueryBinding(queryInfo, rawSet); } } } private _arraysEqual(arr1: Element[], arr2: Element[]): boolean { if (arr1.length !== arr2.length) return false; return arr1.every((element, index) => element === arr2[index]); } private _setupRefreshTimer(metadata: QueryMetadata | EventMetadata) { if (!metadata.refreshTime || !this._rawSet) return; const timer = setInterval(() => { if (!this._rawSet) return; if ('selector' in metadata) { // QueryMetadata this._updateQueryBinding(metadata as QueryMetadata, this._rawSet); } else { // EventMetadata this._updateEventBinding(metadata as EventMetadata, this._rawSet); } }, metadata.refreshTime); this._refreshTimers.set(metadata, timer); } private _clearRefreshTimer(metadata: QueryMetadata | EventMetadata) { const timer = this._refreshTimers.get(metadata); if (timer) { clearInterval(timer); this._refreshTimers.delete(metadata); } } private _clearAllRefreshTimers() { for (const timer of Array.from(this._refreshTimers.values())) { clearInterval(timer); } this._refreshTimers.clear(); } async onRawSetRendered(rawSet: RawSet, otherData: OnRawSetRenderedOtherData): Promise { this.onRawSetRenderedOtherDataSubject.next(otherData); // this.onRawSetRenderedOtherDataSubjectSubscription = this.onRawSetRenderedOtherDataSubject.pipe(debounceTime(this.componentConfig?.onRawSetRenderedOtherDataDebounce??0)).subscribe(it => { // this.onRawSetRenderedDebounce(it); // }); const queries = ReflectUtils.getMetadata(QUERY_METADATA_KEY, this.constructor); if (queries) { queries.filter(it => it.refreshRawSetRendered).forEach(queryInfo => { this._updateQueryBinding(queryInfo, rawSet, true); }); } const events = ReflectUtils.getMetadata(EVENT_METADATA_KEY, this.constructor); if (events) { events.filter(it => it.refreshRawSetRendered).forEach(eventInfo => { this._updateEventBinding(eventInfo, rawSet, true); }); } } async onChildRawSetRendered(): Promise { this.onChildRawSetRenderedOtherDataSubject.next(); } /** * Manually refresh all query and event decorators * Useful when DOM elements are dynamically added/removed and you want to update bindings */ refreshDecorators(): void { if (this._rawSet) { this._updateQueryDecorators(this._rawSet); this._updateEventDecorators(this._rawSet); } } /** * Manually refresh specific query decorators by property key * @param propertyKeys - Array of property keys to refresh, or single property key */ refreshQueryDecorators(propertyKeys: (string | symbol) | (string | symbol)[]): void { if (!this._rawSet) return; const keys = Array.isArray(propertyKeys) ? propertyKeys : [propertyKeys]; const queries = ReflectUtils.getMetadata(QUERY_METADATA_KEY, this.constructor); if (queries && Array.isArray(queries)) { for (const queryInfo of queries) { if (keys.includes(queryInfo.propertyKey)) { this._updateQueryBinding(queryInfo, this._rawSet, true); // Force update } } } } /** * Manually refresh specific event decorators by method name * @param methodNames - Array of method names to refresh, or single method name */ refreshEventDecorators(methodNames: (string | symbol) | (string | symbol)[]): void { if (!this._rawSet) return; const names = Array.isArray(methodNames) ? methodNames : [methodNames]; const events = ReflectUtils.getMetadata(EVENT_METADATA_KEY, this.constructor); if (events && Array.isArray(events)) { for (const eventInfo of events) { if (names.includes(eventInfo.methodName)) { this._updateEventBinding(eventInfo, this._rawSet, true); // Force update } } } } private _applyEventBinding(eventInfo: EventMetadata, elements: Element[]) { elements.forEach(element => { const listener = (this as any)[eventInfo.methodName].bind(this); // console.log('bind?????', element); element.addEventListener(eventInfo.eventName, listener); // Track bindings for cleanup this._eventBindings.push({ element, metadata: eventInfo, listener }); this._boundEventListeners.push({ element, eventName: eventInfo.eventName, listener }); }); } private _setupEventDecorators(rawSet: RawSet) { if (!rawSet) return; const events = ReflectUtils.getMetadata(EVENT_METADATA_KEY, this.constructor); if (events && Array.isArray(events)) { for (const eventInfo of events) { const elements = this._queryElements(eventInfo.query, rawSet); this._applyEventBinding(eventInfo, elements); // Setup refresh timer if specified this._setupRefreshTimer(eventInfo); } } } private _updateEventBinding(eventInfo: EventMetadata, rawSet: RawSet, forceUpdate = false) { const newElements = this._queryElements(eventInfo.query, rawSet); // Get current bindings for this event const currentBindings = this._eventBindings.filter(binding => binding.metadata === eventInfo); const currentElements = currentBindings.map(binding => binding.element); // Check if elements have changed const elementsChanged = forceUpdate || !this._arraysEqual(currentElements, newElements); if (elementsChanged) { if (forceUpdate) { // Force update: cleanup all old bindings currentBindings.forEach(({ element, metadata, listener }) => { element.removeEventListener(metadata.eventName, listener); if (metadata.onDestroy) { metadata.onDestroy(this, element); } }); } else { // Smart update: only cleanup removed elements const removedElements = currentElements.filter(element => !newElements.includes(element)); const removedBindings = currentBindings.filter(binding => removedElements.includes(binding.element)); removedBindings.forEach(({ element, metadata, listener }) => { element.removeEventListener(metadata.eventName, listener); if (metadata.onDestroy) { metadata.onDestroy(this, element); } }); } // Remove old bindings from tracking arrays this._eventBindings = this._eventBindings.filter(binding => binding.metadata !== eventInfo); this._boundEventListeners = this._boundEventListeners.filter(binding => !currentBindings.some(cb => cb.element === binding.element && cb.metadata.eventName === binding.eventName) ); // Apply new bindings this._applyEventBinding(eventInfo, newElements); } } private _updateEventDecorators(rawSet: RawSet) { if (!rawSet) return; const events = ReflectUtils.getMetadata(EVENT_METADATA_KEY, this.constructor); if (events && Array.isArray(events)) { for (const eventInfo of events) { this._updateEventBinding(eventInfo, rawSet); } } } getAttributeNames(attribute = this._attribute): string[] { const target = attribute as any; if (!target) return []; const keys = Object.keys(target); const names = new Set(); for (const k of keys) { names.add(k); // names.add(ConvertUtils.camelToSnakeCase(k)); // names.add(ConvertUtils.snakeToCamelCase(k)); // names.add(k.toLowerCase()); // names.add(k.toUpperCase()); } return Array.from(names); } getAttribute(name: K, attribute = this._attribute): T[K] | null { const target = attribute as any; if (!target) { return null; } const nameString = String(name); let p = nameString; if (p in target) { return target[p]; } p = ConvertUtils.snakeToCamelCase(nameString); if (p in target) { return target[p]; } p = ConvertUtils.camelToSnakeCase(nameString); if (p in target) { return target[p]; } p = nameString.toUpperCase() if (p in target) { return target[p]; } p = nameString.toLowerCase() if (p in target) { return target[p]; } return null; } hasAttribute(name: keyof T) { return this.getAttribute(name) !==null; } equalsAttributeName(n: string, name: keyof T) { return n === name || ConvertUtils.snakeToCamelCase(n) === name || ConvertUtils.camelToSnakeCase(n) === name || n.toUpperCase() === (name as string).toUpperCase() || n.toLowerCase() === (name as string).toLowerCase() ; } get uuid(): string | undefined { return this._rawSet?.uuid; } get childrenSet() { return this._children; } get children(): any[] { return this._children.map(it=>it.instance); } getElement(): T | undefined { return (this.render ?? this.rawSet?.dataSet?.render)?.getElement?.(); } getFirstChild(type: ConstructorType): T | undefined { return this._children.find(it => it.instance instanceof type)?.instance; } getParentThis

():P { return this.render?.parentThis as P; } getChildren>(type: C): InstanceType[]; getChildren[]>(types: C): InstanceType[]; getChildren(typeOrTypes: ConstructorType | ConstructorType[]): any[] { const targets = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes]; return this._children.filter(childSet => targets.some(targetConstructor => childSet.instance instanceof targetConstructor) )?.map(it=>it.instance); } private setRawSet(rawSet: RawSet) { this._rawSet = rawSet; if (this._rawSet) { this.setAttribute(this._rawSet.dataSet?.render?.attribute as T); } } setAttribute(attribute: T) { this._attribute = attribute; } // TODO: rawSet isConnected로 체크해서 들어가있는것만 처리되도록 또는 이미들어가있는것도 뺴야될것같은데.. 적용하긴했음..지켜봐야될듯. onCreatedThisChild(child: any, data: OnCreateRenderDataParams) { // console.log('onCreatedThisChild------------------------',child, data, data.render?.rawSet?.isConnected) const n = [...this._children]; n.push({instance:child, data}); this._children = n.filter(it=>it.data.render?.rawSet?.isConnected) this.childrenSetSubject.next(this._children); // Update decorators when children change (DOM might have changed) // Only update if elements actually changed if (this._rawSet) { this._updateQueryDecorators(this._rawSet); this._updateEventDecorators(this._rawSet); } } onCreatedThisChildDebounce(childrenSet: ChildrenSet[]) { } onRawSetRenderedDebounce(childrenSet: OnRawSetRenderedOtherData[]) { } onChildRawSetRenderedDebounce(){ } onCreateRenderData(data: OnCreateRenderDataParams): void { this._render = data.render; if (data.render?.rawSet) { this.setRawSet(data.render.rawSet); } if (this._config?.onlyParentType) { const isOk = Array.isArray(this._config?.onlyParentType) ? this._config?.onlyParentType : [this._config?.onlyParentType].some(it => data.render?.parentThis instanceof it); if (!isOk) { throw new Error('only my parentThis ' + this._config?.onlyParentType); } } } onChangeAttrRender(name: string, value: any, other: OtherData): void { if (other?.rawSet) { this.setRawSet(other.rawSet); } const attributes = ReflectUtils.getMetadata(ATTRIBUTE_METADATA_KEY, this.constructor); if (attributes && Array.isArray(attributes)) { for (const attrInfo of attributes) { if (this.equalsAttributeName(name, attrInfo.name as any)) { const finalValue = attrInfo.converter ? attrInfo.converter(value) : value; if (attrInfo.isMethod) { const method = (this as any)[attrInfo.propertyKey]; if (typeof method === 'function') { method.call(this, finalValue); } } else { (this as any)[attrInfo.propertyKey] = finalValue; } } } } } async onInitRender(param: any, rawSet: RawSet) { // console.log('onInitRender ComponentBase', this.constructor.name, rawSet?.uuid); const window = rawSet.dataSet?.config?.window; this.createChildrenDebounceSubscription = this.childrenSetSubject.pipe( MessageOperator.debounceTime(this.componentConfig?.createChildrenDebounce??1, { setTimeout: window ? window.setTimeout.bind(window) : undefined, clearTimeout: window ? window.clearTimeout.bind(window) : undefined, }) ).subscribe(it => { // console.log('call onCreatedThisChildDebounce', it); this.onCreatedThisChildDebounce(it); }); this.onRawSetRenderedOtherDataSubjectSubscription = this.onRawSetRenderedOtherDataSubject.pipe( MessageOperator.bufferTime(this.componentConfig?.onRawSetRenderedOtherDataDebounce??1, { skipEmpty: true, setInterval: window ? window.setInterval.bind(window) : undefined, clearInterval: window ? window.clearInterval.bind(window) : undefined, setTimeout: window ? window.setTimeout.bind(window) : undefined, clearTimeout: window ? window.clearTimeout.bind(window) : undefined, }) ).subscribe(it => { this.onRawSetRenderedDebounce(it); }); this.onChildRawSetRenderedOtherDataSubjectSubscription = this.onChildRawSetRenderedOtherDataSubject.pipe( MessageOperator.debounceTime(this.componentConfig?.onChildRawSetRenderedOtherDataDebounce??10, { setTimeout: window ? window.setTimeout.bind(window) : undefined, clearTimeout: window ? window.clearTimeout.bind(window) : undefined, }) ).subscribe(it => { this.onChildRawSetRenderedDebounce(); }); if (rawSet) { this.setRawSet(rawSet); const e = this.getElement(); if (e) { (e as any).component = this; } // Setup decorators this._setupQueryDecorators(rawSet); this._setupEventDecorators(rawSet); } } onDrThisBind() { // Logic moved to onInitRender for robustness } onDrThisUnBind() { this.onDestroy(); } onDestroyRender(data?: OnDestroyRenderParams): void { this.onDestroy(); } onDestroy() { this._cleanupEventListeners(); this._cleanupQueryBindings(); this._cleanupEventBindings(); this._clearAllRefreshTimers(); this.createChildrenDebounceSubscription?.unsubscribe(); this.onRawSetRenderedOtherDataSubjectSubscription?.unsubscribe(); this.onChildRawSetRenderedOtherDataSubjectSubscription?.unsubscribe(); // console.log('ComponentBase onDestroy', this.constructor.name, this.onRawSetRenderedOtherDataSubjectSubscription.closed); } }