From 31018ef9c09b137707e2d82d9a986f5433e72914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Y=C4=B1lmaz?= Date: Tue, 24 Sep 2024 09:09:12 +0200 Subject: [PATCH] Refactor ngx-mapbox-gl to support zoneless change detection --- .vscode/settings.json | 3 +- .../src/app/demo/demo-index.component.ts | 32 +++-- .../src/lib/map/map.component.spec.ts | 5 +- .../src/lib/map/map.component.ts | 115 +++++++++--------- libs/ngx-mapbox-gl/src/lib/map/map.service.ts | 48 ++++---- 5 files changed, 100 insertions(+), 103 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 14070d9e8..2c6839fbe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,6 @@ "**/.DS_Store": true, ".ng_build": true }, - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "terminal.integrated.env.osx": {} } diff --git a/apps/showcase/src/app/demo/demo-index.component.ts b/apps/showcase/src/app/demo/demo-index.component.ts index 121d1aa2d..56ee2ffd0 100644 --- a/apps/showcase/src/app/demo/demo-index.component.ts +++ b/apps/showcase/src/app/demo/demo-index.component.ts @@ -1,5 +1,5 @@ import { - AfterViewInit, + afterNextRender, Component, ElementRef, NgZone, @@ -21,7 +21,7 @@ type RoutesByCategory = { [P in Category]: Routes }; templateUrl: './demo-index.component.html', styleUrls: ['./demo-index.component.scss'], }) -export class DemoIndexComponent implements OnInit, AfterViewInit { +export class DemoIndexComponent implements OnInit { routes: RoutesByCategory; originalRoutes: RoutesByCategory; categories: Category[]; @@ -42,16 +42,16 @@ export class DemoIndexComponent implements OnInit, AfterViewInit { route.data ? route.data['cat'] : null ) as unknown as RoutesByCategory; this.categories = Object.values(Category); + + afterNextRender(() => { + this.scrollInToActiveExampleLink(); + }); } ngOnInit() { this.routes = this.originalRoutes; } - ngAfterViewInit() { - this.scrollInToActiveExampleLink(); - } - toggleSidenav() { this.sidenavIsOpen = !this.sidenavIsOpen; } @@ -93,16 +93,14 @@ export class DemoIndexComponent implements OnInit, AfterViewInit { } private scrollInToActiveExampleLink() { - this.zone.onStable.pipe(first()).subscribe(() => { - const activeLink = this.exampleLinks.find((elm) => - (elm.nativeElement as HTMLElement).classList.contains('active') - ); - if (activeLink) { - scrollIntoView(activeLink.nativeElement as HTMLElement, { - block: 'center', - scrollMode: 'if-needed', - }); - } - }); + const activeLink = this.exampleLinks.find((elm) => + (elm.nativeElement as HTMLElement).classList.contains('active') + ); + if (activeLink) { + scrollIntoView(activeLink.nativeElement as HTMLElement, { + block: 'center', + scrollMode: 'if-needed', + }); + } } } diff --git a/libs/ngx-mapbox-gl/src/lib/map/map.component.spec.ts b/libs/ngx-mapbox-gl/src/lib/map/map.component.spec.ts index ffec23191..5751de7ef 100644 --- a/libs/ngx-mapbox-gl/src/lib/map/map.component.spec.ts +++ b/libs/ngx-mapbox-gl/src/lib/map/map.component.spec.ts @@ -9,6 +9,7 @@ import { import { ReplaySubject } from 'rxjs'; import { MapComponent } from './map.component'; import { MapService, SetupMap } from './map.service'; +import { provideExperimentalZonelessChangeDetection } from '@angular/core'; describe('MapComponent', () => { class MapServiceSpy { @@ -27,6 +28,7 @@ describe('MapComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [MapComponent], + providers: [provideExperimentalZonelessChangeDetection()], }) .overrideComponent(MapComponent, { set: { @@ -36,9 +38,10 @@ describe('MapComponent', () => { .compileComponents(); })); - beforeEach(() => { + beforeEach(async () => { fixture = TestBed.createComponent(MapComponent); component = fixture.debugElement.componentInstance; + await fixture.whenStable(); msSpy = fixture.debugElement.injector.get(MapService) as any; }); diff --git a/libs/ngx-mapbox-gl/src/lib/map/map.component.ts b/libs/ngx-mapbox-gl/src/lib/map/map.component.ts index 8f768af0e..bed59e011 100644 --- a/libs/ngx-mapbox-gl/src/lib/map/map.component.ts +++ b/libs/ngx-mapbox-gl/src/lib/map/map.component.ts @@ -1,5 +1,5 @@ import { - AfterViewInit, + afterNextRender, ChangeDetectionStrategy, Component, ElementRef, @@ -55,7 +55,6 @@ export class MapComponent implements OnChanges, OnDestroy, - AfterViewInit, Omit, MapEvent { @@ -309,63 +308,63 @@ export class MapComponent @ViewChild('container', { static: true }) mapContainer: ElementRef; - constructor(private mapService: MapService) {} - - ngAfterViewInit() { - this.warnDeprecatedOutputs(); - this.mapService.setup({ - accessToken: this.accessToken, - customMapboxApiUrl: this.customMapboxApiUrl, - mapOptions: { - collectResourceTiming: this.collectResourceTiming, - container: this.mapContainer.nativeElement, - crossSourceCollisions: this.crossSourceCollisions, - fadeDuration: this.fadeDuration, - minZoom: this.minZoom, - maxZoom: this.maxZoom, - minPitch: this.minPitch, - maxPitch: this.maxPitch, - style: this.style, - hash: this.hash, - interactive: this.interactive, - bearingSnap: this.bearingSnap, - pitchWithRotate: this.pitchWithRotate, - clickTolerance: this.clickTolerance, - attributionControl: this.attributionControl, - logoPosition: this.logoPosition, - failIfMajorPerformanceCaveat: this.failIfMajorPerformanceCaveat, - preserveDrawingBuffer: this.preserveDrawingBuffer, - refreshExpiredTiles: this.refreshExpiredTiles, - maxBounds: this.maxBounds, - scrollZoom: this.scrollZoom, - boxZoom: this.boxZoom, - dragRotate: this.dragRotate, - dragPan: this.dragPan, - keyboard: this.keyboard, - doubleClickZoom: this.doubleClickZoom, - touchPitch: this.touchPitch, - touchZoomRotate: this.touchZoomRotate, - trackResize: this.trackResize, - center: this.center, - zoom: this.zoom, - bearing: this.bearing, - pitch: this.pitch, - renderWorldCopies: this.renderWorldCopies, - maxTileCacheSize: this.maxTileCacheSize, - localIdeographFontFamily: this.localIdeographFontFamily, - transformRequest: this.transformRequest, - bounds: this.bounds ? this.bounds : this.fitBounds, - fitBoundsOptions: this.fitBoundsOptions, - antialias: this.antialias, - locale: this.locale, - cooperativeGestures: this.cooperativeGestures, - projection: this.projection, - }, - mapEvents: this, + constructor(private mapService: MapService) { + afterNextRender(() => { + this.warnDeprecatedOutputs(); + this.mapService.setup({ + accessToken: this.accessToken, + customMapboxApiUrl: this.customMapboxApiUrl, + mapOptions: { + collectResourceTiming: this.collectResourceTiming, + container: this.mapContainer.nativeElement, + crossSourceCollisions: this.crossSourceCollisions, + fadeDuration: this.fadeDuration, + minZoom: this.minZoom, + maxZoom: this.maxZoom, + minPitch: this.minPitch, + maxPitch: this.maxPitch, + style: this.style, + hash: this.hash, + interactive: this.interactive, + bearingSnap: this.bearingSnap, + pitchWithRotate: this.pitchWithRotate, + clickTolerance: this.clickTolerance, + attributionControl: this.attributionControl, + logoPosition: this.logoPosition, + failIfMajorPerformanceCaveat: this.failIfMajorPerformanceCaveat, + preserveDrawingBuffer: this.preserveDrawingBuffer, + refreshExpiredTiles: this.refreshExpiredTiles, + maxBounds: this.maxBounds, + scrollZoom: this.scrollZoom, + boxZoom: this.boxZoom, + dragRotate: this.dragRotate, + dragPan: this.dragPan, + keyboard: this.keyboard, + doubleClickZoom: this.doubleClickZoom, + touchPitch: this.touchPitch, + touchZoomRotate: this.touchZoomRotate, + trackResize: this.trackResize, + center: this.center, + zoom: this.zoom, + bearing: this.bearing, + pitch: this.pitch, + renderWorldCopies: this.renderWorldCopies, + maxTileCacheSize: this.maxTileCacheSize, + localIdeographFontFamily: this.localIdeographFontFamily, + transformRequest: this.transformRequest, + bounds: this.bounds ? this.bounds : this.fitBounds, + fitBoundsOptions: this.fitBoundsOptions, + antialias: this.antialias, + locale: this.locale, + cooperativeGestures: this.cooperativeGestures, + projection: this.projection, + }, + mapEvents: this, + }); + if (this.cursorStyle) { + this.mapService.changeCanvasCursor(this.cursorStyle); + } }); - if (this.cursorStyle) { - this.mapService.changeCanvasCursor(this.cursorStyle); - } } ngOnDestroy() { diff --git a/libs/ngx-mapbox-gl/src/lib/map/map.service.ts b/libs/ngx-mapbox-gl/src/lib/map/map.service.ts index 42fe9ab78..7d4d525d9 100644 --- a/libs/ngx-mapbox-gl/src/lib/map/map.service.ts +++ b/libs/ngx-mapbox-gl/src/lib/map/map.service.ts @@ -8,7 +8,6 @@ import { } from '@angular/core'; import MapboxGl from 'mapbox-gl'; import { AsyncSubject, Observable, Subscription } from 'rxjs'; -import { first } from 'rxjs/operators'; import { LayerEvents, MapEvent, @@ -100,32 +99,29 @@ export class MapService { } setup(options: SetupMap) { - // Need onStable to wait for a potential @angular/route transition to end - this.zone.onStable.pipe(first()).subscribe(() => { - // Workaround rollup issue - // this.assign( - // MapboxGl, - // 'accessToken', - // options.accessToken || this.MAPBOX_API_KEY - // ); - if (options.customMapboxApiUrl) { - (MapboxGl.baseApiUrl as string) = options.customMapboxApiUrl; - } - this.createMap({ - ...(options.mapOptions as MapboxGl.MapboxOptions), - accessToken: options.accessToken || this.MAPBOX_API_KEY || '', - }); - this.hookEvents(options.mapEvents); - this.mapEvents = options.mapEvents; - this.mapCreated.next(undefined); - this.mapCreated.complete(); - // Intentionnaly emit mapCreate after internal mapCreated event - if (options.mapEvents.mapCreate.observed) { - this.zone.run(() => { - options.mapEvents.mapCreate.emit(this.mapInstance); - }); - } + // Workaround rollup issue + // this.assign( + // MapboxGl, + // 'accessToken', + // options.accessToken || this.MAPBOX_API_KEY + // ); + if (options.customMapboxApiUrl) { + (MapboxGl.baseApiUrl as string) = options.customMapboxApiUrl; + } + this.createMap({ + ...(options.mapOptions as MapboxGl.MapboxOptions), + accessToken: options.accessToken || this.MAPBOX_API_KEY || '', }); + this.hookEvents(options.mapEvents); + this.mapEvents = options.mapEvents; + this.mapCreated.next(undefined); + this.mapCreated.complete(); + // Intentionnaly emit mapCreate after internal mapCreated event + if (options.mapEvents.mapCreate.observed) { + this.zone.run(() => { + options.mapEvents.mapCreate.emit(this.mapInstance); + }); + } } destroyMap() {