Skip to content

Commit

Permalink
feat(core): support auto fitBounds
Browse files Browse the repository at this point in the history
This adds support for auto fitBounds features. Right now, only markers and custom components are supported. We'll add support for other core components later
  • Loading branch information
sebholstein committed Sep 22, 2018
1 parent 89b6e5c commit 4d3103c
Show file tree
Hide file tree
Showing 13 changed files with 453 additions and 23 deletions.
3 changes: 3 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"singleQuote": true
}
19 changes: 19 additions & 0 deletions docs/content/guides/auto-fit-bounds.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
+++
date = "2018-09-22T09:31:00-01:00"
draft = false
title = "Enable auto fit bounds"

+++

Angular Google Maps (AGM) has an auto fit bounds feature, that adds all containing components to the bounds of the map.
To enable it, set the `fitBounds` input of `agm-map` to `true` and add the `agmFitBounds` input/directive to `true` for all components
you want to include in the bounds of the map.

```html
<agm-map [fitBounds]="true">
<agm-marker [agmFitBounds]="true"></agm-marker>

<!-- not included -->
<agm-marker [agmFitBounds]="false"></agm-marker>
</agm-map>
```
54 changes: 54 additions & 0 deletions docs/content/guides/implement-auto-fit-bounds.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
+++
date = "2018-09-22T09:31:00-01:00"
draft = false
title = "Support auto fit bounds for custom components"

+++

Angular Google Maps (AGM) has an auto fit bounds feature, that adds all containing components to the bounds of the map:

```html
<agm-map [fitBounds]="true">
<agm-marker [agmFitBounds]="true"></agm-marker>
</agm-map>
```

Let`s say we have a custom component, that extends the features of AGM:


```html
<agm-map [fitBounds]="true">
<my-custom-component></my-custom-component>
</agm-map>
```

To add support the auto fit bounds feature for `<my-custom-component>`, we have to implement the `FitBoundsAccessor`:

```typescript
import { FitBoundsAccessor, FitBoundsDetails } from '@agm/core';
import { forwardRef, Component } from '@angular/core';

