@@ -58,6 +58,28 @@ type ForeignListenerRef = {
5858 _id : number ;
5959} ;
6060
61+ type ListenerScheduleFilter = ( listeners : Listener [ ] ) => Listener [ ] ;
62+
63+ type ScheduledListenerTask = {
64+ _kind : "listener" ;
65+ _listeners : Listener [ ] ;
66+ _target : Scope | ScopeProxy | undefined ;
67+ _filter ?: ListenerScheduleFilter ;
68+ } ;
69+
70+ type ScheduledCallbackTask = {
71+ _kind : "callback" ;
72+ _callback : ( ) => void ;
73+ } ;
74+
75+ type ScheduledTask = ScheduledListenerTask | ScheduledCallbackTask ;
76+
77+ type ListenerSchedulerState = {
78+ _queue : ScheduledTask [ ] ;
79+ _queued : boolean ;
80+ _flushing : boolean ;
81+ } ;
82+
6183export interface ScopeEvent {
6284 targetScope : typeof Proxy < ng . Scope > ;
6385 currentScope : typeof Proxy < ng . Scope > | null ;
@@ -542,6 +564,9 @@ export class Scope {
542564 /** @internal */
543565 _ownedForeignListeners : ForeignListenerRef [ ] ;
544566
567+ /** @internal */
568+ _listenerScheduler : ListenerSchedulerState ;
569+
545570 /**
546571 * Initializes the handler with the target object and a context.
547572 *
@@ -579,6 +604,12 @@ export class Scope {
579604
580605 this . _ownedForeignListeners = [ ] ;
581606
607+ this . _listenerScheduler = context ?. _listenerScheduler ?? {
608+ _queue : [ ] ,
609+ _queued : false ,
610+ _flushing : false ,
611+ } ;
612+
582613 this . _propertyMap = {
583614 $apply : this . $apply . bind ( this ) ,
584615 $broadcast : this . $broadcast . bind ( this ) ,
@@ -1148,19 +1179,111 @@ export class Scope {
11481179 }
11491180 }
11501181
1182+ /** @internal Queues a shared scheduled task flush for this scope family. */
1183+ _enqueueScheduledTask ( task : ScheduledTask ) : void {
1184+ const scheduler = this . _listenerScheduler ;
1185+
1186+ scheduler . _queue . push ( task ) ;
1187+
1188+ if ( ! scheduler . _queued && ! scheduler . _flushing ) {
1189+ scheduler . _queued = true ;
1190+
1191+ queueMicrotask ( ( ) => {
1192+ this . _flushScheduledTasks ( ) ;
1193+ } ) ;
1194+ }
1195+ }
1196+
1197+ /** @internal Flushes queued listener and callback tasks in FIFO order. */
1198+ _flushScheduledTasks ( ) : void {
1199+ const scheduler = this . _listenerScheduler ;
1200+
1201+ if ( scheduler . _flushing ) {
1202+ return ;
1203+ }
1204+
1205+ scheduler . _queued = false ;
1206+ scheduler . _flushing = true ;
1207+
1208+ let processed = 0 ;
1209+
1210+ try {
1211+ while ( processed < scheduler . _queue . length ) {
1212+ const task = scheduler . _queue [ processed ++ ] ;
1213+
1214+ if ( task . _kind === "callback" ) {
1215+ task . _callback ( ) ;
1216+ this . _drainPostUpdateQueue ( ) ;
1217+
1218+ continue ;
1219+ }
1220+
1221+ const filteredListeners = task . _filter
1222+ ? task . _filter ( task . _listeners )
1223+ : task . _listeners ;
1224+
1225+ for ( let i = 0 , l = filteredListeners . length ; i < l ; i ++ ) {
1226+ this . _notifyListener ( filteredListeners [ i ] , task . _target ) ;
1227+ this . _drainPostUpdateQueue ( ) ;
1228+ }
1229+ }
1230+ } finally {
1231+ if ( processed > 0 ) {
1232+ scheduler . _queue . splice ( 0 , processed ) ;
1233+ }
1234+
1235+ scheduler . _flushing = false ;
1236+
1237+ if ( scheduler . _queue . length > 0 && ! scheduler . _queued ) {
1238+ scheduler . _queued = true ;
1239+
1240+ queueMicrotask ( ( ) => {
1241+ this . _flushScheduledTasks ( ) ;
1242+ } ) ;
1243+ }
1244+ }
1245+ }
1246+
1247+ /** @internal Drains post-update callbacks in FIFO order. */
1248+ _drainPostUpdateQueue ( ) : void {
1249+ if ( $postUpdateQueue . length === 0 ) {
1250+ return ;
1251+ }
1252+
1253+ let index = 0 ;
1254+
1255+ while ( index < $postUpdateQueue . length ) {
1256+ $postUpdateQueue [ index ++ ] ( ) ;
1257+ }
1258+
1259+ $postUpdateQueue . length = 0 ;
1260+ }
1261+
1262+ /** @internal Schedules a callback to run in the shared listener flush queue. */
1263+ _scheduleCallback ( callback : ( ) => void ) : void {
1264+ this . _enqueueScheduledTask ( {
1265+ _kind : "callback" ,
1266+ _callback : callback ,
1267+ } ) ;
1268+ }
1269+
11511270 /** @internal Queues listener notification for the next microtask, optionally filtering the list first. */
11521271 _scheduleListener (
11531272 listeners : Listener [ ] ,
1154- filter ?: ( listeners : Listener [ ] ) => Listener [ ] ,
1273+ filterOrTarget ?: ListenerScheduleFilter | Scope | ScopeProxy ,
11551274 ) : void {
1156- const target = this . $target ;
1157-
1158- queueMicrotask ( ( ) => {
1159- const filteredListeners = filter ? filter ( listeners ) : listeners ;
1160-
1161- for ( let i = 0 , l = filteredListeners . length ; i < l ; i ++ ) {
1162- this . _notifyListener ( filteredListeners [ i ] , target ) ;
1163- }
1275+ const filter =
1276+ typeof filterOrTarget === "function"
1277+ ? ( filterOrTarget as ListenerScheduleFilter )
1278+ : undefined ;
1279+
1280+ const target = filter ? this . $target : ( filterOrTarget ?? this . $target ) ;
1281+
1282+ this . _enqueueScheduledTask ( {
1283+ _kind : "listener" ,
1284+ _listeners : listeners ,
1285+ _target : target ,
1286+ _filter : filter ,
11641287 } ) ;
11651288 }
11661289
@@ -1181,7 +1304,7 @@ export class Scope {
11811304 // Constant are immediately passed to listener function
11821305 if ( get . _constant ) {
11831306 if ( listenerFn ) {
1184- queueMicrotask ( ( ) => {
1307+ this . _scheduleCallback ( ( ) => {
11851308 let res = get ( ) ;
11861309
11871310 while ( isFunction ( res ) ) {
@@ -2023,24 +2146,14 @@ export class Scope {
20232146 }
20242147
20252148 _listenerFn ( newVal , _originalTarget ) ;
2026-
2027- if ( $postUpdateQueue . length > 0 ) {
2028- for ( let qi = 0 ; qi < $postUpdateQueue . length ; qi ++ ) {
2029- $postUpdateQueue [ qi ] ( ) ;
2030- }
2031- $postUpdateQueue . length = 0 ;
2032- }
20332149 } catch ( err ) {
20342150 $exceptionHandler ( err ) ;
20352151 }
20362152 }
20372153
20382154 /* @ignore */
20392155 $flushQueue ( ) {
2040- for ( let i = 0 ; i < $postUpdateQueue . length ; i ++ ) {
2041- $postUpdateQueue [ i ] ( ) ;
2042- }
2043- $postUpdateQueue . length = 0 ;
2156+ this . _drainPostUpdateQueue ( ) ;
20442157 }
20452158
20462159 /** Searches this scope tree for a scope with the given id. */
0 commit comments