Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): auto fitBounds #1389

Merged
merged 11 commits into from
Sep 22, 2018
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';
Loading