From 4b79c43f41a69f8695f21ab9ef87fb5834b19a81 Mon Sep 17 00:00:00 2001 From: Matthew Ehrlich Date: Mon, 9 Sep 2019 19:37:20 -0700 Subject: [PATCH 1/7] feat(google-maps): Add map-info-window component Add component that implements the Google Maps JavaScript API InfoWindow that displays on the Google Map and can be anchored to a marker. --- src/dev-app/google-map/google-map-demo.html | 10 +- src/dev-app/google-map/google-map-demo.ts | 11 +- src/google-maps/google-map/google-map.spec.ts | 16 ++ src/google-maps/google-map/google-map.ts | 17 ++ src/google-maps/google-maps-module.ts | 5 +- src/google-maps/map-info-window/BUILD.bazel | 53 +++++ src/google-maps/map-info-window/index.ts | 10 + .../map-info-window/map-info-window-module.ts | 17 ++ .../map-info-window/map-info-window.scss | 5 + .../map-info-window/map-info-window.spec.ts | 194 ++++++++++++++++++ .../map-info-window/map-info-window.ts | 186 +++++++++++++++++ src/google-maps/map-marker/map-marker.ts | 3 +- src/google-maps/public-api.ts | 1 + .../testing/fake-google-map-utils.ts | 27 +++ stylelint-config.json | 2 +- 15 files changed, 546 insertions(+), 11 deletions(-) create mode 100644 src/google-maps/map-info-window/BUILD.bazel create mode 100644 src/google-maps/map-info-window/index.ts create mode 100644 src/google-maps/map-info-window/map-info-window-module.ts create mode 100644 src/google-maps/map-info-window/map-info-window.scss create mode 100644 src/google-maps/map-info-window/map-info-window.spec.ts create mode 100644 src/google-maps/map-info-window/map-info-window.ts diff --git a/src/dev-app/google-map/google-map-demo.html b/src/dev-app/google-map/google-map-demo.html index 588e0a77432d..afaecc973b46 100644 --- a/src/dev-app/google-map/google-map-demo.html +++ b/src/dev-app/google-map/google-map-demo.html @@ -7,10 +7,12 @@ (mapMousemove)="handleMove($event)" (mapRightclick)="handleRightclick()"> - + + Testing 1 2 3
Latitude: {{display?.lat}}
diff --git a/src/dev-app/google-map/google-map-demo.ts b/src/dev-app/google-map/google-map-demo.ts index 163c8573f39e..91d41c0ea06e 100644 --- a/src/dev-app/google-map/google-map-demo.ts +++ b/src/dev-app/google-map/google-map-demo.ts @@ -6,8 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component} from '@angular/core'; import {HttpClient} from '@angular/common/http'; +import {Component, ViewChild} from '@angular/core'; +import {MapInfoWindow, MapMarker} from '@angular/google-maps'; /** Demo Component for @angular/google-maps/map */ @Component({ @@ -16,11 +17,14 @@ import {HttpClient} from '@angular/common/http'; templateUrl: 'google-map-demo.html', }) export class GoogleMapDemo { + @ViewChild(MapInfoWindow, {static: false}) infoWindow: MapInfoWindow; + isReady = false; center = {lat: 24, lng: 12}; markerOptions = {draggable: false}; markerPositions: google.maps.LatLngLiteral[] = []; + infoWindowPosition: google.maps.LatLngLiteral; zoom = 4; display?: google.maps.LatLngLiteral; @@ -39,9 +43,8 @@ export class GoogleMapDemo { this.display = event.latLng.toJSON(); } - clickMarker(event: google.maps.MouseEvent) { - console.log(this.markerOptions); - this.markerOptions = {draggable: true}; + clickMarker(marker: MapMarker) { + this.infoWindow.open(marker); } handleRightclick() { diff --git a/src/google-maps/google-map/google-map.spec.ts b/src/google-maps/google-map/google-map.spec.ts index f3de7fc8ac95..90028064f1ad 100644 --- a/src/google-maps/google-map/google-map.spec.ts +++ b/src/google-maps/google-map/google-map.spec.ts @@ -2,6 +2,7 @@ import {Component} from '@angular/core'; import {async, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; +import {MapInfoWindow, MapInfoWindowModule} from '../map-info-window/index'; import {MapMarker, MapMarkerModule} from '../map-marker/index'; import { createMapConstructorSpy, @@ -40,6 +41,7 @@ describe('GoogleMap', () => { TestBed.configureTestingModule({ imports: [ GoogleMapModule, + MapInfoWindowModule, MapMarkerModule, ], declarations: [TestApp], @@ -254,6 +256,19 @@ describe('GoogleMap', () => { expect(markerComponent._setMap).toHaveBeenCalledWith(mapSpy); }); + + it('calls setMap on child info window components', () => { + mapSpy = createMapSpy(DEFAULT_OPTIONS); + createMapConstructorSpy(mapSpy).and.callThrough(); + + const fixture = TestBed.createComponent(TestApp); + const infoWindowComponent = + fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; + spyOn(infoWindowComponent, '_setMap').and.callThrough(); + fixture.detectChanges(); + + expect(infoWindowComponent._setMap).toHaveBeenCalledWith(mapSpy); + }); }); @Component({ @@ -267,6 +282,7 @@ describe('GoogleMap', () => { (centerChanged)="handleCenterChanged()" (mapRightclick)="handleRightclick($event)"> + test content `, }) class TestApp { diff --git a/src/google-maps/google-map/google-map.ts b/src/google-maps/google-map/google-map.ts index 049c98d7f929..a4d0366949f4 100644 --- a/src/google-maps/google-map/google-map.ts +++ b/src/google-maps/google-map/google-map.ts @@ -24,6 +24,7 @@ import { import {BehaviorSubject, combineLatest, Observable, Subject} from 'rxjs'; import {map, shareReplay, take, takeUntil} from 'rxjs/operators'; +import {MapInfoWindow} from '../map-info-window'; import {MapMarker} from '../map-marker/index'; interface GoogleMapsWindow extends Window { @@ -190,6 +191,8 @@ export class GoogleMap implements OnChanges, OnInit, AfterContentInit, OnDestroy @ContentChildren(MapMarker) _markers: QueryList; + @ContentChildren(MapInfoWindow) _infoWindows: QueryList; + private _mapEl: HTMLElement; private _googleMap!: UpdatedGoogleMap; @@ -238,7 +241,11 @@ export class GoogleMap implements OnChanges, OnInit, AfterContentInit, OnDestroy for (const marker of this._markers.toArray()) { marker._setMap(this._googleMap); } + for (const infoWindow of this._infoWindows.toArray()) { + infoWindow._setMap(this._googleMap); + } this._watchForMarkerChanges(); + this._watchForInfoWindowChanges(); } ngOnDestroy() { @@ -482,4 +489,14 @@ export class GoogleMap implements OnChanges, OnInit, AfterContentInit, OnDestroy } }); } + + private _watchForInfoWindowChanges() { + combineLatest(this._googleMapChanges, this._infoWindows.changes) + .pipe(takeUntil(this._destroy)) + .subscribe(([googleMap, infoWindows]) => { + for (let infoWindow of infoWindows) { + infoWindow._setMap(googleMap); + } + }); + } } diff --git a/src/google-maps/google-maps-module.ts b/src/google-maps/google-maps-module.ts index d816f00546ad..28c81e47a38a 100644 --- a/src/google-maps/google-maps-module.ts +++ b/src/google-maps/google-maps-module.ts @@ -8,16 +8,19 @@ import {NgModule} from '@angular/core'; -import {MapMarker, MapMarkerModule} from './map-marker/index'; import {GoogleMap, GoogleMapModule} from './google-map/index'; +import {MapInfoWindow, MapInfoWindowModule} from './map-info-window/index'; +import {MapMarker, MapMarkerModule} from './map-marker/index'; @NgModule({ imports: [ GoogleMapModule, + MapInfoWindowModule, MapMarkerModule, ], exports: [ GoogleMap, + MapInfoWindow, MapMarker, ], }) diff --git a/src/google-maps/map-info-window/BUILD.bazel b/src/google-maps/map-info-window/BUILD.bazel new file mode 100644 index 000000000000..2f12538c3244 --- /dev/null +++ b/src/google-maps/map-info-window/BUILD.bazel @@ -0,0 +1,53 @@ +package(default_visibility = ["//visibility:public"]) + +load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") +load( + "//tools:defaults.bzl", + "ng_module", + "ng_test_library", + "ng_web_test_suite", +) + +ng_module( + name = "map-info-window", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), + assets = [":map-info-window.css"], + deps = [ + "//src/google-maps/map-marker", + "@npm//@angular/core", + "@npm//@types/googlemaps", + "@npm//rxjs", + ], +) + +sass_binary( + name = "map-info-window_scss", + src = "map-info-window.scss", +) + +ng_test_library( + name = "unit_test_sources", + srcs = glob( + ["**/*.spec.ts"], + exclude = ["**/*.e2e.spec.ts"], + ), + deps = [ + ":map-info-window", + "//src/google-maps/map-marker", + "//src/google-maps/testing", + "@npm//@angular/platform-browser", + ], +) + +ng_web_test_suite( + name = "unit_tests", + deps = [":unit_test_sources"], +) + +filegroup( + name = "source-files", + srcs = glob(["**/*.ts"]), +) diff --git a/src/google-maps/map-info-window/index.ts b/src/google-maps/map-info-window/index.ts new file mode 100644 index 000000000000..46f6e6539a4d --- /dev/null +++ b/src/google-maps/map-info-window/index.ts @@ -0,0 +1,10 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './map-info-window'; +export * from './map-info-window-module'; diff --git a/src/google-maps/map-info-window/map-info-window-module.ts b/src/google-maps/map-info-window/map-info-window-module.ts new file mode 100644 index 000000000000..de7eed77138b --- /dev/null +++ b/src/google-maps/map-info-window/map-info-window-module.ts @@ -0,0 +1,17 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {NgModule} from '@angular/core'; +import {MapInfoWindow} from './map-info-window'; + +@NgModule({ + exports: [MapInfoWindow], + declarations: [MapInfoWindow], +}) +export class MapInfoWindowModule { +} diff --git a/src/google-maps/map-info-window/map-info-window.scss b/src/google-maps/map-info-window/map-info-window.scss new file mode 100644 index 000000000000..fbb7ad6aefdd --- /dev/null +++ b/src/google-maps/map-info-window/map-info-window.scss @@ -0,0 +1,5 @@ +:host { + .map-info-window-content { + display: none; + } +} diff --git a/src/google-maps/map-info-window/map-info-window.spec.ts b/src/google-maps/map-info-window/map-info-window.spec.ts new file mode 100644 index 000000000000..737f293c1b8f --- /dev/null +++ b/src/google-maps/map-info-window/map-info-window.spec.ts @@ -0,0 +1,194 @@ +import {Component} from '@angular/core'; +import {async, TestBed} from '@angular/core/testing'; +import {By} from '@angular/platform-browser'; + +import {MapMarker} from '../map-marker/index'; +import { + createInfoWindowConstructorSpy, + createInfoWindowSpy, + TestingWindow +} from '../testing/fake-google-map-utils'; + +import {MapInfoWindow, MapInfoWindowModule} from './index'; + +describe('MapInfoWindow', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MapInfoWindowModule], + declarations: [TestApp], + }); + })); + + beforeEach(() => { + TestBed.compileComponents(); + }); + + afterEach(() => { + const testingWindow: TestingWindow = window; + delete testingWindow.google; + }); + + it('initializes a Google Map Info Window', () => { + const infoWindowSpy = createInfoWindowSpy({}); + const infoWindowConstructorSpy = + createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough(); + + const fixture = TestBed.createComponent(TestApp); + const fakeMap = {} as unknown as google.maps.Map; + const infoWindowComponent = + fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; + infoWindowComponent._setMap(fakeMap); + fixture.detectChanges(); + + expect(infoWindowConstructorSpy).toHaveBeenCalledWith({ + position: undefined, + content: jasmine.any(Node), + }); + }); + + it('sets position', () => { + const fakeMap = {} as unknown as google.maps.Map; + const position: google.maps.LatLngLiteral = {lat: 5, lng: 7}; + const infoWindowSpy = createInfoWindowSpy({position}); + const infoWindowConstructorSpy = + createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough(); + + const fixture = TestBed.createComponent(TestApp); + fixture.componentInstance.position = position; + const infoWindowComponent = + fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; + infoWindowComponent._setMap(fakeMap); + fixture.detectChanges(); + + expect(infoWindowConstructorSpy).toHaveBeenCalledWith({ + position, + content: jasmine.any(Node), + }); + }); + + it('sets options', () => { + const fakeMap = {} as unknown as google.maps.Map; + const options: google.maps.InfoWindowOptions = { + position: {lat: 3, lng: 5}, + maxWidth: 50, + disableAutoPan: true, + }; + const infoWindowSpy = createInfoWindowSpy(options); + const infoWindowConstructorSpy = + createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough(); + + const fixture = TestBed.createComponent(TestApp); + fixture.componentInstance.options = options; + const infoWindowComponent = + fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; + infoWindowComponent._setMap(fakeMap); + fixture.detectChanges(); + + expect(infoWindowConstructorSpy).toHaveBeenCalledWith({ + ...options, + content: jasmine.any(Node), + }); + }); + + it('gives preference to position over options', () => { + const fakeMap = {} as unknown as google.maps.Map; + const position: google.maps.LatLngLiteral = {lat: 5, lng: 7}; + const options: google.maps.InfoWindowOptions = { + position: {lat: 3, lng: 5}, + maxWidth: 50, + disableAutoPan: true, + }; + const infoWindowSpy = createInfoWindowSpy({...options, position}); + const infoWindowConstructorSpy = + createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough(); + + const fixture = TestBed.createComponent(TestApp); + fixture.componentInstance.options = options; + fixture.componentInstance.position = position; + const infoWindowComponent = + fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; + infoWindowComponent._setMap(fakeMap); + fixture.detectChanges(); + + expect(infoWindowConstructorSpy).toHaveBeenCalledWith({ + ...options, + position, + content: jasmine.any(Node), + }); + }); + + it('exposes meethods that change the configuration of the info window', () => { + const fakeMap = {} as unknown as google.maps.Map; + const fakeMarker = {} as unknown as google.maps.Marker; + const fakeMarkerComponent = {_marker: fakeMarker} as unknown as MapMarker; + const infoWindowSpy = createInfoWindowSpy({}); + createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough(); + + const fixture = TestBed.createComponent(TestApp); + const infoWindowComponent = + fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; + infoWindowComponent._setMap(fakeMap); + fixture.detectChanges(); + + infoWindowComponent.close(); + expect(infoWindowSpy.close).toHaveBeenCalled(); + + infoWindowComponent.open(fakeMarkerComponent); + expect(infoWindowSpy.open).toHaveBeenCalledWith(fakeMap, fakeMarker); + }); + + it('exposes methods that provide information about the info window', () => { + const fakeMap = {} as unknown as google.maps.Map; + const infoWindowSpy = createInfoWindowSpy({}); + createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough(); + + const fixture = TestBed.createComponent(TestApp); + const infoWindowComponent = + fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; + infoWindowComponent._setMap(fakeMap); + fixture.detectChanges(); + + infoWindowSpy.getContent.and.returnValue('test content'); + expect(infoWindowComponent.getContent()).toBe('test content'); + + infoWindowSpy.getPosition.and.returnValue(null); + expect(infoWindowComponent.getPosition()).toBe(null); + + infoWindowSpy.getZIndex.and.returnValue(5); + expect(infoWindowComponent.getZIndex()).toBe(5); + }); + + it('initializes info window event handlers', () => { + const fakeMap = {} as unknown as google.maps.Map; + const infoWindowSpy = createInfoWindowSpy({}); + createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough(); + + const fixture = TestBed.createComponent(TestApp); + const infoWindowComponent = + fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; + infoWindowComponent._setMap(fakeMap); + fixture.detectChanges(); + + expect(infoWindowSpy.addListener).toHaveBeenCalledWith('closeclick', jasmine.any(Function)); + expect(infoWindowSpy.addListener) + .not.toHaveBeenCalledWith('content_changed', jasmine.any(Function)); + expect(infoWindowSpy.addListener).not.toHaveBeenCalledWith('domready', jasmine.any(Function)); + expect(infoWindowSpy.addListener) + .not.toHaveBeenCalledWith('position_changed', jasmine.any(Function)); + expect(infoWindowSpy.addListener) + .not.toHaveBeenCalledWith('zindex_changed', jasmine.any(Function)); + }); +}); + +@Component({ + selector: 'test-app', + template: `test content`, +}) +class TestApp { + position?: google.maps.LatLngLiteral; + options?: google.maps.InfoWindowOptions; + + handleClose() {} +} diff --git a/src/google-maps/map-info-window/map-info-window.ts b/src/google-maps/map-info-window/map-info-window.ts new file mode 100644 index 000000000000..3c3c733bd3cb --- /dev/null +++ b/src/google-maps/map-info-window/map-info-window.ts @@ -0,0 +1,186 @@ +import { + Component, + ElementRef, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, + ViewChild +} from '@angular/core'; +import {BehaviorSubject, combineLatest, Observable, ReplaySubject, Subject} from 'rxjs'; +import {map, takeUntil} from 'rxjs/operators'; + +import {MapMarker} from '../map-marker/index'; + +/** + * Angular component that renders a Google Maps info window via the Google Maps JavaScript API. + * @see developers.google.com/maps/documentation/javascript/reference/info-window + */ +@Component({ + selector: 'map-info-window', + template: `
+ +
`, + styleUrls: ['map-info-window.css'], +}) +export class MapInfoWindow implements OnInit, OnDestroy { + @Input() + set options(options: google.maps.InfoWindowOptions) { + this._options.next(options || {}); + } + + @Input() + set position(position: google.maps.LatLngLiteral) { + this._position.next(position); + } + + /** + * See + * developers.google.com/maps/documentation/javascript/reference/info-window#InfoWindow.closeclick + */ + @Output() closeclick = new EventEmitter(); + + /** + * See + * developers.google.com/maps/documentation/javascript/reference/info-window + * #InfoWindow.content_changed + */ + @Output() contentChanged = new EventEmitter(); + + /** + * See + * developers.google.com/maps/documentation/javascript/reference/info-window#InfoWindow.domready + */ + @Output() domready = new EventEmitter(); + + /** + * See + * developers.google.com/maps/documentation/javascript/reference/info-window + * #InfoWindow.position_changed + */ + @Output() positionChanged = new EventEmitter(); + + /** + * See + * developers.google.com/maps/documentation/javascript/reference/info-window + * #InfoWindow.zindex_changed + */ + @Output() zindexChanged = new EventEmitter(); + + @ViewChild('infoWindowContent', {static: false}) + set content(content: ElementRef) { + this._content.next(content.nativeElement); + } + + private readonly _options = new BehaviorSubject({}); + private readonly _position = new BehaviorSubject(undefined); + private readonly _content = new ReplaySubject(1); + + private readonly _listeners: google.maps.MapsEventListener[] = []; + + private readonly _destroy = new Subject(); + + private _map?: google.maps.Map; + private _infoWindow?: google.maps.InfoWindow; + + ngOnInit() { + this._combineOptions().pipe(takeUntil(this._destroy)).subscribe(options => { + if (this._infoWindow) { + this._infoWindow.setOptions(options); + } else { + this._infoWindow = new google.maps.InfoWindow(options); + this._initializeEventHandlers(); + } + }); + } + + ngOnDestroy() { + this._destroy.next(); + this._destroy.complete(); + for (let listener of this._listeners) { + listener.remove(); + } + this.close(); + } + + /** + * See developers.google.com/maps/documentation/javascript/reference/info-window#InfoWindow.close + */ + close() { + if (this._infoWindow) { + this._infoWindow.close(); + } + } + + /** + * See + * developers.google.com/maps/documentation/javascript/reference/info-window#InfoWindow.getContent + */ + getContent(): string|Node { + return this._infoWindow!.getContent(); + } + + /** + * See + * developers.google.com/maps/documentation/javascript/reference/info-window + * #InfoWindow.getPosition + */ + getPosition(): google.maps.LatLng|null { + return this._infoWindow!.getPosition() || null; + } + + /** + * See + * developers.google.com/maps/documentation/javascript/reference/info-window#InfoWindow.getZIndex + */ + getZIndex(): number { + return this._infoWindow!.getZIndex(); + } + + /** + * Opens the MapInfoWindow using the provided MapMarker as the anchor. If the anchor is not set, + * then the position property of the options input is used instead. + */ + open(anchor?: MapMarker) { + const marker = anchor ? anchor._marker : undefined; + if (this._map) { + this._infoWindow!.open(this._map, marker); + } + } + + _setMap(googleMap: google.maps.Map) { + if (!this._map) { + this._map = googleMap; + } + } + + private _combineOptions(): Observable { + return combineLatest(this._options, this._position, this._content) + .pipe(map(([options, position, content]) => { + const combinedOptions: google.maps.InfoWindowOptions = { + ...options, + position: position || options.position, + content, + }; + return combinedOptions; + })); + } + + private _initializeEventHandlers() { + const eventHandlers = new Map>([ + ['closeclick', this.closeclick], + ['content_changed', this.contentChanged], + ['domready', this.domready], + ['position_changed', this.positionChanged], + ['zindex_changed', this.zindexChanged], + ]); + eventHandlers.forEach((eventHandler: EventEmitter, name: string) => { + if (eventHandler.observers.length > 0) { + this._listeners.push(this._infoWindow!.addListener(name, () => { + eventHandler.emit(); + })); + } + }); + } +} diff --git a/src/google-maps/map-marker/map-marker.ts b/src/google-maps/map-marker/map-marker.ts index cbf69381c4c8..5ff6b6b4f8bd 100644 --- a/src/google-maps/map-marker/map-marker.ts +++ b/src/google-maps/map-marker/map-marker.ts @@ -204,9 +204,10 @@ export class MapMarker implements OnInit, OnDestroy { private readonly _listeners: google.maps.MapsEventListener[] = []; - private _marker?: google.maps.Marker; private _hasMap = false; + _marker?: google.maps.Marker; + ngOnInit() { const combinedOptionsChanges = this._combineOptions(); diff --git a/src/google-maps/public-api.ts b/src/google-maps/public-api.ts index bd57b5427d97..a16afad5b4df 100644 --- a/src/google-maps/public-api.ts +++ b/src/google-maps/public-api.ts @@ -7,5 +7,6 @@ */ export {GoogleMap} from './google-map/index'; +export {MapInfoWindow} from './map-info-window/index'; export {MapMarker} from './map-marker/index'; export * from './google-maps-module'; diff --git a/src/google-maps/testing/fake-google-map-utils.ts b/src/google-maps/testing/fake-google-map-utils.ts index f0747e883b29..c4c5818aead1 100644 --- a/src/google-maps/testing/fake-google-map-utils.ts +++ b/src/google-maps/testing/fake-google-map-utils.ts @@ -14,6 +14,7 @@ export interface TestingWindow extends Window { maps: { Map?: jasmine.Spy; Marker?: jasmine.Spy; + InfoWindow?: jasmine.Spy; }; }; } @@ -74,3 +75,29 @@ export function createMarkerConstructorSpy(markerSpy: jasmine.SpyObj { + const infoWindowSpy = jasmine.createSpyObj( + 'google.maps.InfoWindow', + ['addListener', 'close', 'getContent', 'getPosition', 'getZIndex', 'open']); + infoWindowSpy.addListener.and.returnValue({remove: () => {}}); + return infoWindowSpy; +} + +/** Creates a jasmine.Spy to watch for the constructor of a google.maps.InfoWindow */ +export function createInfoWindowConstructorSpy( + infoWindowSpy: jasmine.SpyObj): jasmine.Spy { + const infoWindowConstructorSpy = + jasmine.createSpy('InfoWindow constructor', (_options: google.maps.InfoWindowOptions) => { + return infoWindowSpy; + }); + const testingWindow: TestingWindow = window; + testingWindow.google = { + maps: { + 'InfoWindow': infoWindowConstructorSpy, + }, + }; + return infoWindowConstructorSpy; +} diff --git a/stylelint-config.json b/stylelint-config.json index 161526f6890e..7ad289801351 100644 --- a/stylelint-config.json +++ b/stylelint-config.json @@ -89,7 +89,7 @@ "no-eol-whitespace": true, "max-line-length": 100, "linebreaks": "unix", - "selector-class-pattern": ["^_?(mat-|cdk-|example-|demo-|ng-|mdc-)", { + "selector-class-pattern": ["^_?(mat-|cdk-|example-|demo-|ng-|mdc-|map-)", { "resolveNestedSelectors": true }] } From eb5bf288f9a0bad5cef523506cc19ee7a9d592db Mon Sep 17 00:00:00 2001 From: Matthew Ehrlich Date: Tue, 10 Sep 2019 15:44:48 -0700 Subject: [PATCH 2/7] feat(google-maps): Add map-info-window component Fix type error with getPosition test. --- src/google-maps/map-info-window/map-info-window.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/google-maps/map-info-window/map-info-window.spec.ts b/src/google-maps/map-info-window/map-info-window.spec.ts index 737f293c1b8f..42ab3f900f9a 100644 --- a/src/google-maps/map-info-window/map-info-window.spec.ts +++ b/src/google-maps/map-info-window/map-info-window.spec.ts @@ -151,8 +151,8 @@ describe('MapInfoWindow', () => { infoWindowSpy.getContent.and.returnValue('test content'); expect(infoWindowComponent.getContent()).toBe('test content'); - infoWindowSpy.getPosition.and.returnValue(null); - expect(infoWindowComponent.getPosition()).toBe(null); + infoWindowComponent.getPosition(); + expect(infoWindowSpy.getPosition).toHaveBeenCalled(); infoWindowSpy.getZIndex.and.returnValue(5); expect(infoWindowComponent.getZIndex()).toBe(5); From e3b6f0be924937401a21a5b3ecf3f4cbde0eaa04 Mon Sep 17 00:00:00 2001 From: Matthew Ehrlich Date: Tue, 10 Sep 2019 17:04:00 -0700 Subject: [PATCH 3/7] feat(google-maps): Add map-info-window component Fix import statement for info window by adding /index. --- src/google-maps/google-map/google-map.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/google-maps/google-map/google-map.ts b/src/google-maps/google-map/google-map.ts index a4d0366949f4..ebdeb53b5e51 100644 --- a/src/google-maps/google-map/google-map.ts +++ b/src/google-maps/google-map/google-map.ts @@ -24,7 +24,7 @@ import { import {BehaviorSubject, combineLatest, Observable, Subject} from 'rxjs'; import {map, shareReplay, take, takeUntil} from 'rxjs/operators'; -import {MapInfoWindow} from '../map-info-window'; +import {MapInfoWindow} from '../map-info-window/index'; import {MapMarker} from '../map-marker/index'; interface GoogleMapsWindow extends Window { From e4525fd24ebd5eb26ea4ef90e0beba8ac1cc150e Mon Sep 17 00:00:00 2001 From: Matthew Ehrlich Date: Thu, 19 Sep 2019 13:48:38 -0700 Subject: [PATCH 4/7] feat(google-maps): Add map-info-window component Refactor code so that parent map is injected in info window instead of loading info window in the map. --- src/google-maps/google-map/google-map.spec.ts | 16 ------ src/google-maps/google-map/google-map.ts | 19 +------ src/google-maps/map-info-window/BUILD.bazel | 2 + .../map-info-window/map-info-window.scss | 2 +- .../map-info-window/map-info-window.spec.ts | 51 ++++++++----------- .../map-info-window/map-info-window.ts | 20 ++++---- .../testing/fake-google-map-utils.ts | 14 +++-- 7 files changed, 43 insertions(+), 81 deletions(-) diff --git a/src/google-maps/google-map/google-map.spec.ts b/src/google-maps/google-map/google-map.spec.ts index 90028064f1ad..f3de7fc8ac95 100644 --- a/src/google-maps/google-map/google-map.spec.ts +++ b/src/google-maps/google-map/google-map.spec.ts @@ -2,7 +2,6 @@ import {Component} from '@angular/core'; import {async, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; -import {MapInfoWindow, MapInfoWindowModule} from '../map-info-window/index'; import {MapMarker, MapMarkerModule} from '../map-marker/index'; import { createMapConstructorSpy, @@ -41,7 +40,6 @@ describe('GoogleMap', () => { TestBed.configureTestingModule({ imports: [ GoogleMapModule, - MapInfoWindowModule, MapMarkerModule, ], declarations: [TestApp], @@ -256,19 +254,6 @@ describe('GoogleMap', () => { expect(markerComponent._setMap).toHaveBeenCalledWith(mapSpy); }); - - it('calls setMap on child info window components', () => { - mapSpy = createMapSpy(DEFAULT_OPTIONS); - createMapConstructorSpy(mapSpy).and.callThrough(); - - const fixture = TestBed.createComponent(TestApp); - const infoWindowComponent = - fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; - spyOn(infoWindowComponent, '_setMap').and.callThrough(); - fixture.detectChanges(); - - expect(infoWindowComponent._setMap).toHaveBeenCalledWith(mapSpy); - }); }); @Component({ @@ -282,7 +267,6 @@ describe('GoogleMap', () => { (centerChanged)="handleCenterChanged()" (mapRightclick)="handleRightclick($event)"> - test content `, }) class TestApp { diff --git a/src/google-maps/google-map/google-map.ts b/src/google-maps/google-map/google-map.ts index ebdeb53b5e51..81963bf8f4b2 100644 --- a/src/google-maps/google-map/google-map.ts +++ b/src/google-maps/google-map/google-map.ts @@ -24,7 +24,6 @@ import { import {BehaviorSubject, combineLatest, Observable, Subject} from 'rxjs'; import {map, shareReplay, take, takeUntil} from 'rxjs/operators'; -import {MapInfoWindow} from '../map-info-window/index'; import {MapMarker} from '../map-marker/index'; interface GoogleMapsWindow extends Window { @@ -191,10 +190,8 @@ export class GoogleMap implements OnChanges, OnInit, AfterContentInit, OnDestroy @ContentChildren(MapMarker) _markers: QueryList; - @ContentChildren(MapInfoWindow) _infoWindows: QueryList; - private _mapEl: HTMLElement; - private _googleMap!: UpdatedGoogleMap; + _googleMap!: UpdatedGoogleMap; private _googleMapChanges!: Observable; @@ -241,11 +238,7 @@ export class GoogleMap implements OnChanges, OnInit, AfterContentInit, OnDestroy for (const marker of this._markers.toArray()) { marker._setMap(this._googleMap); } - for (const infoWindow of this._infoWindows.toArray()) { - infoWindow._setMap(this._googleMap); - } this._watchForMarkerChanges(); - this._watchForInfoWindowChanges(); } ngOnDestroy() { @@ -489,14 +482,4 @@ export class GoogleMap implements OnChanges, OnInit, AfterContentInit, OnDestroy } }); } - - private _watchForInfoWindowChanges() { - combineLatest(this._googleMapChanges, this._infoWindows.changes) - .pipe(takeUntil(this._destroy)) - .subscribe(([googleMap, infoWindows]) => { - for (let infoWindow of infoWindows) { - infoWindow._setMap(googleMap); - } - }); - } } diff --git a/src/google-maps/map-info-window/BUILD.bazel b/src/google-maps/map-info-window/BUILD.bazel index 2f12538c3244..f6e0b7dcbe18 100644 --- a/src/google-maps/map-info-window/BUILD.bazel +++ b/src/google-maps/map-info-window/BUILD.bazel @@ -16,6 +16,7 @@ ng_module( ), assets = [":map-info-window.css"], deps = [ + "//src/google-maps/google-map", "//src/google-maps/map-marker", "@npm//@angular/core", "@npm//@types/googlemaps", @@ -36,6 +37,7 @@ ng_test_library( ), deps = [ ":map-info-window", + "//src/google-maps/google-map", "//src/google-maps/map-marker", "//src/google-maps/testing", "@npm//@angular/platform-browser", diff --git a/src/google-maps/map-info-window/map-info-window.scss b/src/google-maps/map-info-window/map-info-window.scss index fbb7ad6aefdd..bf576e2a1429 100644 --- a/src/google-maps/map-info-window/map-info-window.scss +++ b/src/google-maps/map-info-window/map-info-window.scss @@ -1,4 +1,4 @@ -:host { +.map-info-window-container { .map-info-window-content { display: none; } diff --git a/src/google-maps/map-info-window/map-info-window.spec.ts b/src/google-maps/map-info-window/map-info-window.spec.ts index 42ab3f900f9a..6057334cbd1e 100644 --- a/src/google-maps/map-info-window/map-info-window.spec.ts +++ b/src/google-maps/map-info-window/map-info-window.spec.ts @@ -2,25 +2,36 @@ import {Component} from '@angular/core'; import {async, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; +import {DEFAULT_OPTIONS, GoogleMapModule, UpdatedGoogleMap} from '../google-map/index'; import {MapMarker} from '../map-marker/index'; import { createInfoWindowConstructorSpy, createInfoWindowSpy, + createMapConstructorSpy, + createMapSpy, TestingWindow } from '../testing/fake-google-map-utils'; import {MapInfoWindow, MapInfoWindowModule} from './index'; describe('MapInfoWindow', () => { + let mapSpy: jasmine.SpyObj; + beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [MapInfoWindowModule], + imports: [ + GoogleMapModule, + MapInfoWindowModule, + ], declarations: [TestApp], }); })); beforeEach(() => { TestBed.compileComponents(); + + mapSpy = createMapSpy(DEFAULT_OPTIONS); + createMapConstructorSpy(mapSpy).and.callThrough(); }); afterEach(() => { @@ -34,10 +45,6 @@ describe('MapInfoWindow', () => { createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough(); const fixture = TestBed.createComponent(TestApp); - const fakeMap = {} as unknown as google.maps.Map; - const infoWindowComponent = - fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; - infoWindowComponent._setMap(fakeMap); fixture.detectChanges(); expect(infoWindowConstructorSpy).toHaveBeenCalledWith({ @@ -47,7 +54,6 @@ describe('MapInfoWindow', () => { }); it('sets position', () => { - const fakeMap = {} as unknown as google.maps.Map; const position: google.maps.LatLngLiteral = {lat: 5, lng: 7}; const infoWindowSpy = createInfoWindowSpy({position}); const infoWindowConstructorSpy = @@ -55,9 +61,6 @@ describe('MapInfoWindow', () => { const fixture = TestBed.createComponent(TestApp); fixture.componentInstance.position = position; - const infoWindowComponent = - fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; - infoWindowComponent._setMap(fakeMap); fixture.detectChanges(); expect(infoWindowConstructorSpy).toHaveBeenCalledWith({ @@ -67,7 +70,6 @@ describe('MapInfoWindow', () => { }); it('sets options', () => { - const fakeMap = {} as unknown as google.maps.Map; const options: google.maps.InfoWindowOptions = { position: {lat: 3, lng: 5}, maxWidth: 50, @@ -79,9 +81,6 @@ describe('MapInfoWindow', () => { const fixture = TestBed.createComponent(TestApp); fixture.componentInstance.options = options; - const infoWindowComponent = - fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; - infoWindowComponent._setMap(fakeMap); fixture.detectChanges(); expect(infoWindowConstructorSpy).toHaveBeenCalledWith({ @@ -91,7 +90,6 @@ describe('MapInfoWindow', () => { }); it('gives preference to position over options', () => { - const fakeMap = {} as unknown as google.maps.Map; const position: google.maps.LatLngLiteral = {lat: 5, lng: 7}; const options: google.maps.InfoWindowOptions = { position: {lat: 3, lng: 5}, @@ -105,9 +103,6 @@ describe('MapInfoWindow', () => { const fixture = TestBed.createComponent(TestApp); fixture.componentInstance.options = options; fixture.componentInstance.position = position; - const infoWindowComponent = - fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; - infoWindowComponent._setMap(fakeMap); fixture.detectChanges(); expect(infoWindowConstructorSpy).toHaveBeenCalledWith({ @@ -117,8 +112,7 @@ describe('MapInfoWindow', () => { }); }); - it('exposes meethods that change the configuration of the info window', () => { - const fakeMap = {} as unknown as google.maps.Map; + it('exposes methods that change the configuration of the info window', () => { const fakeMarker = {} as unknown as google.maps.Marker; const fakeMarkerComponent = {_marker: fakeMarker} as unknown as MapMarker; const infoWindowSpy = createInfoWindowSpy({}); @@ -127,25 +121,22 @@ describe('MapInfoWindow', () => { const fixture = TestBed.createComponent(TestApp); const infoWindowComponent = fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; - infoWindowComponent._setMap(fakeMap); fixture.detectChanges(); infoWindowComponent.close(); expect(infoWindowSpy.close).toHaveBeenCalled(); infoWindowComponent.open(fakeMarkerComponent); - expect(infoWindowSpy.open).toHaveBeenCalledWith(fakeMap, fakeMarker); + expect(infoWindowSpy.open).toHaveBeenCalledWith(mapSpy, fakeMarker); }); it('exposes methods that provide information about the info window', () => { - const fakeMap = {} as unknown as google.maps.Map; const infoWindowSpy = createInfoWindowSpy({}); createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough(); const fixture = TestBed.createComponent(TestApp); const infoWindowComponent = fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; - infoWindowComponent._setMap(fakeMap); fixture.detectChanges(); infoWindowSpy.getContent.and.returnValue('test content'); @@ -159,14 +150,10 @@ describe('MapInfoWindow', () => { }); it('initializes info window event handlers', () => { - const fakeMap = {} as unknown as google.maps.Map; const infoWindowSpy = createInfoWindowSpy({}); createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough(); const fixture = TestBed.createComponent(TestApp); - const infoWindowComponent = - fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; - infoWindowComponent._setMap(fakeMap); fixture.detectChanges(); expect(infoWindowSpy.addListener).toHaveBeenCalledWith('closeclick', jasmine.any(Function)); @@ -182,9 +169,13 @@ describe('MapInfoWindow', () => { @Component({ selector: 'test-app', - template: `test content`, + template: ` + + test content + + `, }) class TestApp { position?: google.maps.LatLngLiteral; diff --git a/src/google-maps/map-info-window/map-info-window.ts b/src/google-maps/map-info-window/map-info-window.ts index 3c3c733bd3cb..4f1f19a3cac4 100644 --- a/src/google-maps/map-info-window/map-info-window.ts +++ b/src/google-maps/map-info-window/map-info-window.ts @@ -11,6 +11,7 @@ import { import {BehaviorSubject, combineLatest, Observable, ReplaySubject, Subject} from 'rxjs'; import {map, takeUntil} from 'rxjs/operators'; +import {GoogleMap} from '../google-map/index'; import {MapMarker} from '../map-marker/index'; /** @@ -19,8 +20,10 @@ import {MapMarker} from '../map-marker/index'; */ @Component({ selector: 'map-info-window', - template: `
- + template: `
+
+ +
`, styleUrls: ['map-info-window.css'], }) @@ -81,9 +84,10 @@ export class MapInfoWindow implements OnInit, OnDestroy { private readonly _destroy = new Subject(); - private _map?: google.maps.Map; private _infoWindow?: google.maps.InfoWindow; + constructor(private readonly googleMap: GoogleMap) {} + ngOnInit() { this._combineOptions().pipe(takeUntil(this._destroy)).subscribe(options => { if (this._infoWindow) { @@ -144,14 +148,8 @@ export class MapInfoWindow implements OnInit, OnDestroy { */ open(anchor?: MapMarker) { const marker = anchor ? anchor._marker : undefined; - if (this._map) { - this._infoWindow!.open(this._map, marker); - } - } - - _setMap(googleMap: google.maps.Map) { - if (!this._map) { - this._map = googleMap; + if (this.googleMap._googleMap) { + this._infoWindow!.open(this.googleMap._googleMap, marker); } } diff --git a/src/google-maps/testing/fake-google-map-utils.ts b/src/google-maps/testing/fake-google-map-utils.ts index c4c5818aead1..5f5988efc736 100644 --- a/src/google-maps/testing/fake-google-map-utils.ts +++ b/src/google-maps/testing/fake-google-map-utils.ts @@ -94,10 +94,14 @@ export function createInfoWindowConstructorSpy( return infoWindowSpy; }); const testingWindow: TestingWindow = window; - testingWindow.google = { - maps: { - 'InfoWindow': infoWindowConstructorSpy, - }, - }; + if (testingWindow.google && testingWindow.google.maps) { + testingWindow.google.maps['InfoWindow'] = infoWindowConstructorSpy; + } else { + testingWindow.google = { + maps: { + 'InfoWindow': infoWindowConstructorSpy, + }, + }; + } return infoWindowConstructorSpy; } From c3103dc76a0ffb290f536673c89b2f5cf7347ac8 Mon Sep 17 00:00:00 2001 From: Matthew Ehrlich Date: Thu, 19 Sep 2019 16:41:20 -0700 Subject: [PATCH 5/7] feat(google-maps): Add map-info-window component Remove BUILD files to conform with the recent fix for the npm package. --- src/google-maps/BUILD.bazel | 2 + src/google-maps/map-info-window/BUILD.bazel | 55 ------------------- .../map-info-window/assets/BUILD.bazel | 8 +++ .../{ => assets}/map-info-window.scss | 0 .../map-info-window/map-info-window.ts | 2 +- 5 files changed, 11 insertions(+), 56 deletions(-) delete mode 100644 src/google-maps/map-info-window/BUILD.bazel create mode 100644 src/google-maps/map-info-window/assets/BUILD.bazel rename src/google-maps/map-info-window/{ => assets}/map-info-window.scss (100%) diff --git a/src/google-maps/BUILD.bazel b/src/google-maps/BUILD.bazel index 2437ae7a759e..a6b033f27dfd 100644 --- a/src/google-maps/BUILD.bazel +++ b/src/google-maps/BUILD.bazel @@ -9,10 +9,12 @@ ng_module( ["**/*.ts"], exclude = ["**/*.spec.ts"], ), + assets = ["//src/google-maps/map-info-window/assets:map-info-window.css"], module_name = "@angular/google-maps", deps = [ "@npm//@angular/core", "@npm//@types/googlemaps", + "@npm//rxjs", ], ) diff --git a/src/google-maps/map-info-window/BUILD.bazel b/src/google-maps/map-info-window/BUILD.bazel deleted file mode 100644 index f6e0b7dcbe18..000000000000 --- a/src/google-maps/map-info-window/BUILD.bazel +++ /dev/null @@ -1,55 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") -load( - "//tools:defaults.bzl", - "ng_module", - "ng_test_library", - "ng_web_test_suite", -) - -ng_module( - name = "map-info-window", - srcs = glob( - ["**/*.ts"], - exclude = ["**/*.spec.ts"], - ), - assets = [":map-info-window.css"], - deps = [ - "//src/google-maps/google-map", - "//src/google-maps/map-marker", - "@npm//@angular/core", - "@npm//@types/googlemaps", - "@npm//rxjs", - ], -) - -sass_binary( - name = "map-info-window_scss", - src = "map-info-window.scss", -) - -ng_test_library( - name = "unit_test_sources", - srcs = glob( - ["**/*.spec.ts"], - exclude = ["**/*.e2e.spec.ts"], - ), - deps = [ - ":map-info-window", - "//src/google-maps/google-map", - "//src/google-maps/map-marker", - "//src/google-maps/testing", - "@npm//@angular/platform-browser", - ], -) - -ng_web_test_suite( - name = "unit_tests", - deps = [":unit_test_sources"], -) - -filegroup( - name = "source-files", - srcs = glob(["**/*.ts"]), -) diff --git a/src/google-maps/map-info-window/assets/BUILD.bazel b/src/google-maps/map-info-window/assets/BUILD.bazel new file mode 100644 index 000000000000..5d71feba3e6d --- /dev/null +++ b/src/google-maps/map-info-window/assets/BUILD.bazel @@ -0,0 +1,8 @@ +package(default_visibility = ["//visibility:public"]) + +load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") + +sass_binary( + name = "map-info-window_scss", + src = "map-info-window.scss", +) diff --git a/src/google-maps/map-info-window/map-info-window.scss b/src/google-maps/map-info-window/assets/map-info-window.scss similarity index 100% rename from src/google-maps/map-info-window/map-info-window.scss rename to src/google-maps/map-info-window/assets/map-info-window.scss diff --git a/src/google-maps/map-info-window/map-info-window.ts b/src/google-maps/map-info-window/map-info-window.ts index 4f1f19a3cac4..5d5411401ecf 100644 --- a/src/google-maps/map-info-window/map-info-window.ts +++ b/src/google-maps/map-info-window/map-info-window.ts @@ -25,7 +25,7 @@ import {MapMarker} from '../map-marker/index';
`, - styleUrls: ['map-info-window.css'], + styleUrls: ['./assets/map-info-window.css'], }) export class MapInfoWindow implements OnInit, OnDestroy { @Input() From c2e860d2407af48fb9100c24e46c216c4dea99b0 Mon Sep 17 00:00:00 2001 From: Matthew Ehrlich Date: Fri, 20 Sep 2019 10:09:51 -0700 Subject: [PATCH 6/7] feat(google-maps): Add map-info-window component Fix lint errors for features missing in the info window component. Add comment to style to explain its function. --- .../map-info-window/assets/map-info-window.scss | 2 ++ .../map-info-window/map-info-window.ts | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/google-maps/map-info-window/assets/map-info-window.scss b/src/google-maps/map-info-window/assets/map-info-window.scss index bf576e2a1429..e75b0fec2648 100644 --- a/src/google-maps/map-info-window/assets/map-info-window.scss +++ b/src/google-maps/map-info-window/assets/map-info-window.scss @@ -1,3 +1,5 @@ +// Style to prevent info window content from displaying in the DOM, but instead +// keeps it in the map. .map-info-window-container { .map-info-window-content { display: none; diff --git a/src/google-maps/map-info-window/map-info-window.ts b/src/google-maps/map-info-window/map-info-window.ts index 5d5411401ecf..1f9f509c4421 100644 --- a/src/google-maps/map-info-window/map-info-window.ts +++ b/src/google-maps/map-info-window/map-info-window.ts @@ -1,4 +1,13 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + import { + ChangeDetectionStrategy, Component, ElementRef, EventEmitter, @@ -6,7 +15,8 @@ import { OnDestroy, OnInit, Output, - ViewChild + ViewChild, + ViewEncapsulation, } from '@angular/core'; import {BehaviorSubject, combineLatest, Observable, ReplaySubject, Subject} from 'rxjs'; import {map, takeUntil} from 'rxjs/operators'; @@ -19,6 +29,7 @@ import {MapMarker} from '../map-marker/index'; * @see developers.google.com/maps/documentation/javascript/reference/info-window */ @Component({ + moduleId: module.id, selector: 'map-info-window', template: `
@@ -26,6 +37,8 @@ import {MapMarker} from '../map-marker/index';
`, styleUrls: ['./assets/map-info-window.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, }) export class MapInfoWindow implements OnInit, OnDestroy { @Input() From ceb3a343f19abdf7b67b63d97a1e4ee1b95a73a1 Mon Sep 17 00:00:00 2001 From: Matthew Ehrlich Date: Mon, 23 Sep 2019 15:19:29 -0700 Subject: [PATCH 7/7] feat(google-maps): Add map-info-window directive Change MapInfoWindow from a component to a directive. --- src/google-maps/BUILD.bazel | 1 - .../map-info-window/assets/BUILD.bazel | 8 ---- .../assets/map-info-window.scss | 7 --- .../map-info-window/map-info-window.spec.ts | 8 ++-- .../map-info-window/map-info-window.ts | 46 ++++++------------- 5 files changed, 19 insertions(+), 51 deletions(-) delete mode 100644 src/google-maps/map-info-window/assets/BUILD.bazel delete mode 100644 src/google-maps/map-info-window/assets/map-info-window.scss diff --git a/src/google-maps/BUILD.bazel b/src/google-maps/BUILD.bazel index a6b033f27dfd..0ee91e2d76bf 100644 --- a/src/google-maps/BUILD.bazel +++ b/src/google-maps/BUILD.bazel @@ -9,7 +9,6 @@ ng_module( ["**/*.ts"], exclude = ["**/*.spec.ts"], ), - assets = ["//src/google-maps/map-info-window/assets:map-info-window.css"], module_name = "@angular/google-maps", deps = [ "@npm//@angular/core", diff --git a/src/google-maps/map-info-window/assets/BUILD.bazel b/src/google-maps/map-info-window/assets/BUILD.bazel deleted file mode 100644 index 5d71feba3e6d..000000000000 --- a/src/google-maps/map-info-window/assets/BUILD.bazel +++ /dev/null @@ -1,8 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") - -sass_binary( - name = "map-info-window_scss", - src = "map-info-window.scss", -) diff --git a/src/google-maps/map-info-window/assets/map-info-window.scss b/src/google-maps/map-info-window/assets/map-info-window.scss deleted file mode 100644 index e75b0fec2648..000000000000 --- a/src/google-maps/map-info-window/assets/map-info-window.scss +++ /dev/null @@ -1,7 +0,0 @@ -// Style to prevent info window content from displaying in the DOM, but instead -// keeps it in the map. -.map-info-window-container { - .map-info-window-content { - display: none; - } -} diff --git a/src/google-maps/map-info-window/map-info-window.spec.ts b/src/google-maps/map-info-window/map-info-window.spec.ts index 6057334cbd1e..c7f488434c37 100644 --- a/src/google-maps/map-info-window/map-info-window.spec.ts +++ b/src/google-maps/map-info-window/map-info-window.spec.ts @@ -119,8 +119,8 @@ describe('MapInfoWindow', () => { createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough(); const fixture = TestBed.createComponent(TestApp); - const infoWindowComponent = - fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; + const infoWindowComponent = fixture.debugElement.query(By.directive( + MapInfoWindow))!.injector.get(MapInfoWindow); fixture.detectChanges(); infoWindowComponent.close(); @@ -135,8 +135,8 @@ describe('MapInfoWindow', () => { createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough(); const fixture = TestBed.createComponent(TestApp); - const infoWindowComponent = - fixture.debugElement.query(By.directive(MapInfoWindow)).componentInstance; + const infoWindowComponent = fixture.debugElement.query(By.directive( + MapInfoWindow))!.injector.get(MapInfoWindow); fixture.detectChanges(); infoWindowSpy.getContent.and.returnValue('test content'); diff --git a/src/google-maps/map-info-window/map-info-window.ts b/src/google-maps/map-info-window/map-info-window.ts index 1f9f509c4421..11136f8c82e7 100644 --- a/src/google-maps/map-info-window/map-info-window.ts +++ b/src/google-maps/map-info-window/map-info-window.ts @@ -7,18 +7,15 @@ */ import { - ChangeDetectionStrategy, - Component, + Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, - ViewChild, - ViewEncapsulation, } from '@angular/core'; -import {BehaviorSubject, combineLatest, Observable, ReplaySubject, Subject} from 'rxjs'; +import {BehaviorSubject, combineLatest, Observable, Subject} from 'rxjs'; import {map, takeUntil} from 'rxjs/operators'; import {GoogleMap} from '../google-map/index'; @@ -28,17 +25,9 @@ import {MapMarker} from '../map-marker/index'; * Angular component that renders a Google Maps info window via the Google Maps JavaScript API. * @see developers.google.com/maps/documentation/javascript/reference/info-window */ -@Component({ - moduleId: module.id, +@Directive({ selector: 'map-info-window', - template: `
-
- -
-
`, - styleUrls: ['./assets/map-info-window.css'], - changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None, + host: {'style': 'display: none'}, }) export class MapInfoWindow implements OnInit, OnDestroy { @Input() @@ -84,14 +73,8 @@ export class MapInfoWindow implements OnInit, OnDestroy { */ @Output() zindexChanged = new EventEmitter(); - @ViewChild('infoWindowContent', {static: false}) - set content(content: ElementRef) { - this._content.next(content.nativeElement); - } - private readonly _options = new BehaviorSubject({}); private readonly _position = new BehaviorSubject(undefined); - private readonly _content = new ReplaySubject(1); private readonly _listeners: google.maps.MapsEventListener[] = []; @@ -99,7 +82,8 @@ export class MapInfoWindow implements OnInit, OnDestroy { private _infoWindow?: google.maps.InfoWindow; - constructor(private readonly googleMap: GoogleMap) {} + constructor(private readonly googleMap: GoogleMap, private _elementRef: ElementRef) { + } ngOnInit() { this._combineOptions().pipe(takeUntil(this._destroy)).subscribe(options => { @@ -162,20 +146,20 @@ export class MapInfoWindow implements OnInit, OnDestroy { open(anchor?: MapMarker) { const marker = anchor ? anchor._marker : undefined; if (this.googleMap._googleMap) { + this._elementRef.nativeElement.style.display = ''; this._infoWindow!.open(this.googleMap._googleMap, marker); } } private _combineOptions(): Observable { - return combineLatest(this._options, this._position, this._content) - .pipe(map(([options, position, content]) => { - const combinedOptions: google.maps.InfoWindowOptions = { - ...options, - position: position || options.position, - content, - }; - return combinedOptions; - })); + return combineLatest(this._options, this._position).pipe(map(([options, position]) => { + const combinedOptions: google.maps.InfoWindowOptions = { + ...options, + position: position || options.position, + content: this._elementRef.nativeElement, + }; + return combinedOptions; + })); } private _initializeEventHandlers() {