Skip to content

Commit 3e2e023

Browse files
crisbetojelbourn
authored andcommitted
fix(google-maps): internal events run inside NgZone (#18034)
The custom Google Maps event work by binding everything ahead of time and then dispatching the results to the various listeners, however the result of this is that they'll trigger change detection for events that the consumer hasn't subscribed to. These changes switch to initializing the Google Maps objects outside the `NgZone` and then only bringing the ones that the consumer has subscribed to back inside.
1 parent 4405429 commit 3e2e023

File tree

6 files changed

+50
-18
lines changed

6 files changed

+50
-18
lines changed

src/google-maps/google-map/google-map.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
Optional,
2323
Inject,
2424
PLATFORM_ID,
25+
NgZone,
2526
} from '@angular/core';
2627
import {isPlatformBrowser} from '@angular/common';
2728
import {BehaviorSubject, combineLatest, Observable, Subject} from 'rxjs';
@@ -64,7 +65,7 @@ export const DEFAULT_WIDTH = '500px';
6465
encapsulation: ViewEncapsulation.None,
6566
})
6667
export class GoogleMap implements OnChanges, OnInit, OnDestroy {
67-
private _eventManager = new MapEventManager();
68+
private _eventManager: MapEventManager = new MapEventManager(this._ngZone);
6869
private _googleMapChanges: Observable<google.maps.Map>;
6970

7071
private readonly _options = new BehaviorSubject<google.maps.MapOptions>(DEFAULT_OPTIONS);
@@ -223,6 +224,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
223224

224225
constructor(
225226
private readonly _elementRef: ElementRef,
227+
private _ngZone: NgZone,
226228
/**
227229
* @deprecated `platformId` parameter to become required.
228230
* @breaking-change 10.0.0
@@ -454,7 +456,12 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
454456
Observable<google.maps.Map> {
455457
return optionsChanges.pipe(
456458
take(1),
457-
map(options => new google.maps.Map(this._mapEl, options)),
459+
map(options => {
460+
// Create the object outside the zone so its events don't trigger change detection.
461+
// We'll bring it back in inside the `MapEventManager` only for the events that the
462+
// user has subscribed to.
463+
return this._ngZone.runOutsideAngular(() => new google.maps.Map(this._mapEl, options));
464+
}),
458465
shareReplay(1));
459466
}
460467

src/google-maps/map-event-manager.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {NgZone} from '@angular/core';
910
import {Observable, Subscriber} from 'rxjs';
1011

1112
type MapEventManagerTarget = {
@@ -28,6 +29,8 @@ export class MapEventManager {
2829
this._listeners = [];
2930
}
3031

32+
constructor(private _ngZone: NgZone) {}
33+
3134
/** Gets an observable that adds an event listener to the map when a consumer subscribes to it. */
3235
getLazyEmitter<T>(name: string): Observable<T> {
3336
const observable = new Observable<T>(observer => {
@@ -37,7 +40,9 @@ export class MapEventManager {
3740
return undefined;
3841
}
3942

40-
const listener = this._target.addListener(name, (event: T) => observer.next(event));
43+
const listener = this._target.addListener(name, (event: T) => {
44+
this._ngZone.run(() => observer.next(event));
45+
});
4146
this._listeners.push(listener);
4247
return () => listener.remove();
4348
});

src/google-maps/map-info-window/map-info-window.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
OnDestroy,
1717
OnInit,
1818
Output,
19+
NgZone,
1920
} from '@angular/core';
2021
import {BehaviorSubject, combineLatest, Observable, Subject} from 'rxjs';
2122
import {map, takeUntil} from 'rxjs/operators';
@@ -33,7 +34,7 @@ import {MapEventManager} from '../map-event-manager';
3334
host: {'style': 'display: none'},
3435
})
3536
export class MapInfoWindow implements OnInit, OnDestroy {
36-
private _eventManager = new MapEventManager();
37+
private _eventManager = new MapEventManager(this._ngZone);
3738
private readonly _options = new BehaviorSubject<google.maps.InfoWindowOptions>({});
3839
private readonly _position =
3940
new BehaviorSubject<google.maps.LatLngLiteral|google.maps.LatLng|undefined>(undefined);
@@ -87,15 +88,22 @@ export class MapInfoWindow implements OnInit, OnDestroy {
8788
zindexChanged: Observable<void> = this._eventManager.getLazyEmitter<void>('zindex_changed');
8889

8990
constructor(private readonly _googleMap: GoogleMap,
90-
private _elementRef: ElementRef<HTMLElement>) {}
91+
private _elementRef: ElementRef<HTMLElement>,
92+
private _ngZone: NgZone) {}
9193

9294
ngOnInit() {
9395
if (this._googleMap._isBrowser) {
9496
this._combineOptions().pipe(takeUntil(this._destroy)).subscribe(options => {
9597
if (this._infoWindow) {
9698
this._infoWindow.setOptions(options);
9799
} else {
98-
this._infoWindow = new google.maps.InfoWindow(options);
100+
// Create the object outside the zone so its events don't trigger change detection.
101+
// We'll bring it back in inside the `MapEventManager` only for the events that the
102+
// user has subscribed to.
103+
this._ngZone.runOutsideAngular(() => {
104+
this._infoWindow = new google.maps.InfoWindow(options);
105+
});
106+
99107
this._eventManager.setTarget(this._infoWindow);
100108
}
101109
});

src/google-maps/map-marker/map-marker.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import {
1616
OnDestroy,
1717
OnInit,
1818
Output,
19-
ViewEncapsulation
19+
ViewEncapsulation,
20+
NgZone
2021
} from '@angular/core';
2122
import {BehaviorSubject, combineLatest, Observable, Subject} from 'rxjs';
2223
import {map, take, takeUntil} from 'rxjs/operators';
@@ -43,7 +44,7 @@ export const DEFAULT_MARKER_OPTIONS = {
4344
encapsulation: ViewEncapsulation.None,
4445
})
4546
export class MapMarker implements OnInit, OnDestroy {
46-
private _eventManager = new MapEventManager();
47+
private _eventManager = new MapEventManager(this._ngZone);
4748
private readonly _options =
4849
new BehaviorSubject<google.maps.MarkerOptions>(DEFAULT_MARKER_OPTIONS);
4950
private readonly _title = new BehaviorSubject<string|undefined>(undefined);
@@ -236,15 +237,20 @@ export class MapMarker implements OnInit, OnDestroy {
236237

237238
_marker?: google.maps.Marker;
238239

239-
constructor(private readonly _googleMap: GoogleMap) {}
240+
constructor(
241+
private readonly _googleMap: GoogleMap,
242+
private _ngZone: NgZone) {}
240243

241244
ngOnInit() {
242245
if (this._googleMap._isBrowser) {
243246
const combinedOptionsChanges = this._combineOptions();
244247

245248
combinedOptionsChanges.pipe(take(1)).subscribe(options => {
246-
this._marker = new google.maps.Marker(options);
247-
this._marker.setMap(this._googleMap._googleMap);
249+
// Create the object outside the zone so its events don't trigger change detection.
250+
// We'll bring it back in inside the `MapEventManager` only for the events that the
251+
// user has subscribed to.
252+
this._ngZone.runOutsideAngular(() => this._marker = new google.maps.Marker(options));
253+
this._marker!.setMap(this._googleMap._googleMap);
248254
this._eventManager.setTarget(this._marker);
249255
});
250256

src/google-maps/map-polyline/map-polyline.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
OnDestroy,
1616
OnInit,
1717
Output,
18+
NgZone,
1819
} from '@angular/core';
1920
import {BehaviorSubject, combineLatest, Observable, Subject} from 'rxjs';
2021
import {map, take, takeUntil} from 'rxjs/operators';
@@ -30,7 +31,7 @@ import {MapEventManager} from '../map-event-manager';
3031
selector: 'map-polyline',
3132
})
3233
export class MapPolyline implements OnInit, OnDestroy {
33-
private _eventManager = new MapEventManager();
34+
private _eventManager = new MapEventManager(this._ngZone);
3435
private readonly _options = new BehaviorSubject<google.maps.PolylineOptions>({});
3536
private readonly _path =
3637
new BehaviorSubject<google.maps.MVCArray<google.maps.LatLng>|google.maps.LatLng[]|
@@ -129,14 +130,19 @@ export class MapPolyline implements OnInit, OnDestroy {
129130
polylineRightclick: Observable<google.maps.PolyMouseEvent> =
130131
this._eventManager.getLazyEmitter<google.maps.PolyMouseEvent>('rightclick');
131132

132-
constructor(private readonly _map: GoogleMap) {}
133+
constructor(
134+
private readonly _map: GoogleMap,
135+
private _ngZone: NgZone) {}
133136

134137
ngOnInit() {
135138
if (this._map._isBrowser) {
136139
const combinedOptionsChanges = this._combineOptions();
137140

138141
combinedOptionsChanges.pipe(take(1)).subscribe(options => {
139-
this._polyline = new google.maps.Polyline(options);
142+
// Create the object outside the zone so its events don't trigger change detection.
143+
// We'll bring it back in inside the `MapEventManager` only for the events that the
144+
// user has subscribed to.
145+
this._ngZone.runOutsideAngular(() => this._polyline = new google.maps.Polyline(options));
140146
this._polyline.setMap(this._map._googleMap);
141147
this._eventManager.setTarget(this._polyline);
142148
});

tools/public_api_guard/google-maps/google-maps.d.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export declare class GoogleMap implements OnChanges, OnInit, OnDestroy {
2828
width: string | number;
2929
set zoom(zoom: number);
3030
zoomChanged: Observable<void>;
31-
constructor(_elementRef: ElementRef,
31+
constructor(_elementRef: ElementRef, _ngZone: NgZone,
3232
platformId?: Object);
3333
fitBounds(bounds: google.maps.LatLngBounds | google.maps.LatLngBoundsLiteral, padding?: number | google.maps.Padding): void;
3434
getBounds(): google.maps.LatLngBounds | null;
@@ -63,7 +63,7 @@ export declare class MapInfoWindow implements OnInit, OnDestroy {
6363
set position(position: google.maps.LatLngLiteral | google.maps.LatLng);
6464
positionChanged: Observable<void>;
6565
zindexChanged: Observable<void>;
66-
constructor(_googleMap: GoogleMap, _elementRef: ElementRef<HTMLElement>);
66+
constructor(_googleMap: GoogleMap, _elementRef: ElementRef<HTMLElement>, _ngZone: NgZone);
6767
close(): void;
6868
getContent(): string | Node;
6969
getPosition(): google.maps.LatLng | null;
@@ -103,7 +103,7 @@ export declare class MapMarker implements OnInit, OnDestroy {
103103
titleChanged: Observable<void>;
104104
visibleChanged: Observable<void>;
105105
zindexChanged: Observable<void>;
106-
constructor(_googleMap: GoogleMap);
106+
constructor(_googleMap: GoogleMap, _ngZone: NgZone);
107107
getAnimation(): google.maps.Animation | null;
108108
getClickable(): boolean;
109109
getCursor(): string | null;
@@ -137,7 +137,7 @@ export declare class MapPolyline implements OnInit, OnDestroy {
137137
polylineMouseover: Observable<google.maps.PolyMouseEvent>;
138138
polylineMouseup: Observable<google.maps.PolyMouseEvent>;
139139
polylineRightclick: Observable<google.maps.PolyMouseEvent>;
140-
constructor(_map: GoogleMap);
140+
constructor(_map: GoogleMap, _ngZone: NgZone);
141141
getDraggable(): boolean;
142142
getEditable(): boolean;
143143
getPath(): google.maps.MVCArray<google.maps.LatLng>;

0 commit comments

Comments
 (0)