Skip to content

Commit fcbfb94

Browse files
committed
Refactor ngx-mapbox-gl to support zoneless change detection
1 parent 35f6e85 commit fcbfb94

File tree

7 files changed

+631
-739
lines changed

7 files changed

+631
-739
lines changed

apps/showcase/src/app/demo/demo-index.component.ts

+16-16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {
2+
afterNextRender,
3+
afterRender,
24
AfterViewInit,
35
Component,
46
ElementRef,
@@ -21,7 +23,7 @@ type RoutesByCategory = { [P in Category]: Routes };
2123
templateUrl: './demo-index.component.html',
2224
styleUrls: ['./demo-index.component.scss'],
2325
})
24-
export class DemoIndexComponent implements OnInit, AfterViewInit {
26+
export class DemoIndexComponent implements OnInit {
2527
routes: RoutesByCategory;
2628
originalRoutes: RoutesByCategory;
2729
categories: Category[];
@@ -42,16 +44,16 @@ export class DemoIndexComponent implements OnInit, AfterViewInit {
4244
route.data ? route.data['cat'] : null
4345
) as unknown as RoutesByCategory;
4446
this.categories = Object.values(Category);
47+
48+
afterNextRender(() => {
49+
this.scrollInToActiveExampleLink();
50+
});
4551
}
4652

4753
ngOnInit() {
4854
this.routes = this.originalRoutes;
4955
}
5056

51-
ngAfterViewInit() {
52-
this.scrollInToActiveExampleLink();
53-
}
54-
5557
toggleSidenav() {
5658
this.sidenavIsOpen = !this.sidenavIsOpen;
5759
}
@@ -93,16 +95,14 @@ export class DemoIndexComponent implements OnInit, AfterViewInit {
9395
}
9496

9597
private scrollInToActiveExampleLink() {
96-
this.zone.onStable.pipe(first()).subscribe(() => {
97-
const activeLink = this.exampleLinks.find((elm) =>
98-
(elm.nativeElement as HTMLElement).classList.contains('active')
99-
);
100-
if (activeLink) {
101-
scrollIntoView(activeLink.nativeElement as HTMLElement, {
102-
block: 'center',
103-
scrollMode: 'if-needed',
104-
});
105-
}
106-
});
98+
const activeLink = this.exampleLinks.find((elm) =>
99+
(elm.nativeElement as HTMLElement).classList.contains('active')
100+
);
101+
if (activeLink) {
102+
scrollIntoView(activeLink.nativeElement as HTMLElement, {
103+
block: 'center',
104+
scrollMode: 'if-needed',
105+
});
106+
}
107107
}
108108
}

apps/showcase/src/app/demo/stackblitz-edit/create-stackblitz-project.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Project } from '@stackblitz/sdk/typings/interfaces';
1+
import { Project } from '@stackblitz/sdk';
22

