import React , { Component } from 'react' ; import { union } from 'lodash-es' ; import './polyfills' ; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' ; import { NgModuleRef , ApplicationRef , ComponentRef , SimpleChange , enableProdMode , PlatformRef , CompilerOptions , Type , NgZone , } from '@angular/core' ; import { Router , NavigationEnd } from '@angular/router' ; import { BootstrapOptions } from '@angular/core/src/application_ref' ; import { withRouter , RouteComponentProps } from 'react-router' ; import { Location , UnregisterCallback } from 'history' ; import { Subscription } from 'rxjs' ; import shallowEqual from './shallowEqual' ; export interface AngularModuleRendererProps { props ? : { } ; module : Type < any > ; selector : string ; compilerOptions ? : ( CompilerOptions & BootstrapOptions ) | ( CompilerOptions & BootstrapOptions ) [ ] ; } type Props = AngularModuleRendererProps & RouteComponentProps ; if ( process . env . ENV === 'production' ) { enableProdMode ( ) ; } export class AngularModuleRenderer extends Component < Props > { private platformRef : PlatformRef ; private moduleRef : NgModuleRef < any > ; private componentRef : ComponentRef < any > ; private nodeRef = React . createRef < HTMLElement > ( ) ; private routerSubscription : Subscription ; private historyListenerDisposer : UnregisterCallback ; private routerTimeout : NodeJS . Timer ; private historyTimeout : NodeJS . Timer ; shouldComponentUpdate ( nextProps ) { return ! shallowEqual ( this . props , nextProps ) || ! shallowEqual ( this . props . props , nextProps . props ) ; } componentDidUpdate ( prevProps ) { if ( ! shallowEqual ( prevProps . props , this . props ) ) { this . mergeProps ( prevProps . props ) ; } } componentDidMount ( ) { this . load ( ) ; } componentWillUnmount ( ) { this . routerTimeout && clearTimeout ( this . routerTimeout ) ; this . historyTimeout && clearTimeout ( this . historyTimeout ) ; if ( this . moduleRef ) { const node = this . nodeRef . current ! ; const { parentNode , nextSibling } = node ; this . moduleRef . destroy ( ) ; this . platformRef . destroy ( ) ; if ( parentNode ) { parentNode . insertBefore ( node , nextSibling ) ; } } if ( this . routerSubscription ) { this . routerSubscription . unsubscribe ( ) ; delete this . routerSubscription ; } if ( this . historyListenerDisposer ) { this . historyListenerDisposer ( ) ; delete this . historyListenerDisposer ; } } render ( ) { return React . createElement ( this . props . selector , { ref : this . nodeRef } ) ; } private mergeProps ( prevProps ? ) { const prevPropsKeys = prevProps ? Object . keys ( prevProps ) : null ; const propsKeys = this . props . props ? Object . keys ( this . props . props ) : null ; const allKeys = union ( prevPropsKeys , propsKeys ) ; if ( allKeys . length > 0 ) { let runDetectChanges = false ; const { instance } = this . componentRef ; const changes = instance . ngOnChanges ? { } : undefined ; allKeys . forEach ( ( key ) => { const prevValue = instance [ key ] ; const value = this . props . props ? ( this . props . props as { [ n ] : any ; } ) [ key ] : undefined ; if ( prevValue !== value ) { runDetectChanges = true ; if ( changes ) { changes [ key ] = new SimpleChange ( instance [ key ] , value , true ) ; } this . componentRef . instance [ key ] = value ; if ( changes ) { this . componentRef . instance . ngOnChanges ( changes ) ; } } } ) ; if ( runDetectChanges ) { this . componentRef . changeDetectorRef . detectChanges ( ) ; } } } private async load ( ) { const { module , compilerOptions } = this . props ; this . platformRef = platformBrowserDynamic ( ) ; this . moduleRef = await this . platformRef . bootstrapModule ( module , compilerOptions ) ; const router = this . moduleRef . injector . get ( Router ) ; if ( router ) { this . routerSubscription = router . events . subscribe ( ( event ) => { if ( event instanceof NavigationEnd ) { this . routerTimeout = setTimeout ( ( ) => { if ( this . props . history . createHref ( this . props . history . location ) !== router . url ) { const { pushState } = history ; history . pushState = ( ) => { } ; this . props . history . push ( router . url ) ; history . pushState = pushState ; } } , 1 ) ; } } ) ; this . historyListenerDisposer = this . props . history . listen ( ( location ) => { this . historyTimeout = setTimeout ( ( ) => { if ( this . props . history . createHref ( this . props . history . location ) !== router . url ) { const { pushState } = history ; history . pushState = ( ) => { } ; this . moduleRef . injector . get ( NgZone ) . run ( ( ) => { router . navigateByUrl ( this . props . history . createHref ( location ) ) ; } ) ; history . pushState = pushState ; } } , 1 ) ; } ) ; } [ this . componentRef ] = this . moduleRef . injector . get ( ApplicationRef ) . components ; if ( this . componentRef ) { this . mergeProps ( ) ; } } } export default withRouter ( AngularModuleRenderer ) ;