22
33import { Injectable , Renderer2 , RendererStyleFlags2 , RendererType2 } from '@angular/core' ;
44import { EventManager , ɵDomRendererFactory2 , ɵDomSharedStylesHost } from '@angular/platform-browser' ;
5+ import * as ReactDOM from 'react-dom' ;
56
67import { isReactNode , ReactNode } from './react-node' ;
78
@@ -25,7 +26,7 @@ export class AngularReactRendererFactory extends ɵDomRendererFactory2 {
2526 // renders are flushed.
2627 public setRenderPendingCallback = ( ) => {
2728 this . isRenderPending = true ;
28- }
29+ } ;
2930
3031 constructor ( eventManager : EventManager , sharedStylesHost : ɵDomSharedStylesHost ) {
3132 super ( eventManager , sharedStylesHost ) ;
@@ -41,13 +42,33 @@ export class AngularReactRendererFactory extends ɵDomRendererFactory2 {
4142 return super . createRenderer ( element , type ) ;
4243 }
4344
44- begin ( ) { }
45+ begin ( ) { }
4546
4647 end ( ) {
47- if ( DEBUG ) { console . log ( 'RootRenderer > end > isRenderPending:' , this . isRenderPending , 'reactRootNodes:' , this . reactRootNodes ) ; }
48+ if ( DEBUG ) {
49+ console . log (
50+ 'RootRenderer > end > isRenderPending:' ,
51+ this . isRenderPending ,
52+ 'reactRootNodes:' ,
53+ this . reactRootNodes
54+ ) ;
55+ }
4856 // Flush any pending React element render updates. This cannot be done
4957 // earlier (as is done for DOM elements) because React element props
5058 // are ReadOnly.
59+
60+ // Workaround for ReactNodes inside ReactContent being added to the root of the VDOM and not removed from the VDOM when unmounted from the DOM.
61+ for ( let i = 0 ; i < this . reactRootNodes . length ; i ++ ) {
62+ const node = this . reactRootNodes [ i ] ;
63+ if (
64+ ! isReactNode ( node . parent ) &&
65+ ! document . contains ( node . parent ) &&
66+ ReactDOM . unmountComponentAtNode ( node . parent )
67+ ) {
68+ this . reactRootNodes . splice ( i -- , 1 ) ;
69+ }
70+ }
71+
5172 if ( this . isRenderPending ) {
5273 // Remove root nodes that are pending destroy after render.
5374 this . reactRootNodes = this . reactRootNodes . filter ( node => ! node . render ( ) . destroyPending ) ;
@@ -59,27 +80,35 @@ export class AngularReactRendererFactory extends ɵDomRendererFactory2 {
5980class ReactRenderer implements Renderer2 {
6081 readonly data : { [ key : string ] : any } = Object . create ( null ) ;
6182
62- constructor ( private rootRenderer : AngularReactRendererFactory ) { }
83+ constructor ( private rootRenderer : AngularReactRendererFactory ) { }
6384
64- destroy ( ) : void { }
85+ destroy ( ) : void { }
6586
6687 destroyNode ( node : ReactNode ) : void {
67- if ( DEBUG ) { console . error ( 'Renderer > destroyNode > node:' , node . toString ( ) ) ; }
88+ if ( DEBUG ) {
89+ console . error ( 'Renderer > destroyNode > node:' , node . toString ( ) ) ;
90+ }
6891 node . destroyNode ( ) ;
6992 }
7093
7194 createElement ( name : string , namespace ?: string ) : ReactNode {
72- if ( DEBUG ) { console . error ( 'Renderer > createElement > name:' , name , namespace ? 'namespace:' : '' , namespace ) ; }
95+ if ( DEBUG ) {
96+ console . error ( 'Renderer > createElement > name:' , name , namespace ? 'namespace:' : '' , namespace ) ;
97+ }
7398 return new ReactNode ( name ) ;
7499 }
75100
76101 createComment ( value : string ) : ReactNode {
77- if ( DEBUG ) { console . error ( 'Renderer > createComment > value:' , value . trim ( ) ) ; }
102+ if ( DEBUG ) {
103+ console . error ( 'Renderer > createComment > value:' , value . trim ( ) ) ;
104+ }
78105 return new ReactNode ( ) . asComment ( value ) ;
79106 }
80107
81108 createText ( value : string ) : ReactNode {
82- if ( DEBUG ) { console . error ( 'Renderer > createText > value:' , value . trim ( ) ) ; }
109+ if ( DEBUG ) {
110+ console . error ( 'Renderer > createText > value:' , value . trim ( ) ) ;
111+ }
83112 return new ReactNode ( ) . asText ( value ) ;
84113 }
85114
@@ -97,14 +126,18 @@ class ReactRenderer implements Renderer2 {
97126 // Provide a parent element reference to the ReactNode. This will be used later
98127 // once the ReactNode is fully defined and it is subsequently rendered.
99128 if ( ! isReactNode ( parent ) ) {
100- if ( DEBUG ) { console . warn ( 'Renderer > appendChild > asDOM > parent:' , parent . toString ( ) , 'node:' , node . toString ( ) ) ; }
129+ if ( DEBUG ) {
130+ console . warn ( 'Renderer > appendChild > asDOM > parent:' , parent . toString ( ) , 'node:' , node . toString ( ) ) ;
131+ }
101132 node . setRenderPendingCallback = this . rootRenderer . setRenderPendingCallback ;
102133 this . rootRenderer . reactRootNodes . push ( node ) ;
103134 node . parent = parent ;
104135 return ;
105136 }
106137
107- if ( DEBUG ) { console . warn ( 'Renderer > appendChild > asReact > parent:' , parent . toString ( ) , 'node:' , node . toString ( ) ) ; }
138+ if ( DEBUG ) {
139+ console . warn ( 'Renderer > appendChild > asReact > parent:' , parent . toString ( ) , 'node:' , node . toString ( ) ) ;
140+ }
108141 node . setRenderPendingCallback = ( ) => parent . setRenderPending ( ) ;
109142 parent . addChild ( node ) ;
110143 node . parent = parent ;
@@ -120,7 +153,16 @@ class ReactRenderer implements Renderer2 {
120153 // once the ReactNode is fully defined and it is subsequently rendered. In this
121154 // case, React cannot "insertBefore". Instead, we have to create a target element
122155 // where the ReactNode can be rendered later.
123- if ( DEBUG ) { console . warn ( 'Renderer > insertBefore > asDOM > parent:' , parent . toString ( ) , 'node:' , node . toString ( ) , 'refChild:' , refChild . toString ( ) ) ; }
156+ if ( DEBUG ) {
157+ console . warn (
158+ 'Renderer > insertBefore > asDOM > parent:' ,
159+ parent . toString ( ) ,
160+ 'node:' ,
161+ node . toString ( ) ,
162+ 'refChild:' ,
163+ refChild . toString ( )
164+ ) ;
165+ }
124166 const target = document . createElement ( 'div' ) ;
125167 parent . insertBefore ( target , refChild ) ;
126168 node . parent = target ;
@@ -136,39 +178,71 @@ class ReactRenderer implements Renderer2 {
136178 // Remove a parent element reference from the ReactNode. This will be later
137179 // result in the ReactNode unloading itself.
138180 if ( ! isReactNode ( parent ) ) {
139- if ( DEBUG ) { console . warn ( 'Renderer > removeChild > asDOM > parent:' , parent . toString ( ) , 'node:' , node . toString ( ) ) ; }
181+ if ( DEBUG ) {
182+ console . warn ( 'Renderer > removeChild > asDOM > parent:' , parent . toString ( ) , 'node:' , node . toString ( ) ) ;
183+ }
140184 node . parent = null ;
141185 return ;
142186 }
143187
144- if ( DEBUG ) { console . warn ( 'Renderer > removeChild > asReact > parent:' , parent . toString ( ) , 'node:' , node . toString ( ) ) ; }
188+ if ( DEBUG ) {
189+ console . warn ( 'Renderer > removeChild > asReact > parent:' , parent . toString ( ) , 'node:' , node . toString ( ) ) ;
190+ }
145191 parent . removeChild ( node ) ;
146192 }
147193
148194 selectRootElement ( selectorOrNode : string | any ) : any {
149- if ( DEBUG ) { console . log ( 'NOT IMPLEMENTED - Renderer > selectRootElement > selectorOrNode:' , selectorOrNode ) ; }
195+ if ( DEBUG ) {
196+ console . log ( 'NOT IMPLEMENTED - Renderer > selectRootElement > selectorOrNode:' , selectorOrNode ) ;
197+ }
150198 }
151199
152200 parentNode ( node : ReactNode ) : any {
153- if ( DEBUG ) { console . log ( 'NOT IMPLEMENTED - Renderer > parentNode > node:' , node . toString ( ) ) ; }
201+ if ( DEBUG ) {
202+ console . log ( 'NOT IMPLEMENTED - Renderer > parentNode > node:' , node . toString ( ) ) ;
203+ }
154204 }
155205
156206 nextSibling ( node : any ) : any {
157- if ( DEBUG ) { console . log ( 'NOT IMPLEMENTED - Renderer > nextSibling > node:' , node . toString ( ) ) ; }
207+ if ( DEBUG ) {
208+ console . log ( 'NOT IMPLEMENTED - Renderer > nextSibling > node:' , node . toString ( ) ) ;
209+ }
158210 }
159211
160212 setAttribute ( node : ReactNode , name : string , value : string , namespace ?: string ) : void {
161- if ( DEBUG ) { console . log ( 'Renderer > setAttribute > node:' , node . toString ( ) , 'name:' , name , 'value:' , value , namespace ? 'namespace:' : '' , namespace ) ; }
213+ if ( DEBUG ) {
214+ console . log (
215+ 'Renderer > setAttribute > node:' ,
216+ node . toString ( ) ,
217+ 'name:' ,
218+ name ,
219+ 'value:' ,
220+ value ,
221+ namespace ? 'namespace:' : '' ,
222+ namespace
223+ ) ;
224+ }
162225 node . setProperty ( name , value ) ;
163226 }
164227
165228 removeAttribute ( node : ReactNode , name : string , namespace ?: string ) : void {
166- if ( DEBUG ) { console . log ( 'Renderer > removeAttribute > node:' , node . toString ( ) , 'name:' , name , namespace ? 'namespace:' : '' , namespace ) ; }
229+ if ( DEBUG ) {
230+ console . log (
231+ 'Renderer > removeAttribute > node:' ,
232+ node . toString ( ) ,
233+ 'name:' ,
234+ name ,
235+ namespace ? 'namespace:' : '' ,
236+ namespace
237+ ) ;
238+ }
167239 node . removeProperty ( name ) ;
168240 }
169241
170242 addClass ( node : ReactNode , name : string ) : void {
171- if ( DEBUG ) { console . log ( 'Renderer > addClass > node:' , node . toString ( ) , 'name:' , name ) ; }
243+ if ( DEBUG ) {
244+ console . log ( 'Renderer > addClass > node:' , node . toString ( ) , 'name:' , name ) ;
245+ }
172246
173247 // Update the virtual node.
174248 // TODO: This may only support a single class name, but might work if property name is a single
@@ -177,7 +251,9 @@ class ReactRenderer implements Renderer2 {
177251 }
178252
179253 removeClass ( node : ReactNode , name : string ) : void {
180- if ( DEBUG ) { console . log ( 'Renderer > removeClass > node:' , node . toString ( ) , 'name:' , name ) ; }
254+ if ( DEBUG ) {
255+ console . log ( 'Renderer > removeClass > node:' , node . toString ( ) , 'name:' , name ) ;
256+ }
181257
182258 // Update the virtual node.
183259 // TODO: This may not work correctly to remove a single name from a comma-delimited list.
@@ -194,31 +270,37 @@ class ReactRenderer implements Renderer2 {
194270 }
195271
196272 removeStyle ( node : ReactNode , style : string , flags : RendererStyleFlags2 ) : void {
197- if ( DEBUG ) { console . log ( 'Renderer > removeStyle > node:' , node . toString ( ) , 'style:' , style , 'flags:' , flags ) ; }
273+ if ( DEBUG ) {
274+ console . log ( 'Renderer > removeStyle > node:' , node . toString ( ) , 'style:' , style , 'flags:' , flags ) ;
275+ }
198276 node . removeProperty ( 'style' , style ) ;
199277 }
200278
201279 setProperty ( node : ReactNode , name : string , value : any ) : void {
202- if ( DEBUG ) { console . log ( 'Renderer > setProperty > node:' , node . toString ( ) , 'name:' , name , 'value:' , value ) ; }
280+ if ( DEBUG ) {
281+ console . log ( 'Renderer > setProperty > node:' , node . toString ( ) , 'name:' , name , 'value:' , value ) ;
282+ }
203283 node . setProperty ( name , value ) ;
204284 }
205285
206286 setValue ( node : ReactNode , value : string ) : void {
207- if ( DEBUG ) { console . log ( 'Renderer > setValue > node:' , node . toString ( ) , 'value:' , value ) ; }
287+ if ( DEBUG ) {
288+ console . log ( 'Renderer > setValue > node:' , node . toString ( ) , 'value:' , value ) ;
289+ }
208290 node . setProperty ( 'value' , value ) ;
209291 }
210292
211293 listen ( target : ReactNode , event : string , callback : ( event : any ) => boolean ) : ( ) => void {
212- if ( DEBUG ) { console . log ( 'Renderer > listen > target:' , target , 'event:' , event ) ; }
294+ if ( DEBUG ) {
295+ console . log ( 'Renderer > listen > target:' , target , 'event:' , event ) ;
296+ }
213297 target . setProperty ( event , callback ) ;
214298
215299 // NEEDS WORK: Implement prevent default callback behavior.
216300 // return <() => void>this.eventManager.addEventListener(
217301 // target, event, decoratePreventDefault(callback)) as() => void;
218302
219-
220303 // tslint:disable-next-line:no-unused-expression
221304 return ( ) => null ;
222305 }
223-
224306}
0 commit comments