Skip to content

Commit f60d5e6

Browse files
committed
fix(google-maps): internal events run inside NgZone
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 a30094b commit f60d5e6

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 = new MapEventManager(this._ngZone);
6869

6970
/** Whether we're currently rendering inside a browser. */
7071
private _isBrowser: boolean;
@@ -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
@@ -453,7 +455,12 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
453455
Observable<google.maps.Map> {
454456
return optionsChanges.pipe(
455457
take(1),
456-
map(options => new google.maps.Map(this._mapEl, options)),
458+
map(options => {
459+
// Create the object outside the zone so its events don't trigger change detection.
460+
// We'll bring it back in inside the `MapEventManager` only for the events that the
461+
// user has subscribed to.
462+
return this._ngZone.runOutsideAngular(() => new google.maps.Map(this._mapEl, options));
463+
}),
457464
shareReplay(1));
458465
}
459466

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,14 +88,21 @@ 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
this._combineOptions().pipe(takeUntil(this._destroy)).subscribe(options => {
9496
if (this._infoWindow) {
9597
this._infoWindow.setOptions(options);
9698
} else {
97-
this._infoWindow = new google.maps.InfoWindow(options);
99+
// Create the object outside the zone so its events don't trigger change detection.
100+
// We'll bring it back in inside the `MapEventManager` only for the events that the
101+
// user has subscribed to.
102+
this._ngZone.runOutsideAngular(() => {
103+
this._infoWindow = new google.maps.InfoWindow(options);
104+
});
105+
98106
this._eventManager.setTarget(this._infoWindow);
99107
}
100108
});

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,14 +237,19 @@ 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
const combinedOptionsChanges = this._combineOptions();
243246

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

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,13 +130,18 @@ 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
const combinedOptionsChanges = this._combineOptions();
136139

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

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export declare class GoogleMap implements OnChanges, OnInit, OnDestroy {
2727
width: string;
2828
zoom: number;
2929
zoomChanged: Observable<void>;
30-
constructor(_elementRef: ElementRef,
30+
constructor(_elementRef: ElementRef, _ngZone: NgZone,
3131
platformId?: Object);
3232
fitBounds(bounds: google.maps.LatLngBounds | google.maps.LatLngBoundsLiteral, padding?: number | google.maps.Padding): void;
3333
getBounds(): google.maps.LatLngBounds | null;
@@ -62,7 +62,7 @@ export declare class MapInfoWindow implements OnInit, OnDestroy {
6262
position: google.maps.LatLngLiteral | google.maps.LatLng;
6363
positionChanged: Observable<void>;
6464
zindexChanged: Observable<void>;
65-
constructor(_googleMap: GoogleMap, _elementRef: ElementRef<HTMLElement>);
65+
constructor(_googleMap: GoogleMap, _elementRef: ElementRef<HTMLElement>, _ngZone: NgZone);
6666
close(): void;
6767
getContent(): string | Node;
6868
getPosition(): google.maps.LatLng | null;
@@ -102,7 +102,7 @@ export declare class MapMarker implements OnInit, OnDestroy {
102102
titleChanged: Observable<void>;
103103
visibleChanged: Observable<void>;
104104
zindexChanged: Observable<void>;
105-
constructor(_googleMap: GoogleMap);
105+
constructor(_googleMap: GoogleMap, _ngZone: NgZone);
106106
getAnimation(): google.maps.Animation | null;
107107
getClickable(): boolean;
108108
getCursor(): string | null;
@@ -136,7 +136,7 @@ export declare class MapPolyline implements OnInit, OnDestroy {
136136
polylineMouseover: Observable<google.maps.PolyMouseEvent>;
137137
polylineMouseup: Observable<google.maps.PolyMouseEvent>;
138138
polylineRightclick: Observable<google.maps.PolyMouseEvent>;
139-
constructor(_map: GoogleMap);
139+
constructor(_map: GoogleMap, _ngZone: NgZone);
140140
getDraggable(): boolean;
141141
getEditable(): boolean;
142142
getPath(): google.maps.MVCArray<google.maps.LatLng>;

0 commit comments

Comments
 (0)