import { AfterViewInit , ChangeDetectorRef , Component , ComponentFactoryResolver , ComponentRef , ElementRef , EventEmitter , Injector , Input , OnDestroy , OnInit , Type , ViewChild } from '@angular/core' ; import { RunService } from '@deja-vu/core' ; import * as _ from 'lodash' ; import { Subscription } from 'rxjs' ; import { AppComponentDefinition , ComponentInstance , ConceptComponentDefinition } from '../datatypes' ; import { DynamicComponentDirective } from '../dynamic-component.directive' ; import { ChildScopeIO , fullyQualifyName , ScopeIO } from '../io' ; @ Component ( { selector : 'app-component-instance' , templateUrl : './component-instance.component.html' , styleUrls : [ './component-instance.component.scss' ] } ) export class ComponentInstanceComponent implements OnInit , AfterViewInit , OnDestroy { @ ViewChild ( DynamicComponentDirective ) private readonly componentHost : DynamicComponentDirective ; @ ViewChild ( 'templateElem' ) private readonly templateElem : ElementRef ; @ Input ( ) componentInstance : ComponentInstance ; @ Input ( ) parentScopeIO : ScopeIO = new ScopeIO ( ) ; @ Input ( ) shouldReLink : EventEmitter < any > ; @ Input ( ) extraInputs ? : { [ ioName ] : string ; } ; scopeIO : ChildScopeIO ; private subscriptions : Subscription [ ] = [ ] ; hidden = false ; constructor ( private readonly componentFactoryResolver , private readonly elem , private readonly rs , private readonly injector , public readonly ref ) { } ngOnInit ( ) { let extraScopeIO ; if ( this . extraInputs ) { extraScopeIO = new ScopeIO ( ) ; _ . forOwn ( this . extraInputs , ( thisProp , ioName ) => { extraScopeIO . getSubject ( ioName ) . next ( this [ thisProp ] ) ; } ) ; } this . scopeIO = new ChildScopeIO ( this . componentInstance , this . parentScopeIO , this . shouldReLink , extraScopeIO ? { scope : extraScopeIO , inputs : Object . keys ( this . extraInputs ) } : undefined ) ; this . scopeIO . link ( ) ; if ( this . shouldReLink ) { this . shouldReLink . subscribe ( ( ) => this . scopeIO . link ( ) ) ; } this . registerRunService ( ) ; } ngAfterViewInit ( ) { this . loadContent ( ) ; } ngOnDestroy ( ) { this . subscriptions . forEach ( ( s ) => s . unsubscribe ( ) ) ; this . scopeIO . unlink ( ) ; } shouldPassComponentInstance ( ) { return this . componentInstance . isText ; } /**
     * To be called OnInit
     * Should only be called once
     */ private registerRunService ( ) { if ( this . componentInstance && this . componentInstance . isAppComponent ) { this . rs . registerAppComponent ( this . elem , { } ) ; } } /**
     * To be called AfterViewInit
     * Should only be called once
     */ private loadContent ( ) { if ( ! ( this . componentInstance . of instanceof AppComponentDefinition ) ) { setTimeout ( ( ) => this . loadConceptComponent ( ) ) ; } setTimeout ( ( ) => { const sub = this . scopeIO . getSubject ( 'hidden' ) . subscribe ( ( value ) => { this . hidden = value ; } ) ; this . subscriptions . push ( sub ) ; } ) ; } /**
     * To be called in a new thread AfterViewInit
     */ private loadConceptComponent ( ) { const componentDefinition = < ConceptComponentDefinition > this . componentInstance . of ; const { component } = componentDefinition ; const componentFactory = this . componentFactoryResolver . resolveComponentFactory ( < Type < { } > > component ) ; const viewContainerRef = this . componentHost . viewContainerRef ; viewContainerRef . clear ( ) ; let componentRef = viewContainerRef . createComponent ( componentFactory , 0 , this . injector ) ; if ( ! this . extraInputs ) { componentDefinition . outputs . forEach ( ( output ) => { const sub = ( < EventEmitter < any > > componentRef . instance [ output ] ) . subscribe ( ( val ) => { const fqName = fullyQualifyName ( output , this . componentInstance ) ; this . parentScopeIO . getSubject ( fqName ) . next ( val ) ; } ) ; this . subscriptions . push ( sub ) ; } ) ; } const defaults = { } ; componentDefinition . inputs . forEach ( ( input ) => { defaults [ input ] = componentRef . instance [ input ] ; const fromSubject = this . scopeIO . getSubject ( input ) ; const sub = fromSubject . subscribe ( ( val ) => { if ( val === undefined ) { val = defaults [ input ] ; } if ( input === '*content' ) { if ( val ) { let childComponentRef ; const childComponentFactory = this . componentFactoryResolver . resolveComponentFactory < ComponentInstanceComponent > ( val . type ) ; childComponentRef = childComponentFactory . create ( this . injector ) ; viewContainerRef . clear ( ) ; componentRef = viewContainerRef . createComponent ( componentFactory , 0 , this . injector , [ [ childComponentRef . location . nativeElement ] ] ) ; const instance = childComponentRef . instance ; instance . componentInstance = val . inputs . componentInstance ; instance . parentScopeIO = val . inputs . parentScopeIO ; instance . ref . detectChanges ( ) ; instance . ngOnInit ( ) ; instance . ngAfterViewInit ( ) ; } else { viewContainerRef . clear ( ) ; componentRef = viewContainerRef . createComponent ( componentFactory , 0 , this . injector ) ; } } else { if ( ( ! componentDefinition . componentInputs [ input ] && componentRef . instance [ input ] !== val ) || ( componentDefinition . componentInputs [ input ] && _ . get ( val , [ 'inputs' , 'componentInstance' ] ) !== _ . get ( componentRef . instance [ input ] , [ 'inputs' , 'componentInstance' ] ) ) ) { componentRef . instance [ input ] = val ; } } } ) ; this . subscriptions . push ( sub ) ; } ) ; if ( this . shouldPassComponentInstance ( ) ) { componentRef . instance [ 'componentInstance' ] = this . componentInstance ; } this . ref . detectChanges ( ) ; if ( this . templateElem . nativeElement && ! _ . isEmpty ( this . templateElem . nativeElement . children ) ) { this . templateElem . nativeElement . children [ 0 ] . setAttribute ( 'dvOf' , this . componentInstance . from . name ) ; } } }