1- import { Inject , Injectable , NgZone , Optional , Renderer2 , RendererFactory2 , RendererStyleFlags2 , RendererType2 , ViewEncapsulation } from '@angular/core' ;
1+ import { Inject , Injectable , Injector , NgZone , Optional , Renderer2 , RendererFactory2 , RendererStyleFlags2 , RendererType2 , ViewEncapsulation , inject , runInInjectionContext } from '@angular/core' ;
22import { addTaggedAdditionalCSS , Application , ContentView , Device , getViewById , Observable , profile , Utils , View } from '@nativescript/core' ;
33import { getViewClass , isKnownView } from './element-registry' ;
44import { getFirstNativeLikeView , NgView , TextNode } from './views' ;
55
66import { NamespaceFilter , NAMESPACE_FILTERS } from './property-filter' ;
7- import { APP_ROOT_VIEW , ENABLE_REUSABE_VIEWS , NATIVESCRIPT_ROOT_MODULE_ID } from './tokens' ;
7+ import { APP_ROOT_VIEW , ENABLE_REUSABE_VIEWS , NATIVESCRIPT_ROOT_MODULE_ID , PREVENT_SPECIFIC_EVENTS_DURING_CD } from './tokens' ;
88import { NativeScriptDebug } from './trace' ;
99import { ViewUtil } from './view-util' ;
1010
@@ -34,17 +34,68 @@ function inRootZone() {
3434 } ;
3535}
3636
37+ @Injectable ( {
38+ providedIn : 'root' ,
39+ } )
40+ export class NativeScriptRendererHelperService {
41+ private _executingDomChanges = 0 ;
42+ get executingDomChanges ( ) {
43+ return this . _executingDomChanges ;
44+ }
45+ get isExecutingDomChanges ( ) {
46+ return this . _executingDomChanges > 0 ;
47+ }
48+ beginDomChanges ( ) {
49+ this . _executingDomChanges ++ ;
50+ }
51+ endDomChanges ( ) {
52+ this . _executingDomChanges -- ;
53+ }
54+ executeDomChange < T > ( fn : ( ) => T ) : T {
55+ try {
56+ this . beginDomChanges ( ) ;
57+ return fn ( ) ;
58+ } finally {
59+ this . endDomChanges ( ) ;
60+ }
61+ }
62+ }
63+
64+ function modifiesDom ( ) {
65+ return function (
66+ target : {
67+ _rendererHelper : NativeScriptRendererHelperService ;
68+ } ,
69+ key : string | symbol ,
70+ descriptor : PropertyDescriptor ,
71+ ) {
72+ const childFunction = descriptor . value ;
73+ descriptor . value = function ( ...args : unknown [ ] ) {
74+ const fn = childFunction . bind ( this ) ;
75+ return this . _rendererHelper . executeDomChange ( ( ) => fn ( ...args ) ) ;
76+ } ;
77+ return descriptor ;
78+ } ;
79+ }
80+
3781export class NativeScriptRendererFactory implements RendererFactory2 {
3882 private componentRenderers = new Map < string , Renderer2 > ( ) ;
3983 private defaultRenderer : Renderer2 ;
4084 // backwards compatibility with RadListView
85+ private rootView = inject ( APP_ROOT_VIEW ) ;
86+ private namespaceFilters = inject ( NAMESPACE_FILTERS ) ;
87+ private rootModuleID = inject ( NATIVESCRIPT_ROOT_MODULE_ID ) ;
88+ private reuseViews = inject ( ENABLE_REUSABE_VIEWS , {
89+ optional : true ,
90+ } ) ;
91+ private injector = inject ( Injector ) ;
4192 private viewUtil = new ViewUtil ( this . namespaceFilters , this . reuseViews ) ;
4293
43- constructor ( @ Inject ( APP_ROOT_VIEW ) private rootView : View , @ Inject ( NAMESPACE_FILTERS ) private namespaceFilters : NamespaceFilter [ ] , @ Inject ( NATIVESCRIPT_ROOT_MODULE_ID ) private rootModuleID : string | number , @ Optional ( ) @ Inject ( ENABLE_REUSABE_VIEWS ) private reuseViews ) {
94+ constructor ( ) {
4495 if ( typeof this . reuseViews !== 'boolean' ) {
4596 this . reuseViews = false ; // default to false
4697 }
47- this . defaultRenderer = new NativeScriptRenderer ( rootView , namespaceFilters , this . reuseViews ) ;
98+ this . defaultRenderer = new NativeScriptRenderer ( this . rootView ) ;
4899 }
49100 createRenderer ( hostElement : any , type : RendererType2 ) : Renderer2 {
50101 if ( NativeScriptDebug . enabled ) {
@@ -77,7 +128,9 @@ export class NativeScriptRendererFactory implements RendererFactory2 {
77128 type . styles . map ( ( s ) => s . toString ( ) ) . forEach ( ( v ) => addStyleToCss ( v , this . rootModuleID ) ) ;
78129 renderer = this . defaultRenderer ;
79130 } else {
80- renderer = new EmulatedRenderer ( type , hostElement , this . namespaceFilters , this . rootModuleID , this . reuseViews ) ;
131+ runInInjectionContext ( this . injector , ( ) => {
132+ renderer = new EmulatedRenderer ( type , hostElement ) ;
133+ } ) ;
81134 ( < EmulatedRenderer > renderer ) . applyToHost ( hostElement ) ;
82135 }
83136
@@ -126,9 +179,23 @@ export class NativeScriptRendererFactory implements RendererFactory2 {
126179}
127180
128181class NativeScriptRenderer implements Renderer2 {
182+ private namespaceFilters = inject ( NAMESPACE_FILTERS ) ;
183+ private reuseViews = inject ( ENABLE_REUSABE_VIEWS , {
184+ optional : true ,
185+ } ) ;
129186 private viewUtil = new ViewUtil ( this . namespaceFilters , this . reuseViews ) ;
187+ _rendererHelper = inject ( NativeScriptRendererHelperService ) ;
188+ private specificPreventedEvents = new Set (
189+ inject ( PREVENT_SPECIFIC_EVENTS_DURING_CD , {
190+ optional : true ,
191+ } ) ?? [ ] ,
192+ ) ;
193+ private preventChangeEvents =
194+ inject ( PREVENT_SPECIFIC_EVENTS_DURING_CD , {
195+ optional : true ,
196+ } ) ?? false ;
130197
131- constructor ( private rootView : View , private namespaceFilters ?: NamespaceFilter [ ] , private reuseViews ?: boolean ) { }
198+ constructor ( private rootView : View ) { }
132199 get data ( ) : { [ key : string ] : any } {
133200 throw new Error ( 'Method not implemented.' ) ;
134201 }
@@ -138,6 +205,7 @@ class NativeScriptRenderer implements Renderer2 {
138205 }
139206 }
140207 @inRootZone ( )
208+ @modifiesDom ( )
141209 createElement ( name : string , namespace ?: string ) {
142210 if ( NativeScriptDebug . enabled ) {
143211 NativeScriptDebug . rendererLog ( `NativeScriptRenderer.createElement: ${ name } ` ) ;
@@ -154,13 +222,15 @@ class NativeScriptRenderer implements Renderer2 {
154222 return view ;
155223 }
156224 @inRootZone ( )
225+ @modifiesDom ( )
157226 createComment ( value : string ) {
158227 if ( NativeScriptDebug . enabled ) {
159228 NativeScriptDebug . rendererLog ( `NativeScriptRenderer.createComment ${ value } ` ) ;
160229 }
161230 return this . viewUtil . createComment ( value ) ;
162231 }
163232 @inRootZone ( )
233+ @modifiesDom ( )
164234 createText ( value : string ) {
165235 if ( NativeScriptDebug . enabled ) {
166236 NativeScriptDebug . rendererLog ( `NativeScriptRenderer.createText ${ value } ` ) ;
@@ -177,20 +247,23 @@ class NativeScriptRenderer implements Renderer2 {
177247 }
178248 } ) ;
179249 @inRootZone ( )
250+ @modifiesDom ( )
180251 appendChild ( parent : View , newChild : View ) : void {
181252 if ( NativeScriptDebug . enabled ) {
182253 NativeScriptDebug . rendererLog ( `NativeScriptRenderer.appendChild child: ${ newChild } parent: ${ parent } ` ) ;
183254 }
184255 this . viewUtil . appendChild ( parent , newChild ) ;
185256 }
186257 @inRootZone ( )
258+ @modifiesDom ( )
187259 insertBefore ( parent : any , newChild : any , refChild : any ) : void {
188260 if ( NativeScriptDebug . enabled ) {
189261 NativeScriptDebug . rendererLog ( `NativeScriptRenderer.insertBefore child: ${ newChild } ` + `parent: ${ parent } refChild: ${ refChild } ` ) ;
190262 }
191263 this . viewUtil . insertBefore ( parent , newChild , refChild ) ;
192264 }
193265 @inRootZone ( )
266+ @modifiesDom ( )
194267 removeChild ( parent : any , oldChild : any , isHostElement ?: boolean ) : void {
195268 if ( NativeScriptDebug . enabled ) {
196269 NativeScriptDebug . rendererLog ( `NativeScriptRenderer.removeChild child: ${ oldChild } parent: ${ parent } ` ) ;
@@ -231,6 +304,7 @@ class NativeScriptRenderer implements Renderer2 {
231304 return node . nextSibling ;
232305 }
233306 @inRootZone ( )
307+ @modifiesDom ( )
234308 setAttribute ( el : any , name : string , value : string , namespace ?: string ) : void {
235309 if ( NativeScriptDebug . enabled ) {
236310 NativeScriptDebug . rendererLog ( `NativeScriptRenderer.setAttribute ${ namespace ? namespace + ':' : '' } ${ el } .${ name } = ${ value } ` ) ;
@@ -243,40 +317,47 @@ class NativeScriptRenderer implements Renderer2 {
243317 }
244318 }
245319 @inRootZone ( )
320+ @modifiesDom ( )
246321 addClass ( el : any , name : string ) : void {
247322 if ( NativeScriptDebug . enabled ) {
248323 NativeScriptDebug . rendererLog ( `NativeScriptRenderer.addClass ${ name } ` ) ;
249324 }
250325 this . viewUtil . addClass ( el , name ) ;
251326 }
252327 @inRootZone ( )
328+ @modifiesDom ( )
253329 removeClass ( el : any , name : string ) : void {
254330 if ( NativeScriptDebug . enabled ) {
255331 NativeScriptDebug . rendererLog ( `NativeScriptRenderer.removeClass ${ name } ` ) ;
256332 }
257333 this . viewUtil . removeClass ( el , name ) ;
258334 }
259335 @inRootZone ( )
336+ @modifiesDom ( )
260337 setStyle ( el : any , style : string , value : any , flags ?: RendererStyleFlags2 ) : void {
261338 if ( NativeScriptDebug . enabled ) {
262339 NativeScriptDebug . rendererLog ( `NativeScriptRenderer.setStyle: ${ el } , ${ style } = ${ value } ` ) ;
263340 }
264341 this . viewUtil . setStyle ( el , style , value ) ;
265342 }
266343 @inRootZone ( )
344+ @modifiesDom ( )
267345 removeStyle ( el : any , style : string , flags ?: RendererStyleFlags2 ) : void {
268346 if ( NativeScriptDebug . enabled ) {
269347 NativeScriptDebug . rendererLog ( 'NativeScriptRenderer.removeStyle: ${styleName}' ) ;
270348 }
271349 this . viewUtil . removeStyle ( el , style ) ;
272350 }
273351 @inRootZone ( )
352+ @modifiesDom ( )
274353 setProperty ( el : any , name : string , value : any ) : void {
275354 if ( NativeScriptDebug . enabled ) {
276355 NativeScriptDebug . rendererLog ( `NativeScriptRenderer.setProperty ${ el } .${ name } = ${ value } ` ) ;
277356 }
278357 this . viewUtil . setProperty ( el , name , value ) ;
279358 }
359+ @inRootZone ( )
360+ @modifiesDom ( )
280361 setValue ( node : any , value : string ) : void {
281362 if ( NativeScriptDebug . enabled ) {
282363 NativeScriptDebug . rendererLog ( `NativeScriptRenderer.setValue renderNode: ${ node } , value: ${ value } ` ) ;
@@ -291,17 +372,26 @@ class NativeScriptRenderer implements Renderer2 {
291372 if ( NativeScriptDebug . enabled ) {
292373 NativeScriptDebug . rendererLog ( `NativeScriptRenderer.listen: ${ eventName } ` ) ;
293374 }
294- target . on ( eventName , callback ) ;
375+ let modifiedCallback = callback ;
376+ if ( ( this . preventChangeEvents && eventName . endsWith ( 'Change' ) ) || this . specificPreventedEvents . has ( eventName ) ) {
377+ modifiedCallback = ( ...args ) => {
378+ if ( this . _rendererHelper . isExecutingDomChanges ) {
379+ return ;
380+ }
381+ return callback ( ...args ) ;
382+ } ;
383+ }
384+ target . on ( eventName , modifiedCallback ) ;
295385 if ( eventName === View . loadedEvent && target . isLoaded ) {
296386 // we must create a new obervable here to ensure that the event goes through whatever zone patches are applied
297387 const obs = new Observable ( ) ;
298- obs . once ( eventName , callback ) ;
388+ obs . once ( eventName , modifiedCallback ) ;
299389 obs . notify ( {
300390 eventName,
301391 object : target ,
302392 } ) ;
303393 }
304- return ( ) => target . off ( eventName , callback ) ;
394+ return ( ) => target . off ( eventName , modifiedCallback ) ;
305395 }
306396}
307397
@@ -328,9 +418,10 @@ const addScopedStyleToCss = profile(`"renderer".addScopedStyleToCss`, function a
328418export class EmulatedRenderer extends NativeScriptRenderer {
329419 private contentAttr : string ;
330420 private hostAttr : string ;
421+ private rootModuleId = inject ( NATIVESCRIPT_ROOT_MODULE_ID ) ;
331422
332- constructor ( component : RendererType2 , rootView : View , namespaceFilters : NamespaceFilter [ ] , private rootModuleId : string | number , reuseViews : boolean ) {
333- super ( rootView , namespaceFilters , reuseViews ) ;
423+ constructor ( component : RendererType2 , rootView : View ) {
424+ super ( rootView ) ;
334425
335426 const componentId = component . id . replace ( ATTR_SANITIZER , '_' ) ;
336427 this . contentAttr = replaceNgAttribute ( CONTENT_ATTR , componentId ) ;
@@ -357,6 +448,8 @@ export class EmulatedRenderer extends NativeScriptRenderer {
357448 }
358449
359450 @profile
451+ @inRootZone ( )
452+ @modifiesDom ( )
360453 private addStyles ( styles : ( string | any [ ] ) [ ] , componentId : string ) {
361454 styles
362455 . map ( ( s ) => s . toString ( ) )
0 commit comments