1- import { ApplicationRef , ChangeDetectorRef , ComponentFactory , ComponentRef , DestroyRef , Injector , OnChanges , QueryList , Type , ViewContainerRef , reflectComponentType } from '@angular/core' ;
1+ import { ApplicationRef , ChangeDetectorRef , ComponentFactory , ComponentRef , DestroyRef , EventEmitter , Injector , OnChanges , QueryList , Type , ViewContainerRef , reflectComponentType } from '@angular/core' ;
22import { takeUntilDestroyed } from '@angular/core/rxjs-interop' ;
3- import { NgElement } from '@angular/elements' ;
4- import { fromEvent } from 'rxjs' ;
5- import { takeUntil } from 'rxjs/operators' ;
3+ import { NgElement , NgElementStrategyEvent } from '@angular/elements' ;
4+ import { fromEvent , Observable } from 'rxjs' ;
5+ import { map , takeUntil } from 'rxjs/operators' ;
66import { ComponentConfig , ContentQueryMeta } from './component-config' ;
77
88import { ComponentNgElementStrategy , ComponentNgElementStrategyFactory , extractProjectableNodes , isFunction } from './ng-element-strategy' ;
@@ -29,6 +29,8 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
2929 /** Cached child instances per query prop. Used for dynamic components's child templates that normally persist in Angular runtime */
3030 protected cachedChildComponents : Map < string , ComponentRef < any > [ ] > = new Map ( ) ;
3131 private setComponentRef : ( value : ComponentRef < any > ) => void ;
32+ /** The maximum depth at which event arguments are processed and angular components wrapped with Proxies, that handle template set */
33+ private maxEventProxyDepth = 3 ;
3234
3335 /**
3436 * Resolvable component reference.
@@ -405,6 +407,94 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
405407 super . disconnect ( ) ;
406408 }
407409 }
410+
411+ //#region Handle event args that return reference to components, since they return angular ref and not custom elements.
412+ /** Sets up listeners for the component's outputs so that the events stream emits the events. */
413+ protected override initializeOutputs ( componentRef : ComponentRef < any > ) : void {
414+ const eventEmitters : Observable < NgElementStrategyEvent > [ ] = this . _componentFactory . outputs . map (
415+ ( { propName, templateName } ) => {
416+ const emitter : EventEmitter < any > = componentRef . instance [ propName ] ;
417+ return emitter . pipe ( map ( ( value : any ) => ( { name : templateName , value : this . patchOutputComponents ( propName , value ) } ) ) ) ;
418+ } ,
419+ ) ;
420+
421+ ( this as any ) . eventEmitters . next ( eventEmitters ) ;
422+ }
423+
424+ protected patchOutputComponents ( eventName : string , eventArgs : any ) {
425+ // Single out only `columnInit` event for now. If more events pop up will require a config generation.
426+ if ( eventName !== "columnInit" ) {
427+ return eventArgs ;
428+ }
429+ return this . createProxyForComponentValue ( eventArgs , 1 ) . value ;
430+ }
431+
432+ /**
433+ * Nested search of event args that contain angular components and replace them with proxies.
434+ * If event args are array of angular component instances should return array of proxies of each of those instances.
435+ * If event args are object that has a single property being angular component should return same object except the angular component being a proxy of itself.
436+ */
437+ protected createProxyForComponentValue ( value : any , depth : number ) : { value : any , hasProxies : boolean } {
438+ if ( depth > this . maxEventProxyDepth ) {
439+ return { value, hasProxies : false } ;
440+ }
441+
442+ let hasProxies = false ;
443+ // TO DO!: Not very reliable as it is a very internal API and could be subject to change. If something comes up, should be changed.
444+ if ( value ?. __ngContext__ ) {
445+ const componentConfig = this . config . find ( ( info : ComponentConfig ) => value . constructor === info . component ) ;
446+ if ( componentConfig ?. templateProps ) {
447+ return { value : this . createElementsComponentProxy ( value , componentConfig ) , hasProxies : true } ;
448+ }
449+ } else if ( Array . isArray ( value ) ) {
450+ if ( ! value [ 0 ] ) {
451+ return { value, hasProxies : false } ;
452+ } else {
453+ // For array limit their parsing to first level and check if first item has created proxy inside.
454+ const firstItem = this . createProxyForComponentValue ( value [ 0 ] , this . maxEventProxyDepth ) ;
455+ if ( firstItem . hasProxies ) {
456+ const mappedArray = value . slice ( 1 , value . length ) . map ( item => this . createProxyForComponentValue ( item , depth + 1 ) ) ;
457+ mappedArray . unshift ( firstItem ) ;
458+ return { value : mappedArray , hasProxies : true } ;
459+ }
460+ }
461+ } else if ( typeof value === "object" && Object . entries ( value ) . length && ! ( value instanceof Event ) ) {
462+ for ( const [ key , item ] of Object . entries ( value ) ) {
463+ if ( ! item ) {
464+ value [ key ] = item ;
465+ } else {
466+ const parsedItem = this . createProxyForComponentValue ( item , depth + 1 ) ;
467+ value [ key ] = parsedItem . value ;
468+ hasProxies = parsedItem . hasProxies || hasProxies ;
469+ }
470+ }
471+ }
472+
473+ return { value, hasProxies } ;
474+ }
475+
476+ /** Create proxy for a component that handles setting template props, making sure it provides correct TemplateRef and not Lit template */
477+ protected createElementsComponentProxy ( component : any , config : ComponentConfig ) {
478+ const parentThis = this ;
479+ return new Proxy ( component , {
480+ set ( target : any , prop : string , newValue : any ) {
481+ // For now handle only template props
482+ if ( config . templateProps . includes ( prop ) ) {
483+ const oldRef = target [ prop ] ;
484+ const oldValue = oldRef && parentThis . templateWrapper . getTemplateFunction ( oldRef ) ;
485+ if ( oldValue === newValue ) {
486+ newValue = oldRef ;
487+ } else {
488+ newValue = parentThis . templateWrapper . addTemplate ( newValue ) ;
489+ }
490+ }
491+ target [ prop ] = newValue ;
492+
493+ return true ;
494+ }
495+ } ) ;
496+ }
497+ //#endregion
408498}
409499
410500/**
0 commit comments