1- import { ApplicationRef , ChangeDetectorRef , ComponentFactory , ComponentRef , Injector , OnChanges , QueryList , Type , ViewContainerRef , reflectComponentType } from '@angular/core' ;
2- import { NgElement } from '@angular/elements' ;
3- import { fromEvent } from 'rxjs' ;
4- import { takeUntil } from 'rxjs/operators' ;
1+ import { ApplicationRef , ChangeDetectorRef , ComponentFactory , ComponentRef , DestroyRef , EventEmitter , Injector , OnChanges , QueryList , Type , ViewContainerRef , reflectComponentType } from '@angular/core' ;
2+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop' ;
3+ import { NgElement , NgElementStrategyEvent } from '@angular/elements' ;
4+ import { fromEvent , Observable } from 'rxjs' ;
5+ import { map , takeUntil } from 'rxjs/operators' ;
56import { ComponentConfig , ContentQueryMeta } from './component-config' ;
67
78import { ComponentNgElementStrategy , ComponentNgElementStrategyFactory , extractProjectableNodes , isFunction } from './ng-element-strategy' ;
@@ -28,6 +29,8 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
2829 /** Cached child instances per query prop. Used for dynamic components's child templates that normally persist in Angular runtime */
2930 protected cachedChildComponents : Map < string , ComponentRef < any > [ ] > = new Map ( ) ;
3031 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 ;
3134
3235 /**
3336 * Resolvable component reference.
@@ -48,6 +51,14 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
4851 return this . _templateWrapper ;
4952 }
5053
54+ private _configSelectors : string ;
55+ public get configSelectors ( ) : string {
56+ if ( ! this . _configSelectors ) {
57+ this . _configSelectors = this . config . map ( x => x . selector ) . join ( ',' ) ;
58+ }
59+ return this . _configSelectors ;
60+ }
61+
5162 constructor ( private _componentFactory : ComponentFactory < any > , private _injector : Injector , private config : ComponentConfig [ ] ) {
5263 super ( _componentFactory , _injector ) ;
5364 }
@@ -233,6 +244,14 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
233244 }
234245 value = this . templateWrapper . addTemplate ( value ) ;
235246 // TODO: discard oldValue
247+
248+ // check template for any angular-element components
249+ this . templateWrapper . templateRendered . pipe ( takeUntilDestroyed ( componentRef . injector . get ( DestroyRef ) ) ) . subscribe ( ( element ) => {
250+ element . querySelectorAll < IgcNgElement > ( this . configSelectors ) ?. forEach ( ( c ) => {
251+ // tie to angularParent lifecycle for cached scenarios like detailTemplate:
252+ c . ngElementStrategy . angularParent = componentRef ;
253+ } ) ;
254+ } ) ;
236255 }
237256 if ( componentRef && componentConfig ?. boolProps ?. includes ( property ) ) {
238257 // bool coerce:
@@ -388,6 +407,94 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
388407 super . disconnect ( ) ;
389408 }
390409 }
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
391498}
392499
393500/**
0 commit comments