-
Notifications
You must be signed in to change notification settings - Fork 6.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(google-maps): Add MapDirectionsRenderer and MapDirectionsService (…
…#21736) * feat(google-maps): Add MapDirectionsRenderer and MapDirectionsService Add the MapDirectionsService which wraps google.maps.DirectionsService to calculate routes between two points and the MapDirectionsRenderer component which allows these routes to be displayed on the Google Map. * feat(google-maps): Add MapDirectionsRenderer and MapDirectionsService Update public api guard with new component and service. Make minor optimizations with MapDirectionsService. * feat(google-maps): Add MapDirectionsRenderer and MapDirectionsService Add comments explaining how secret API key is loaded by dev-server.
- Loading branch information
Showing
15 changed files
with
588 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,3 +43,4 @@ testem.log | |
*.log | ||
.ng-dev.user* | ||
.husky/_ | ||
/src/dev-app/google-maps-api-key.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# MapDirectionsRenderer | ||
|
||
The `MapDirectionsRenderer` component wraps the [`google.maps.DirectionsRenderer` class](https://developers.google.com/maps/documentation/javascript/reference/directions#DirectionsRenderer) from the Google Maps JavaScript API. This can easily be used with the `MapDirectionsService` that wraps [`google.maps.DirectionsService`](https://developers.google.com/maps/documentation/javascript/reference/directions#DirectionsService) which is designed to be used with Angular by returning an `Observable` response and works inside the Angular Zone. | ||
|
||
The `MapDirectionsService`, like the `google.maps.DirectionsService`, has a single method, `route`. Normally, the `google.maps.DirectionsService` takes two arguments, a `google.maps.DirectionsRequest` and a callback that takes the `google.maps.DirectionsResult` and `google.maps.DirectionsStatus` as arguments. The `MapDirectionsService` route method takes takes the `google.maps.DirectionsRequest` as the single argument, and returns an `Observable` of a `MapDirectionsResponse`, which is an interface defined as follows: | ||
|
||
```typescript | ||
export interface MapDirectionsResponse { | ||
status: google.maps.DirectionsStatus; | ||
result?: google.maps.DirectionsResult; | ||
} | ||
``` | ||
|
||
The most common usecase for the component and class would be to use the `MapDirectionsService` to request a route between two points on the map, and then render them on the map using the `MapDirectionsRenderer`. | ||
|
||
## Loading the Library | ||
|
||
Using the `MapDirectionsService` requires the Directions API to be enabled in Google Cloud Console on the same project as the one set up for the Google Maps JavaScript API, and requires an API key that has billing enabled. See [here](https://developers.google.com/maps/documentation/javascript/directions#GetStarted) for details. | ||
|
||
## Example | ||
|
||
```typescript | ||
// google-maps-demo.component.ts | ||
import {Component} from '@angular/core'; | ||
|
||
@Component({ | ||
selector: 'google-map-demo', | ||
templateUrl: 'google-map-demo.html', | ||
}) | ||
export class GoogleMapDemo { | ||
center: google.maps.LatLngLiteral = {lat: 24, lng: 12}; | ||
zoom = 4; | ||
|
||
readonly directionsResults$: Observable<google.maps.DirectionsResult|undefined>; | ||
|
||
constructor(mapDirectionsService: MapDirectionsService) { | ||
const request: google.maps.DirectionsRequest = { | ||
destination: {lat: 12, lng: 4}, | ||
origin: {lat: 13, lng: 5}, | ||
travelMode: google.maps.TravelMode.DRIVING, | ||
}; | ||
this.directionsResults$ = mapDirectionsService.route(request).pipe(map(response => response.result)); | ||
} | ||
} | ||
``` | ||
|
||
```html | ||
<!-- google-maps-demo.component.html --> | ||
<google-map height="400px" | ||
width="750px" | ||
[center]="center" | ||
[zoom]="zoom"> | ||
<map-directions-renderer *ngIf="(directionsResults$ | async) as directionsResults" | ||
[directions]="directionsResults"></map-directions-renderer> | ||
</google-map> | ||
``` |
135 changes: 135 additions & 0 deletions
135
src/google-maps/map-directions-renderer/map-directions-renderer.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import {Component, ViewChild} from '@angular/core'; | ||
import {TestBed, waitForAsync} from '@angular/core/testing'; | ||
import {By} from '@angular/platform-browser'; | ||
import {MapDirectionsRenderer} from './map-directions-renderer'; | ||
import {DEFAULT_OPTIONS} from '../google-map/google-map'; | ||
import {GoogleMapsModule} from '../google-maps-module'; | ||
import { | ||
createDirectionsRendererConstructorSpy, | ||
createDirectionsRendererSpy, | ||
createMapConstructorSpy, | ||
createMapSpy | ||
} from '../testing/fake-google-map-utils'; | ||
|
||
const DEFAULT_DIRECTIONS: google.maps.DirectionsResult = { | ||
geocoded_waypoints: [], | ||
routes: [], | ||
}; | ||
|
||
describe('MapDirectionsRenderer', () => { | ||
let mapSpy: jasmine.SpyObj<google.maps.Map>; | ||
|
||
beforeEach(waitForAsync(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [GoogleMapsModule], | ||
declarations: [TestApp], | ||
}); | ||
})); | ||
|
||
beforeEach(() => { | ||
TestBed.compileComponents(); | ||
|
||
mapSpy = createMapSpy(DEFAULT_OPTIONS); | ||
createMapConstructorSpy(mapSpy).and.callThrough(); | ||
}); | ||
|
||
afterEach(() => { | ||
(window.google as any) = undefined; | ||
}); | ||
|
||
it('initializes a Google Maps DirectionsRenderer', () => { | ||
const directionsRendererSpy = createDirectionsRendererSpy({directions: DEFAULT_DIRECTIONS}); | ||
const directionsRendererConstructorSpy = | ||
createDirectionsRendererConstructorSpy(directionsRendererSpy).and.callThrough(); | ||
|
||
const fixture = TestBed.createComponent(TestApp); | ||
fixture.componentInstance.options = {directions: DEFAULT_DIRECTIONS}; | ||
fixture.detectChanges(); | ||
|
||
expect(directionsRendererConstructorSpy) | ||
.toHaveBeenCalledWith({directions: DEFAULT_DIRECTIONS, map: jasmine.any(Object)}); | ||
expect(directionsRendererSpy.setMap).toHaveBeenCalledWith(mapSpy); | ||
}); | ||
|
||
it('sets directions from directions input', () => { | ||
const directionsRendererSpy = createDirectionsRendererSpy({directions: DEFAULT_DIRECTIONS}); | ||
const directionsRendererConstructorSpy = | ||
createDirectionsRendererConstructorSpy(directionsRendererSpy).and.callThrough(); | ||
|
||
const fixture = TestBed.createComponent(TestApp); | ||
fixture.componentInstance.directions = DEFAULT_DIRECTIONS; | ||
fixture.detectChanges(); | ||
|
||
expect(directionsRendererConstructorSpy) | ||
.toHaveBeenCalledWith({directions: DEFAULT_DIRECTIONS, map: jasmine.any(Object)}); | ||
expect(directionsRendererSpy.setMap).toHaveBeenCalledWith(mapSpy); | ||
}); | ||
|
||
it('gives precedence to directions over options', () => { | ||
const updatedDirections: google.maps.DirectionsResult = { | ||
geocoded_waypoints: [{partial_match: false, place_id: 'test', types: []}], | ||
routes: [], | ||
}; | ||
const directionsRendererSpy = createDirectionsRendererSpy({directions: updatedDirections}); | ||
const directionsRendererConstructorSpy = | ||
createDirectionsRendererConstructorSpy(directionsRendererSpy).and.callThrough(); | ||
|
||
const fixture = TestBed.createComponent(TestApp); | ||
fixture.componentInstance.options = {directions: DEFAULT_DIRECTIONS}; | ||
fixture.componentInstance.directions = updatedDirections; | ||
fixture.detectChanges(); | ||
|
||
expect(directionsRendererConstructorSpy) | ||
.toHaveBeenCalledWith({directions: updatedDirections, map: jasmine.any(Object)}); | ||
expect(directionsRendererSpy.setMap).toHaveBeenCalledWith(mapSpy); | ||
}); | ||
|
||
it('exposes methods that provide information from the DirectionsRenderer', () => { | ||
const directionsRendererSpy = createDirectionsRendererSpy({}); | ||
createDirectionsRendererConstructorSpy(directionsRendererSpy).and.callThrough(); | ||
|
||
const fixture = TestBed.createComponent(TestApp); | ||
|
||
const directionsRendererComponent = | ||
fixture.debugElement.query(By.directive(MapDirectionsRenderer))! | ||
.injector.get<MapDirectionsRenderer>(MapDirectionsRenderer); | ||
fixture.detectChanges(); | ||
|
||
directionsRendererSpy.getDirections.and.returnValue(DEFAULT_DIRECTIONS); | ||
expect(directionsRendererComponent.getDirections()).toBe(DEFAULT_DIRECTIONS); | ||
|
||
directionsRendererComponent.getPanel(); | ||
expect(directionsRendererSpy.getPanel).toHaveBeenCalled(); | ||
|
||
directionsRendererSpy.getRouteIndex.and.returnValue(10); | ||
expect(directionsRendererComponent.getRouteIndex()).toBe(10); | ||
}); | ||
|
||
it('initializes DirectionsRenderer event handlers', () => { | ||
const directionsRendererSpy = createDirectionsRendererSpy({}); | ||
createDirectionsRendererConstructorSpy(directionsRendererSpy).and.callThrough(); | ||
|
||
const fixture = TestBed.createComponent(TestApp); | ||
fixture.detectChanges(); | ||
|
||
expect(directionsRendererSpy.addListener) | ||
.toHaveBeenCalledWith('directions_changed', jasmine.any(Function)); | ||
}); | ||
}); | ||
|
||
@Component({ | ||
selector: 'test-app', | ||
template: `<google-map> | ||
<map-directions-renderer [options]="options" | ||
[directions]="directions" | ||
(directionsChanged)="handleDirectionsChanged()"> | ||
</map-directions-renderer> | ||
</google-map>`, | ||
}) | ||
class TestApp { | ||
@ViewChild(MapDirectionsRenderer) directionsRenderer: MapDirectionsRenderer; | ||
options?: google.maps.DirectionsRendererOptions; | ||
directions?: google.maps.DirectionsResult; | ||
|
||
handleDirectionsChanged() {} | ||
} |
Oops, something went wrong.