-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ObjectManager): Create ObjectManager directive (#162)
* feat(typings): Add Prefix utility and improve some interfaces Add Prefix utility and replace hardcoded interfaces with it. Improve IObjectManagerOptions, IGeoObjectOptions and IClusterPlacemarkOptions. * feat(ObjectManager): Create ObjectManager directive * chore(ObjectManager): Add usage of ObjectManager component in app.component
- Loading branch information
Showing
8 changed files
with
603 additions
and
177 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
171 changes: 171 additions & 0 deletions
171
...lar8-yandex-maps/src/lib/components/ya-object-manager/ya-object-manager.directive.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,171 @@ | ||
import { Component, ViewChild } from '@angular/core'; | ||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||
import { BehaviorSubject } from 'rxjs'; | ||
|
||
import { YaMapComponent } from '../ya-map/ya-map.component'; | ||
import { YaObjectManagerDirective } from './ya-object-manager.directive'; | ||
import { | ||
createMapSpy, | ||
createObjectManagerConstructorSpy, | ||
createObjectManagerSpy, | ||
} from '../../testing/fake-ymaps-utils'; | ||
import { YaReadyEvent } from '../../typings/ya-ready-event'; | ||
|
||
@Component({ | ||
template: ` | ||
<ya-object-manager | ||
[options]="options" | ||
(yaclick)="handleClick()" | ||
(geometrychange)="handleGeometryChange()" | ||
(multitouchmove)="handleMultitouchMove()" | ||
></ya-object-manager> | ||
`, | ||
}) | ||
class MockHostComponent { | ||
@ViewChild(YaObjectManagerDirective, { static: true }) objectManager: YaObjectManagerDirective; | ||
|
||
options: ymaps.IObjectManagerOptions; | ||
|
||
handleClick(): void {} | ||
|
||
handleGeometryChange(): void {} | ||
|
||
handleMultitouchMove(): void {} | ||
} | ||
|
||
describe('YaObjectManagerDirective', () => { | ||
let component: YaObjectManagerDirective; | ||
let fixture: ComponentFixture<MockHostComponent>; | ||
|
||
let mapSpy: jasmine.SpyObj<ymaps.Map>; | ||
let objectManagerSpy: jasmine.SpyObj<ymaps.ObjectManager>; | ||
let objectManagerConstructorSpy: jasmine.Spy; | ||
|
||
beforeEach(async () => { | ||
mapSpy = createMapSpy(); | ||
|
||
await TestBed.configureTestingModule({ | ||
declarations: [MockHostComponent, YaObjectManagerDirective], | ||
providers: [ | ||
{ | ||
provide: YaMapComponent, | ||
useValue: { | ||
isBrowser: true, | ||
map$: new BehaviorSubject(mapSpy), | ||
}, | ||
}, | ||
], | ||
}).compileComponents(); | ||
}); | ||
|
||
beforeEach(() => { | ||
fixture = TestBed.createComponent(MockHostComponent); | ||
component = fixture.componentInstance.objectManager; | ||
objectManagerSpy = createObjectManagerSpy(); | ||
objectManagerConstructorSpy = createObjectManagerConstructorSpy(objectManagerSpy); | ||
}); | ||
|
||
afterEach(() => { | ||
(window.ymaps as any) = undefined; | ||
}); | ||
|
||
it('should create objectManager', () => { | ||
const options = { | ||
clusterize: true, | ||
clusterHasBalloon: false, | ||
geoObjectOpenBalloonOnClick: false, | ||
}; | ||
|
||
fixture.componentInstance.options = options; | ||
fixture.detectChanges(); | ||
|
||
expect(objectManagerConstructorSpy).toHaveBeenCalledWith(options); | ||
expect(mapSpy.geoObjects.add).toHaveBeenCalledWith(objectManagerSpy); | ||
}); | ||
|
||
it('should emit ready on objectManager load', () => { | ||
spyOn(component.ready, 'emit'); | ||
fixture.detectChanges(); | ||
|
||
const readyEvent: YaReadyEvent = { | ||
ymaps: window.ymaps, | ||
target: objectManagerSpy, | ||
}; | ||
|
||
expect(component.ready.emit).toHaveBeenCalledWith(readyEvent); | ||
}); | ||
|
||
it('should set options', () => { | ||
const options = { | ||
clusterize: true, | ||
minClusterSize: 10, | ||
clusterDisableClickZoom: true, | ||
}; | ||
|
||
fixture.componentInstance.options = options; | ||
fixture.detectChanges(); | ||
|
||
expect(objectManagerConstructorSpy.calls.mostRecent()?.args[0]).toEqual(options); | ||
}); | ||
|
||
it('should set options after init', () => { | ||
fixture.detectChanges(); | ||
|
||
const options = { | ||
clusterize: true, | ||
minClusterSize: 10, | ||
clusterDisableClickZoom: true, | ||
}; | ||
|
||
fixture.componentInstance.options = options; | ||
|
||
fixture.detectChanges(); | ||
|
||
expect(objectManagerSpy.options.set).toHaveBeenCalledWith(options); | ||
}); | ||
|
||
it('should remove objectManager from map.geoObjects on destroy', () => { | ||
fixture.detectChanges(); | ||
fixture.destroy(); | ||
|
||
expect(mapSpy.geoObjects.remove).toHaveBeenCalledWith(objectManagerSpy); | ||
}); | ||
|
||
it('should init event handlers that are set on the objectManager', () => { | ||
const addSpy = objectManagerSpy.events.add; | ||
fixture.detectChanges(); | ||
|
||
expect(addSpy).toHaveBeenCalledWith('click', jasmine.any(Function)); | ||
expect(addSpy).toHaveBeenCalledWith('geometrychange', jasmine.any(Function)); | ||
expect(addSpy).toHaveBeenCalledWith('multitouchmove', jasmine.any(Function)); | ||
expect(addSpy).not.toHaveBeenCalledWith('contextmenu', jasmine.any(Function)); | ||
expect(addSpy).not.toHaveBeenCalledWith('dblclick', jasmine.any(Function)); | ||
expect(addSpy).not.toHaveBeenCalledWith('mapchange', jasmine.any(Function)); | ||
expect(addSpy).not.toHaveBeenCalledWith('mousedown', jasmine.any(Function)); | ||
expect(addSpy).not.toHaveBeenCalledWith('mouseenter', jasmine.any(Function)); | ||
expect(addSpy).not.toHaveBeenCalledWith('mouseleave', jasmine.any(Function)); | ||
expect(addSpy).not.toHaveBeenCalledWith('mousemove', jasmine.any(Function)); | ||
expect(addSpy).not.toHaveBeenCalledWith('mouseup', jasmine.any(Function)); | ||
expect(addSpy).not.toHaveBeenCalledWith('multitouchend', jasmine.any(Function)); | ||
expect(addSpy).not.toHaveBeenCalledWith('multitouchstart', jasmine.any(Function)); | ||
expect(addSpy).not.toHaveBeenCalledWith('optionschange', jasmine.any(Function)); | ||
expect(addSpy).not.toHaveBeenCalledWith('overlaychange', jasmine.any(Function)); | ||
expect(addSpy).not.toHaveBeenCalledWith('parentchange', jasmine.any(Function)); | ||
expect(addSpy).not.toHaveBeenCalledWith('propertieschange', jasmine.any(Function)); | ||
expect(addSpy).not.toHaveBeenCalledWith('wheel', jasmine.any(Function)); | ||
}); | ||
|
||
it('should be able to add an event listener after init', () => { | ||
const addSpy = objectManagerSpy.events.add; | ||
fixture.detectChanges(); | ||
|
||
expect(addSpy).not.toHaveBeenCalledWith('overlaychange', jasmine.any(Function)); | ||
|
||
// Pick an event that isn't bound in the template. | ||
const subscription = fixture.componentInstance.objectManager.overlaychange.subscribe(); | ||
fixture.detectChanges(); | ||
|
||
expect(addSpy).toHaveBeenCalledWith('overlaychange', jasmine.any(Function)); | ||
subscription.unsubscribe(); | ||
}); | ||
}); |
197 changes: 197 additions & 0 deletions
197
.../angular8-yandex-maps/src/lib/components/ya-object-manager/ya-object-manager.directive.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,197 @@ | ||
import { | ||
Directive, | ||
EventEmitter, | ||
Input, | ||
NgZone, | ||
OnChanges, | ||
OnDestroy, | ||
OnInit, | ||
Output, | ||
SimpleChanges, | ||
} from '@angular/core'; | ||
import { Observable, Subscription } from 'rxjs'; | ||
|
||
import { EventManager } from '../../event-manager'; | ||
import { YaEvent } from '../../typings/ya-event'; | ||
import { YaMapComponent } from '../ya-map/ya-map.component'; | ||
import { YaReadyEvent } from '../../typings/ya-ready-event'; | ||
|
||
@Directive({ | ||
selector: 'ya-object-manager', | ||
}) | ||
export class YaObjectManagerDirective implements OnInit, OnChanges, OnDestroy { | ||
private readonly _sub = new Subscription(); | ||
|
||
private readonly _eventManager = new EventManager(this._ngZone); | ||
|
||
private _objectManager?: ymaps.ObjectManager; | ||
|
||
@Input() options: ymaps.IObjectManagerOptions; | ||
|
||
/** | ||
* ObjectManager instance is added in a Map. | ||
*/ | ||
@Output() ready: EventEmitter<YaReadyEvent<ymaps.ObjectManager>> = new EventEmitter< | ||
YaReadyEvent<ymaps.ObjectManager> | ||
>(); | ||
|
||
/** | ||
* Single left-click on the object. | ||
*/ | ||
@Output() yaclick: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('click'); | ||
|
||
/** | ||
* Calls the element's context menu. | ||
*/ | ||
@Output() yacontextmenu: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('contextmenu'); | ||
|
||
/** | ||
* Double left-click on the object. | ||
*/ | ||
@Output() yadblclick: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('dblclick'); | ||
|
||
/** | ||
* Change to the geo object geometry. | ||
*/ | ||
@Output() geometrychange: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('geometrychange'); | ||
|
||
/** | ||
* Map reference changed. | ||
*/ | ||
@Output() mapchange: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('mapchange'); | ||
|
||
/** | ||
* Pressing the mouse button over the object. | ||
*/ | ||
@Output() yamousedown: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('mousedown'); | ||
|
||
/** | ||
* Pointing the cursor at the object. | ||
*/ | ||
@Output() yamouseenter: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('mouseenter'); | ||
|
||
/** | ||
* Moving the cursor off of the object. | ||
*/ | ||
@Output() yamouseleave: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('mouseleave'); | ||
|
||
/** | ||
* Moving the cursor over the object. | ||
*/ | ||
@Output() yamousemove: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('mousemove'); | ||
|
||
/** | ||
* Letting go of the mouse button over an object. | ||
*/ | ||
@Output() yamouseup: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('mouseup'); | ||
|
||
/** | ||
* End of multitouch. | ||
*/ | ||
@Output() multitouchend: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('multitouchend'); | ||
|
||
/** | ||
* Repeating event during multitouch. | ||
*/ | ||
@Output() multitouchmove: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('multitouchmove'); | ||
|
||
/** | ||
* Start of multitouch. | ||
*/ | ||
@Output() multitouchstart: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('multitouchstart'); | ||
|
||
/** | ||
* Change to the object options. | ||
*/ | ||
@Output() optionschange: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('optionschange'); | ||
|
||
/** | ||
* Change to the geo object overlay. | ||
*/ | ||
@Output() overlaychange: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('overlaychange'); | ||
|
||
/** | ||
* The parent object reference changed. | ||
*/ | ||
@Output() parentchange: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('parentchange'); | ||
|
||
/** | ||
* Change to the geo object data. | ||
*/ | ||
@Output() propertieschange: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('propertieschange'); | ||
|
||
/** | ||
* Mouse wheel scrolling. | ||
*/ | ||
@Output() yawheel: Observable<YaEvent<ymaps.ObjectManager>> = | ||
this._eventManager.getLazyEmitter('wheel'); | ||
|
||
constructor(private readonly _ngZone: NgZone, private readonly _yaMapComponent: YaMapComponent) {} | ||
|
||
/** | ||
* Handles input changes and passes them in API. | ||
* @param changes | ||
*/ | ||
ngOnChanges(changes: SimpleChanges): void { | ||
const objectManager = this._objectManager; | ||
|
||
if (objectManager) { | ||
const { options } = changes; | ||
|
||
if (options) { | ||
objectManager.options.set(options.currentValue); | ||
} | ||
} | ||
} | ||
|
||
ngOnInit(): void { | ||
// It should be a noop during server-side rendering. | ||
if (this._yaMapComponent.isBrowser) { | ||
const sub = this._yaMapComponent.map$.subscribe((map) => { | ||
if (map) { | ||
const objectManager = this._createObjectManager(); | ||
this._objectManager = objectManager; | ||
|
||
map.geoObjects.add(objectManager); | ||
this._eventManager.setTarget(objectManager); | ||
this._ngZone.run(() => this.ready.emit({ ymaps, target: objectManager })); | ||
} | ||
}); | ||
|
||
this._sub.add(sub); | ||
} | ||
} | ||
|
||
ngOnDestroy(): void { | ||
if (this._objectManager) { | ||
this._yaMapComponent?.map$.value?.geoObjects.remove(this._objectManager); | ||
this._eventManager.destroy(); | ||
} | ||
|
||
this._sub.unsubscribe(); | ||
} | ||
|
||
/** | ||
* Creates ObjectManager. | ||
*/ | ||
private _createObjectManager(): ymaps.ObjectManager { | ||
return new ymaps.ObjectManager(this.options); | ||
} | ||
} |
Oops, something went wrong.