33
export const createStackblitzProject = (
44
projectbase: string[],

apps/showcase/src/app/demo/stackblitz-edit/stackblitz-edit.component.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import {
1010
ViewChild,
1111
} from '@angular/core';
1212
import { ActivatedRoute } from '@angular/router';
13-
import StackBlitzSDK from '@stackblitz/sdk';
14-
import { VM } from '@stackblitz/sdk/typings/VM';
13+
import StackBlitzSDK, { VM } from '@stackblitz/sdk';
1514
import { forkJoin, from, Subscription } from 'rxjs';
1615
import { finalize, shareReplay, switchMap, tap } from 'rxjs/operators';
1716
import { createStackblitzProject } from './create-stackblitz-project';

libs/ngx-mapbox-gl/src/lib/map/map.component.spec.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { SimpleChange } from '@angular/core';
1+
import {
2+
provideExperimentalZonelessChangeDetection,
3+
SimpleChange,
4+
} from '@angular/core';
25
import {
36
ComponentFixture,
47
fakeAsync,
@@ -27,6 +30,7 @@ describe('MapComponent', () => {
2730
beforeEach(waitForAsync(() => {
2831
TestBed.configureTestingModule({
2932
declarations: [MapComponent],
33+
providers: [provideExperimentalZonelessChangeDetection()],
3034
})
3135
.overrideComponent(MapComponent, {
3236
set: {
@@ -36,27 +40,26 @@ describe('MapComponent', () => {
3640
.compileComponents();
3741
}));
3842

39-
beforeEach(() => {
43+
beforeEach(async () => {
4044
fixture = TestBed.createComponent(MapComponent);
45+
await fixture.whenStable();
4146
component = fixture.debugElement.componentInstance;
4247
msSpy = fixture.debugElement.injector.get<MapService>(MapService) as any;
4348
});
4449

4550
describe('Init tests', () => {
4651
it('should init', () => {
47-
fixture.detectChanges();
4852
expect(msSpy.setup).toHaveBeenCalledTimes(1);
4953
});
5054

51-
it('should init with custom inputs', () => {
55+
it('should init with custom inputs', async () => {
5256
component.accessToken = 'tokenTest';
5357
component.style = 'style';
5458
msSpy.setup.mockImplementation((options: SetupMap) => {
5559
expect(options.accessToken).toEqual('tokenTest');
5660
expect(options.mapOptions.style).toEqual('style');
5761
});
58-
fixture.detectChanges();
59-
expect.assertions(2);
62+
expect(msSpy.setup).toHaveBeenCalled();
6063
});
6164
});
6265

libs/ngx-mapbox-gl/src/lib/map/map.component.ts

+57-58
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {
2-
AfterViewInit,
2+
afterNextRender,
33
ChangeDetectionStrategy,
44
Component,
55
ElementRef,
@@ -55,7 +55,6 @@ export class MapComponent
5555
implements
5656
OnChanges,
5757
OnDestroy,
58-
AfterViewInit,
5958
Omit<MapboxOptions, 'bearing' | 'container' | 'pitch' | 'zoom'>,
6059
MapEvent
6160
{
@@ -309,63 +308,63 @@ export class MapComponent
309308

310309
@ViewChild('container', { static: true }) mapContainer: ElementRef;
311310

312-
constructor(private mapService: MapService) {}
313-
314-
ngAfterViewInit() {
315-
this.warnDeprecatedOutputs();
316-
this.mapService.setup({
317-
accessToken: this.accessToken,
318-
customMapboxApiUrl: this.customMapboxApiUrl,
319-
mapOptions: {
320-
collectResourceTiming: this.collectResourceTiming,
321-
container: this.mapContainer.nativeElement,
322-
crossSourceCollisions: this.crossSourceCollisions,
323-
fadeDuration: this.fadeDuration,
324-
minZoom: this.minZoom,
325-
maxZoom: this.maxZoom,
326-
minPitch: this.minPitch,
327-
maxPitch: this.maxPitch,
328-
style: this.style,
329-
hash: this.hash,
330-
interactive: this.interactive,
331-
bearingSnap: this.bearingSnap,
332-
pitchWithRotate: this.pitchWithRotate,
333-
clickTolerance: this.clickTolerance,
334-
attributionControl: this.attributionControl,
335-
logoPosition: this.logoPosition,
336-
failIfMajorPerformanceCaveat: this.failIfMajorPerformanceCaveat,
337-
preserveDrawingBuffer: this.preserveDrawingBuffer,
338-
refreshExpiredTiles: this.refreshExpiredTiles,
339-
maxBounds: this.maxBounds,
340-
scrollZoom: this.scrollZoom,
341-
boxZoom: this.boxZoom,
342-
dragRotate: this.dragRotate,
343-
dragPan: this.dragPan,
344-
keyboard: this.keyboard,
345-
doubleClickZoom: this.doubleClickZoom,
346-
touchPitch: this.touchPitch,
347-
touchZoomRotate: this.touchZoomRotate,
348-
trackResize: this.trackResize,
349-
center: this.center,
350-
zoom: this.zoom,
351-
bearing: this.bearing,
352-
pitch: this.pitch,
353-
renderWorldCopies: this.renderWorldCopies,
354-
maxTileCacheSize: this.maxTileCacheSize,
355-
localIdeographFontFamily: this.localIdeographFontFamily,
356-
transformRequest: this.transformRequest,
357-
bounds: this.bounds ? this.bounds : this.fitBounds,
358-
fitBoundsOptions: this.fitBoundsOptions,
359-
antialias: this.antialias,
360-
locale: this.locale,
361-
cooperativeGestures: this.cooperativeGestures,
362-
projection: this.projection,
363-
},
364-
mapEvents: this,
311+
constructor(private mapService: MapService) {
312+
afterNextRender(() => {
313+
this.warnDeprecatedOutputs();
314+
this.mapService.setup({
315+
accessToken: this.accessToken,
316+
customMapboxApiUrl: this.customMapboxApiUrl,
317+
mapOptions: {
318+
collectResourceTiming: this.collectResourceTiming,
319+
container: this.mapContainer.nativeElement,
320+
crossSourceCollisions: this.crossSourceCollisions,
321+
fadeDuration: this.fadeDuration,
322+
minZoom: this.minZoom,
323+
maxZoom: this.maxZoom,
324+
minPitch: this.minPitch,
325+
maxPitch: this.maxPitch,
326+
style: this.style,
327+
hash: this.hash,
328+
interactive: this.interactive,
329+
bearingSnap: this.bearingSnap,
330+
pitchWithRotate: this.pitchWithRotate,
331+
clickTolerance: this.clickTolerance,
332+
attributionControl: this.attributionControl,
333+
logoPosition: this.logoPosition,
334+
failIfMajorPerformanceCaveat: this.failIfMajorPerformanceCaveat,
335+
preserveDrawingBuffer: this.preserveDrawingBuffer,
336+
refreshExpiredTiles: this.refreshExpiredTiles,
337+
maxBounds: this.maxBounds,
338+
scrollZoom: this.scrollZoom,
339+
boxZoom: this.boxZoom,
340+
dragRotate: this.dragRotate,
341+
dragPan: this.dragPan,
342+
keyboard: this.keyboard,
343+
doubleClickZoom: this.doubleClickZoom,
344+
touchPitch: this.touchPitch,
345+
touchZoomRotate: this.touchZoomRotate,
346+
trackResize: this.trackResize,
347+
center: this.center,
348+
zoom: this.zoom,
349+
bearing: this.bearing,
350+
pitch: this.pitch,
351+
renderWorldCopies: this.renderWorldCopies,
352+
maxTileCacheSize: this.maxTileCacheSize,
353+
localIdeographFontFamily: this.localIdeographFontFamily,
354+
transformRequest: this.transformRequest,
355+
bounds: this.bounds ? this.bounds : this.fitBounds,
356+
fitBoundsOptions: this.fitBoundsOptions,
357+
antialias: this.antialias,
358+
locale: this.locale,
359+
cooperativeGestures: this.cooperativeGestures,
360+
projection: this.projection,
361+
},
362+
mapEvents: this,
363+
});
364+
if (this.cursorStyle) {
365+
this.mapService.changeCanvasCursor(this.cursorStyle);
366+
}
365367
});
366-
if (this.cursorStyle) {
367-
this.mapService.changeCanvasCursor(this.cursorStyle);
368-
}
369368
}
370369

371370
ngOnDestroy() {

libs/ngx-mapbox-gl/src/lib/map/map.service.ts

+23-27
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
} from '@angular/core';
99
import MapboxGl from 'mapbox-gl';
1010
import { AsyncSubject, Observable, Subscription } from 'rxjs';
11-
import { first } from 'rxjs/operators';
1211
import {
1312
LayerEvents,
1413
MapEvent,
@@ -30,7 +29,7 @@ export interface SetupMap {
3029
}
3130

3231
export interface SetupLayer {
33-
layerOptions: MapboxGl.Layer;
32+
layerOptions: mapboxgl.Layer;
3433
layerEvents: LayerEvents;
3534
}
3635

@@ -100,32 +99,29 @@ export class MapService {
10099
}
101100

102101
setup(options: SetupMap) {
103-
// Need onStable to wait for a potential @angular/route transition to end
104-
this.zone.onStable.pipe(first()).subscribe(() => {
105-
// Workaround rollup issue
106-
// this.assign(
107-
// MapboxGl,
108-
// 'accessToken',
109-
// options.accessToken || this.MAPBOX_API_KEY
110-
// );
111-
if (options.customMapboxApiUrl) {
112-
(MapboxGl.baseApiUrl as string) = options.customMapboxApiUrl;
113-
}
114-
this.createMap({
115-
...(options.mapOptions as MapboxGl.MapboxOptions),
116-
accessToken: options.accessToken || this.MAPBOX_API_KEY || '',
117-
});
118-
this.hookEvents(options.mapEvents);
119-
this.mapEvents = options.mapEvents;
120-
this.mapCreated.next(undefined);
121-
this.mapCreated.complete();
122-
// Intentionnaly emit mapCreate after internal mapCreated event
123-
if (options.mapEvents.mapCreate.observed) {
124-
this.zone.run(() => {
125-
options.mapEvents.mapCreate.emit(this.mapInstance);
126-
});
127-
}
102+
// Workaround rollup issue
103+
// this.assign(
104+
// MapboxGl,
105+
// 'accessToken',
106+
// options.accessToken || this.MAPBOX_API_KEY
107+
// );
108+
if (options.customMapboxApiUrl) {
109+
(MapboxGl.baseApiUrl as string) = options.customMapboxApiUrl;
110+
}
111+
this.createMap({
112+
...(options.mapOptions as MapboxGl.MapboxOptions),
113+
accessToken: options.accessToken || this.MAPBOX_API_KEY || '',
128114
});
115+
this.hookEvents(options.mapEvents);
116+
this.mapEvents = options.mapEvents;
117+
this.mapCreated.next(undefined);
118+
this.mapCreated.complete();
119+
// Intentionnaly emit mapCreate after internal mapCreated event
120+
if (options.mapEvents.mapCreate.observed) {
121+
this.zone.run(() => {
122+
options.mapEvents.mapCreate.emit(this.mapInstance);
123+
});
124+
}
129125
}
130126

131127
destroyMap() {

0 commit comments

Comments
 (0)