diff --git a/projects/igniteui-angular-elements/src/app/custom-strategy.ts b/projects/igniteui-angular-elements/src/app/custom-strategy.ts index 0784e3abf83..ecb64e7eea5 100644 --- a/projects/igniteui-angular-elements/src/app/custom-strategy.ts +++ b/projects/igniteui-angular-elements/src/app/custom-strategy.ts @@ -1,4 +1,4 @@ -import { ApplicationRef, ComponentFactory, ComponentRef, DestroyRef, EventEmitter, Injector, QueryList, Type, ViewContainerRef, reflectComponentType } from '@angular/core'; +import { ComponentFactory, ComponentRef, DestroyRef, EventEmitter, Injector, QueryList, Type, ViewContainerRef, reflectComponentType } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { NgElement, NgElementStrategyEvent } from '@angular/elements'; import { fromEvent, Observable } from 'rxjs'; @@ -20,9 +20,6 @@ export abstract class IgcNgElement extends NgElement { * Custom Ignite UI for Angular Elements strategy */ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { - - // public override componentRef: ComponentRef|null = null; - protected element: IgcNgElement; /** The parent _component_'s element (a.k.a the semantic parent, rather than the DOM one after projection) */ protected parentElement?: WeakRef; @@ -46,8 +43,7 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { private _templateWrapperRef: ComponentRef; protected get templateWrapper(): TemplateWrapperComponent { if (!this._templateWrapperRef) { - const componentRef = (this as any).componentRef as ComponentRef; - const viewRef = componentRef.injector.get(ViewContainerRef); + const viewRef = this._componentRef.injector.get(ViewContainerRef); this._templateWrapperRef = viewRef.createComponent(TemplateWrapperComponent); } return this._templateWrapperRef.instance; @@ -61,13 +57,49 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { return this._configSelectors; } + //#region private props accessors + // D.P. Explicitly type assert with T[K] so base changes affect these accessor, otherwise + // indexed access will fallback to `any` for `this['nonExisting']` and thus fail silently + + /** Internal accessor for private {@link componentRef} */ + private get _componentRef() { + return this['componentRef'] as ComponentNgElementStrategy['componentRef']; + } + private set _componentRef(value) { + this['componentRef'] = value; + } + + /** Internal accessor for private {@link injector} */ + private get _injector() { + return this['injector'] as ComponentNgElementStrategy['injector']; + } + private set _injector(value) { + this['injector'] = value; + } + + /** Internal accessor for private {@link appRef} */ + private get _appRef() { + return this['appRef'] as ComponentNgElementStrategy['appRef']; + } + + /** Internal accessor for private {@link eventEmitters} */ + private get _initialInputValues() { + return this['initialInputValues'] as ComponentNgElementStrategy['initialInputValues']; + } + + /** Internal accessor for private {@link eventEmitters} */ + private get _eventEmitters() { + return this['eventEmitters'] as ComponentNgElementStrategy['eventEmitters']; + } + //#endregion private props accessors + constructor( private _componentFactory: ComponentFactory, - private _injector: Injector, + injector: Injector, private _inputMap: Map, private config: ComponentConfig[], ) { - super(_componentFactory, _injector, _inputMap); + super(_componentFactory, injector, _inputMap); } protected override async initializeComponent(element: HTMLElement) { @@ -81,7 +113,7 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { // set componentRef to non-null to prevent DOM moves from re-initializing // TODO: Fail handling or cancellation needed? - (this as any).componentRef = {}; + this._componentRef = {} as any; // const toBeOrphanedChildren = Array.from(element.children).filter(x => !this._componentFactory.ngContentSelectors.some(sel => x.matches(sel))); // for (const iterator of toBeOrphanedChildren) { @@ -138,33 +170,33 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { } // need to be able to reset the injector (as protected) to the parent's to call super normally - (this as any).injector = parentInjector || this._injector; + this._injector = parentInjector || this._injector; // super.initializeComponent(element); /** * Modified copy of super.initializeComponent: */ - const childInjector = Injector.create({ providers: [], parent: (this as any).injector }); + const childInjector = Injector.create({ providers: [], parent: this._injector }); const projectableNodes = extractProjectableNodes( element, this._componentFactory.ngContentSelectors, ); - (this as any).componentRef = this._componentFactory.create(childInjector, projectableNodes, element); - this.setComponentRef((this as any).componentRef); + this._componentRef = this._componentFactory.create(childInjector, projectableNodes, element); + this.setComponentRef(this._componentRef); //we need a name ref on the WC element to be copied down for the purposes of blazor. //alternatively we need to be able to hop back out to the WC element on demand. if (element) { - if ((this as any).componentRef.instance) { - (this as any).componentRef.instance.___wcElement = element; + if (this._componentRef.instance) { + this._componentRef.instance.___wcElement = element; } } this.initializeInputs(); - this.initializeOutputs((this as any).componentRef); + this.initializeOutputs(this._componentRef); // TODO(D.P.): Temporary maintain pre-check for ngAfterViewInit handling on _init flag w/ ngDoCheck interaction of row island - (this as any).componentRef.changeDetectorRef.detectChanges(); + this._componentRef.changeDetectorRef.detectChanges(); if (parentAnchor && parentInjector) { // attempt to attach the newly created ViewRef to the parents's instead of the App global @@ -172,21 +204,18 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { // preserve original position in DOM (in case of projection, e.g. grid pager): const domParent = element.parentElement; const nextSibling = element.nextSibling; - parentAnchor.insert((this as any).componentRef.hostView); //bad, moves in DOM, AND need to be in inner anchor :S + parentAnchor.insert(this._componentRef.hostView); //bad, moves in DOM, AND need to be in inner anchor :S //restore original DOM position domParent.insertBefore(element, nextSibling); - (this as any).componentRef.hostView.detectChanges(); + this._componentRef.hostView.detectChanges(); } else if (!parentAnchor) { - (this as any).appRef.attachView((this as any).componentRef.hostView); - (this as any).componentRef.hostView.detectChanges(); + this._appRef.attachView(this._componentRef.hostView); + this._componentRef.hostView.detectChanges(); } /** * End modified copy of super.initializeComponent */ - // componentRef should also likely be protected: - const componentRef = (this as any).componentRef as ComponentRef; - const parentQueries = this.getParentContentQueries(componentConfig, parents, configParents); for (const { parent, query } of parentQueries) { @@ -199,7 +228,7 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { } } else { const parentRef = await parent.ngElementStrategy[ComponentRefKey]; - parentRef.instance[query.property] = componentRef.instance; + parentRef.instance[query.property] = this._componentRef.instance; parentRef.changeDetectorRef.detectChanges(); } } @@ -209,7 +238,7 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { } // instead of duplicating super.disconnect() w/ the scheduled destroy: - componentRef.onDestroy(() => { + this._componentRef.onDestroy(() => { if (this._templateWrapperRef) { this._templateWrapperRef.destroy(); this._templateWrapperRef = null; @@ -228,21 +257,21 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { } public override setInputValue(property: string, value: any): void { - if ((this as any).componentRef === null || - !(this as any).componentRef.instance) { - (this as any).initialInputValues.set(property, value); + if (this._componentRef === null || !this._componentRef.instance) { + this._initialInputValues.set(property, value); return; } - const componentRef = (this as any).componentRef as ComponentRef; - const componentConfig = this.config?.find(x => x.component === this._componentFactory.componentType); - if (value === componentRef.instance[property]) { + if (value === this._componentRef.instance[property]) { return; } - if (componentRef && componentConfig?.templateProps?.includes(property)) { + + const componentConfig = this.config?.find(x => x.component === this._componentFactory.componentType); + + if (componentConfig?.templateProps?.includes(property)) { // const oldValue = this.getInputValue(property); if (this.templateWrapper.templateFunctions.includes(value)) return; - const oldRef = componentRef.instance[property]; + const oldRef = this._componentRef.instance[property]; const oldValue = oldRef && this.templateWrapper.getTemplateFunction(oldRef); if (oldValue === value) { return; @@ -251,18 +280,18 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { // TODO: discard oldValue // check template for any angular-element components - this.templateWrapper.templateRendered.pipe(takeUntilDestroyed(componentRef.injector.get(DestroyRef))).subscribe((element) => { + this.templateWrapper.templateRendered.pipe(takeUntilDestroyed(this._componentRef.injector.get(DestroyRef))).subscribe((element) => { element.querySelectorAll(this.configSelectors)?.forEach((c) => { // tie to angularParent lifecycle for cached scenarios like detailTemplate: - c.ngElementStrategy.angularParent = componentRef; + c.ngElementStrategy.angularParent = this._componentRef; }); }); } - if (componentRef && componentConfig?.boolProps?.includes(property)) { + if (componentConfig?.boolProps?.includes(property)) { // bool coerce: value = value != null && `${value}` !== 'false'; } - if (componentRef && componentConfig?.numericProps?.includes(property)) { + if (componentConfig?.numericProps?.includes(property)) { // number coerce: if (!isNaN(Number(value) - parseFloat(value))) { value = Number(value); @@ -281,8 +310,7 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { let returnValue = super.getInputValue(property); const componentConfig = this.config?.find(x => x.component === this._componentFactory.componentType); - const componentRef = (this as any).componentRef as ComponentRef; - if (componentRef && componentConfig?.templateProps?.includes(property)) { + if (this._componentRef && componentConfig?.templateProps?.includes(property)) { returnValue = this.templateWrapper.getTemplateFunction(returnValue) || returnValue; } @@ -307,8 +335,7 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { private updateQuery(queryName: string) { this.schedule.delete(queryName); - const componentRef = (this as any).componentRef as ComponentRef; - if (componentRef) { + if (this._componentRef) { const componentConfig = this.config?.find(x => x.component === this._componentFactory.componentType); const query = componentConfig.contentQueries.find(x => x.property === queryName); const children = this.runQueryInDOM(this.element, query); @@ -319,7 +346,7 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { // On the off chance one of many component is stuck initializing(async) and the update runs, don't want it to be stuck waiting for that too // Also the newly initialized comp will schedule an update again anyway // const childRef = await child.ngElementStrategy[ComponentRefKey]; - const childRef = (child.ngElementStrategy as any).componentRef as ComponentRef; + const childRef = child.ngElementStrategy._componentRef; if (childRef?.instance) { childRefs.push(childRef.instance); } @@ -327,7 +354,7 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { if (query.descendants && this.cachedChildComponents.has(queryName)) { childRefs = [...this.cachedChildComponents.get(queryName), ...childRefs]; } - const list = (this as any).componentRef.instance[query.property] as QueryList; + const list = this._componentRef.instance[query.property] as QueryList; list.reset(childRefs); list.notifyOnChanges(); } @@ -354,13 +381,13 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { private addToParentCache(parentElement: IgcNgElement, queryName: string) { const cachedComponents = parentElement.ngElementStrategy.cachedChildComponents.get(queryName) || []; - cachedComponents.push((this as any).componentRef.instance); + cachedComponents.push(this._componentRef.instance); parentElement.ngElementStrategy.cachedChildComponents.set(queryName, cachedComponents); } private removeFromParentCache(parentElement: IgcNgElement, queryName: string) { let cachedComponents = parentElement.ngElementStrategy.cachedChildComponents.get(queryName) || []; - cachedComponents = cachedComponents.filter(x => x !== (this as any).componentRef.instance); + cachedComponents = cachedComponents.filter(x => x !== this._componentRef.instance); parentElement.ngElementStrategy.cachedChildComponents.set(queryName, cachedComponents); } @@ -403,9 +430,8 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { */ private patchGridPopups() { const toggles = new Set(); - const componentRef = (this as any).componentRef as ComponentRef; - const scrollSub = fromEvent(this.element, 'gridScroll').pipe(takeUntil(componentRef.instance.destroy$)); + const scrollSub = fromEvent(this.element, 'gridScroll').pipe(takeUntil(this._componentRef.instance.destroy$)); scrollSub.subscribe(() => { // this.element.querySelectorAll>('igc-combo,igc-select').forEach(c => c.hide()); // this.element.dispatchEvent(new CustomEvent('scroll', { bubbles: false })); @@ -419,7 +445,7 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { } }); - fromEvent(this.element, 'igcOpened').pipe(takeUntil(componentRef.instance.destroy$)).subscribe((e: CustomEvent) => { + fromEvent(this.element, 'igcOpened').pipe(takeUntil(this._componentRef.instance.destroy$)).subscribe((e: CustomEvent) => { if (!Object.keys(e.detail).length) { // toggle directive-based components emit void details // TODO: need better flag @@ -447,7 +473,7 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy { }, ); - (this as any).eventEmitters.next(eventEmitters); + this._eventEmitters.next(eventEmitters); } protected patchOutputComponents(eventName: string, eventArgs: any) { diff --git a/projects/igniteui-angular-elements/src/app/wrapper/wrapper.component.ts b/projects/igniteui-angular-elements/src/app/wrapper/wrapper.component.ts index bf6fbbc5c0e..e40eea1d337 100644 --- a/projects/igniteui-angular-elements/src/app/wrapper/wrapper.component.ts +++ b/projects/igniteui-angular-elements/src/app/wrapper/wrapper.component.ts @@ -27,7 +27,7 @@ export class TemplateWrapperComponent { public templateRefs: QueryList>; constructor(private cdr: ChangeDetectorRef) { } - + protected litRender(container: HTMLElement, templateFunc: (arg: any) => TemplateResult, arg: any) { const part = render(templateFunc(arg), container);