@Component({
selector: 'my-custom-component',
template: '',
providers: [
{provide: FitBoundsAccessor, useExisting: forwardRef(() => MyCustomComponent)}
],
})
export class MyCustomComponent implements FitBoundsAccessor {
**
* This is a method you need to implement with your custom logic.
*/
getFitBoundsDetails$(): Observable<FitBoundsDetails> {
return ...;
}
}
```

The last step is to change your template. Add the `agmFitBounds` input/directive and set the value to true:

```html
<agm-map [fitBounds]="true">
<my-custom-component [agmFitBounds]="true"></my-custom-component>
</agm-map>
```
3 changes: 2 additions & 1 deletion packages/core/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {LazyMapsAPILoader} from './services/maps-api-loader/lazy-maps-api-loader
import {LAZY_MAPS_API_CONFIG, LazyMapsAPILoaderConfigLiteral} from './services/maps-api-loader/lazy-maps-api-loader';
import {MapsAPILoader} from './services/maps-api-loader/maps-api-loader';
import {BROWSER_GLOBALS_PROVIDERS} from './utils/browser-globals';
import {AgmFitBounds} from '@agm/core/directives/fit-bounds';

/**
* @internal
Expand All @@ -21,7 +22,7 @@ export function coreDirectives() {
return [
AgmMap, AgmMarker, AgmInfoWindow, AgmCircle, AgmRectangle,
AgmPolygon, AgmPolyline, AgmPolylinePoint, AgmKmlLayer,
AgmDataLayer
AgmDataLayer, AgmFitBounds
];
}

Expand Down
1 change: 1 addition & 0 deletions packages/core/directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export {AgmMarker} from './directives/marker';
export {AgmPolygon} from './directives/polygon';
export {AgmPolyline} from './directives/polyline';
export {AgmPolylinePoint} from './directives/polyline-point';
export {AgmFitBounds} from './directives/fit-bounds';
78 changes: 78 additions & 0 deletions packages/core/directives/fit-bounds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Directive, OnInit, Self, OnDestroy, Input, OnChanges, SimpleChanges } from '@angular/core';
import { FitBoundsService, FitBoundsAccessor, FitBoundsDetails } from '../services/fit-bounds';
import { Subscription, Subject } from 'rxjs';
import { distinctUntilChanged, takeUntil } from 'rxjs/operators';
import { LatLng, LatLngLiteral } from '@agm/core';

/**
* Adds the given directive to the auto fit bounds feature when the value is true.
* To make it work with you custom AGM component, you also have to implement the {@link FitBoundsAccessor} abstract class.
* @example
* <agm-marker [agmFitBounds]="true"></agm-marker>
*/
@Directive({
selector: '[agmFitBounds]'
})
export class AgmFitBounds implements OnInit, OnDestroy, OnChanges {
/**
* If the value is true, the element gets added to the bounds of the map.
* Default: true.
*/
@Input() agmFitBounds: boolean = true;

private _destroyed$: Subject<void> = new Subject<void>();
private _latestFitBoundsDetails: FitBoundsDetails | null = null;

constructor(
@Self() private readonly _fitBoundsAccessor: FitBoundsAccessor,
private readonly _fitBoundsService: FitBoundsService
) {}

/**
* @internal
*/
ngOnChanges(changes: SimpleChanges) {
this._updateBounds();
}

/**
* @internal
*/
ngOnInit() {
this._fitBoundsAccessor
.getFitBoundsDetails$()
.pipe(
distinctUntilChanged(
(x: FitBoundsDetails, y: FitBoundsDetails) =>
x.latLng.lat === y.latLng.lng
),
takeUntil(this._destroyed$)
)
.subscribe(details => this._updateBounds(details));
}

private _updateBounds(newFitBoundsDetails?: FitBoundsDetails) {
if (newFitBoundsDetails) {
this._latestFitBoundsDetails = newFitBoundsDetails;
}
if (!this._latestFitBoundsDetails) {
return;
}
if (this.agmFitBounds) {
this._fitBoundsService.addToBounds(this._latestFitBoundsDetails.latLng);
} else {
this._fitBoundsService.removeFromBounds(this._latestFitBoundsDetails.latLng);
}
}

/**
* @internal
*/
ngOnDestroy() {
this._destroyed$.next();
this._destroyed$.complete();
if (this._latestFitBoundsDetails !== null) {
this._fitBoundsService.removeFromBounds(this._latestFitBoundsDetails.latLng);
}
}
}
54 changes: 47 additions & 7 deletions packages/core/directives/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import {PolygonManager} from '../services/managers/polygon-manager';
import {PolylineManager} from '../services/managers/polyline-manager';
import {KmlLayerManager} from './../services/managers/kml-layer-manager';
import {DataLayerManager} from './../services/managers/data-layer-manager';
import {FitBoundsService} from '../services/fit-bounds';

declare var google: any;

/**
* AgmMap renders a Google Map.
Expand Down Expand Up @@ -43,7 +46,8 @@ import {DataLayerManager} from './../services/managers/data-layer-manager';
selector: 'agm-map',
providers: [
GoogleMapsAPIWrapper, MarkerManager, InfoWindowManager, CircleManager, RectangleManager,
PolylineManager, PolygonManager, KmlLayerManager, DataLayerManager
PolylineManager, PolygonManager, KmlLayerManager, DataLayerManager, DataLayerManager,
FitBoundsService
],
host: {
// todo: deprecated - we will remove it with the next version
Expand Down Expand Up @@ -180,8 +184,9 @@ export class AgmMap implements OnChanges, OnInit, OnDestroy {

/**
* Sets the viewport to contain the given bounds.
* If this option to `true`, the bounds get automatically computed from all elements that use the {@link AgmFitBounds} directive.
*/
@Input() fitBounds: LatLngBoundsLiteral|LatLngBounds = null;
@Input() fitBounds: LatLngBoundsLiteral|LatLngBounds|boolean = false;

/**
* The initial enabled/disabled state of the Scale control. This is disabled by default.
Expand Down Expand Up @@ -267,6 +272,7 @@ export class AgmMap implements OnChanges, OnInit, OnDestroy {
];

private _observableSubscriptions: Subscription[] = [];
private _fitBoundsSubscription: Subscription;

/**
* This event emitter gets emitted when the user clicks on the map (but not when they click on a
Expand Down Expand Up @@ -317,7 +323,7 @@ export class AgmMap implements OnChanges, OnInit, OnDestroy {
*/
@Output() mapReady: EventEmitter<any> = new EventEmitter<any>();

constructor(private _elem: ElementRef, private _mapsWrapper: GoogleMapsAPIWrapper) {}
constructor(private _elem: ElementRef, private _mapsWrapper: GoogleMapsAPIWrapper, protected _fitBoundsService: FitBoundsService) {}

/** @internal */
ngOnInit() {
Expand Down Expand Up @@ -378,6 +384,9 @@ export class AgmMap implements OnChanges, OnInit, OnDestroy {

// remove all listeners from the map instance
this._mapsWrapper.clearInstanceListeners();
if (this._fitBoundsSubscription) {
this._fitBoundsSubscription.unsubscribe();
}
}

/* @internal */
Expand Down Expand Up @@ -417,13 +426,13 @@ export class AgmMap implements OnChanges, OnInit, OnDestroy {

private _updatePosition(changes: SimpleChanges) {
if (changes['latitude'] == null && changes['longitude'] == null &&
changes['fitBounds'] == null) {
!changes['fitBounds']) {
// no position update needed
return;
}

// we prefer fitBounds in changes
if (changes['fitBounds'] && this.fitBounds != null) {
if ('fitBounds' in changes) {
this._fitBounds();
return;
}
Expand All @@ -447,11 +456,42 @@ export class AgmMap implements OnChanges, OnInit, OnDestroy {
}

private _fitBounds() {
switch (this.fitBounds) {
case true:
this._subscribeToFitBoundsUpdates();
break;
case false:
if (this._fitBoundsSubscription) {
this._fitBoundsSubscription.unsubscribe();
}
break;
default:
this._updateBounds(this.fitBounds);
}
}

private _subscribeToFitBoundsUpdates() {
this._fitBoundsSubscription = this._fitBoundsService.getBounds$().subscribe(b => this._updateBounds(b));
}

protected _updateBounds(bounds: LatLngBounds|LatLngBoundsLiteral) {
if (this._isLatLngBoundsLiteral(bounds)) {
const newBounds = <LatLngBounds>google.maps.LatLngBounds();
newBounds.union(bounds);
bounds = newBounds;
}
if (bounds.isEmpty()) {
return;
}
if (this.usePanning) {
this._mapsWrapper.panToBounds(this.fitBounds);
this._mapsWrapper.panToBounds(bounds);
return;
}
this._mapsWrapper.fitBounds(this.fitBounds);
this._mapsWrapper.fitBounds(bounds);
}

private _isLatLngBoundsLiteral(bounds: LatLngBounds|LatLngBoundsLiteral): bounds is LatLngBoundsLiteral {
return (<any>bounds).extend === undefined;
}

private _handleMapCenterChange() {
Expand Down
37 changes: 26 additions & 11 deletions packages/core/directives/marker.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import {Directive, EventEmitter, OnChanges, OnDestroy, SimpleChange,
AfterContentInit, ContentChildren, QueryList, Input, Output
} from '@angular/core';
import {Subscription} from 'rxjs';

import {MouseEvent} from '../map-types';
import { AfterContentInit, ContentChildren, Directive, EventEmitter, Input, OnChanges, OnDestroy, Output, QueryList, SimpleChange, forwardRef } from '@angular/core';
import { Observable, ReplaySubject, Subscription } from 'rxjs';
import { tap } from 'rxjs/operators';
import { MarkerLabel, MouseEvent } from '../map-types';
import { FitBoundsAccessor, FitBoundsDetails } from '../services/fit-bounds';
import * as mapTypes from '../services/google-maps-types';
import {MarkerManager} from '../services/managers/marker-manager';

import {AgmInfoWindow} from './info-window';
import {MarkerLabel} from '../map-types';
import { MarkerManager } from '../services/managers/marker-manager';
import { AgmInfoWindow } from './info-window';

let markerId = 0;

Expand Down Expand Up @@ -37,13 +34,16 @@ let markerId = 0;
*/
@Directive({
selector: 'agm-marker',
providers: [
{provide: FitBoundsAccessor, useExisting: forwardRef(() => AgmMarker)}
],
inputs: [
'latitude', 'longitude', 'title', 'label', 'draggable: markerDraggable', 'iconUrl',
'openInfoWindow', 'opacity', 'visible', 'zIndex', 'animation'
],
outputs: ['markerClick', 'dragEnd', 'mouseOver', 'mouseOut']
})
export class AgmMarker implements OnDestroy, OnChanges, AfterContentInit {
export class AgmMarker implements OnDestroy, OnChanges, AfterContentInit, FitBoundsAccessor {
/**
* The latitude position of the marker.
*/
Expand Down Expand Up @@ -144,6 +144,8 @@ export class AgmMarker implements OnDestroy, OnChanges, AfterContentInit {
private _id: string;
private _observableSubscriptions: Subscription[] = [];

protected readonly _fitBoundsDetails$: ReplaySubject<FitBoundsDetails> = new ReplaySubject<FitBoundsDetails>(1);

constructor(private _markerManager: MarkerManager) { this._id = (markerId++).toString(); }

/* @internal */
Expand Down Expand Up @@ -174,12 +176,14 @@ export class AgmMarker implements OnDestroy, OnChanges, AfterContentInit {
}
if (!this._markerAddedToManger) {
this._markerManager.addMarker(this);
this._updateFitBoundsDetails();
this._markerAddedToManger = true;
this._addEventListeners();
return;
}
if (changes['latitude'] || changes['longitude']) {
this._markerManager.updateMarkerPosition(this);
this._updateFitBoundsDetails();
}
if (changes['title']) {
this._markerManager.updateTitle(this);
Expand Down Expand Up @@ -210,6 +214,17 @@ export class AgmMarker implements OnDestroy, OnChanges, AfterContentInit {
}
}

/**
* @internal
*/
getFitBoundsDetails$(): Observable<FitBoundsDetails> {
return this._fitBoundsDetails$.asObservable();
}

protected _updateFitBoundsDetails() {
this._fitBoundsDetails$.next({latLng: {lat: this.latitude, lng: this.longitude}});
}

private _addEventListeners() {
const cs = this._markerManager.createEventObservable('click', this).subscribe(() => {
if (this.openInfoWindow) {
Expand Down
1 change: 1 addition & 0 deletions packages/core/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export {DataLayerManager} from './services/managers/data-layer-manager';
export {GoogleMapsScriptProtocol, LAZY_MAPS_API_CONFIG, LazyMapsAPILoader, LazyMapsAPILoaderConfigLiteral} from './services/maps-api-loader/lazy-maps-api-loader';
export {MapsAPILoader} from './services/maps-api-loader/maps-api-loader';
export {NoOpMapsAPILoader} from './services/maps-api-loader/noop-maps-api-loader';
export {FitBoundsAccessor, FitBoundsDetails} from './services/fit-bounds';

0 comments on commit 4d3103c

Please sign in to comment.