@angular-helpers/openlayers 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,49 +1,126 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, NgZone, Injectable, input, output, viewChild, effect, ChangeDetectionStrategy, Component, makeEnvironmentProviders } from '@angular/core';
2
+ import { inject, NgZone, Injectable, DestroyRef, input, output, viewChild, afterNextRender, effect, ChangeDetectionStrategy, Component, makeEnvironmentProviders } from '@angular/core';
3
3
  import OLMap from 'ol/Map';
4
4
  import View from 'ol/View';
5
5
  import { fromLonLat, toLonLat } from 'ol/proj';
6
6
 
7
+ // ZoneHelperService - Handles NgZone compatibility for zoneless mode
8
+ /**
9
+ * Helper service that abstracts NgZone operations for zoneless compatibility.
10
+ *
11
+ * When NgZone is available (classic Angular):
12
+ * - runOutsideAngular: executes outside Angular's zone for performance
13
+ * - runInsideAngular: executes inside Angular's zone to trigger change detection
14
+ *
15
+ * When zoneless (signals-only Angular):
16
+ * - Both methods execute directly since signals handle reactivity
17
+ *
18
+ * @usageNotes
19
+ * Inject this service instead of NgZone directly:
20
+ * ```ts
21
+ * private zoneHelper = inject(OlZoneHelper);
22
+ *
23
+ * this.zoneHelper.runOutsideAngular(() => {
24
+ * // OpenLayers operations that don't need CD
25
+ * });
26
+ * ```
27
+ */
28
+ class OlZoneHelper {
29
+ ngZone = inject(NgZone, { optional: true });
30
+ /**
31
+ * Runs callback outside Angular zone if available (for performance with NgZone),
32
+ * or directly if zoneless.
33
+ *
34
+ * Use for: OpenLayers operations that don't need to trigger Angular change detection
35
+ * (map manipulation, event listeners, animations)
36
+ */
37
+ runOutsideAngular(fn) {
38
+ if (this.ngZone) {
39
+ return this.ngZone.runOutsideAngular(fn);
40
+ }
41
+ return fn();
42
+ }
43
+ /**
44
+ * Runs callback inside Angular zone if available (for triggering CD),
45
+ * or directly if zoneless.
46
+ *
47
+ * Use for: Emitting outputs that should be handled by parent components
48
+ */
49
+ runInsideAngular(fn) {
50
+ if (this.ngZone) {
51
+ return this.ngZone.run(fn);
52
+ }
53
+ return fn();
54
+ }
55
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlZoneHelper, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
56
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlZoneHelper });
57
+ }
58
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlZoneHelper, decorators: [{
59
+ type: Injectable
60
+ }] });
61
+
7
62
  // OlMapService
8
63
  class OlMapService {
9
- ngZone = inject(NgZone);
64
+ zoneHelper = inject(OlZoneHelper);
10
65
  map = null;
66
+ readyCallbacks = [];
11
67
  setMap(map) {
12
68
  this.map = map;
69
+ const callbacks = this.readyCallbacks.splice(0);
70
+ for (const cb of callbacks) {
71
+ cb(map);
72
+ }
13
73
  }
14
74
  getMap() {
15
75
  return this.map;
16
76
  }
77
+ onReady(callback) {
78
+ if (this.map) {
79
+ callback(this.map);
80
+ }
81
+ else {
82
+ this.readyCallbacks.push(callback);
83
+ }
84
+ }
17
85
  getView() {
18
86
  return this.map?.getView() ?? null;
19
87
  }
20
88
  setCenter(coordinate) {
21
89
  const view = this.getView();
22
90
  if (view)
23
- this.ngZone.runOutsideAngular(() => view.setCenter(coordinate));
91
+ this.zoneHelper.runOutsideAngular(() => view.setCenter(coordinate));
24
92
  }
25
93
  setZoom(level) {
26
94
  const view = this.getView();
27
95
  if (view)
28
- this.ngZone.runOutsideAngular(() => view.setZoom(level));
96
+ this.zoneHelper.runOutsideAngular(() => view.setZoom(level));
29
97
  }
30
98
  fitExtent(extent, options) {
99
+ const map = this.map;
31
100
  const view = this.getView();
32
- if (view)
33
- this.ngZone.runOutsideAngular(() => view.fit(extent, options));
101
+ if (!map || !view)
102
+ return;
103
+ // Defer to next macrotask to ensure DOM layout is complete
104
+ setTimeout(() => {
105
+ this.zoneHelper.runOutsideAngular(() => {
106
+ // Force size recalculation before fitting
107
+ map.updateSize();
108
+ view.fit(extent, options);
109
+ });
110
+ }, 0);
34
111
  }
35
112
  animateView(options) {
36
113
  const view = this.getView();
37
114
  if (!view)
38
115
  return Promise.resolve();
39
116
  return new Promise((resolve) => {
40
- this.ngZone.runOutsideAngular(() => {
117
+ this.zoneHelper.runOutsideAngular(() => {
41
118
  view.animate({
42
119
  center: options.center,
43
120
  zoom: options.zoom,
44
121
  rotation: options.rotation,
45
122
  duration: options.duration ?? 250,
46
- }, () => this.ngZone.run(() => resolve()));
123
+ }, () => this.zoneHelper.runInsideAngular(() => resolve()));
47
124
  });
48
125
  });
49
126
  }
@@ -67,7 +144,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
67
144
  // OlMapComponent
68
145
  class OlMapComponent {
69
146
  mapService = inject(OlMapService);
70
- ngZone = inject(NgZone);
147
+ zoneHelper = inject(OlZoneHelper);
148
+ destroyRef = inject(DestroyRef);
71
149
  center = input([0, 0], ...(ngDevMode ? [{ debugName: "center" }] : /* istanbul ignore next */ []));
72
150
  zoom = input(0, ...(ngDevMode ? [{ debugName: "zoom" }] : /* istanbul ignore next */ []));
73
151
  rotation = input(0, ...(ngDevMode ? [{ debugName: "rotation" }] : /* istanbul ignore next */ []));
@@ -78,6 +156,7 @@ class OlMapComponent {
78
156
  mapContainerRef = viewChild.required('mapContainer');
79
157
  map;
80
158
  constructor() {
159
+ afterNextRender(() => this.initMap());
81
160
  effect(() => {
82
161
  const center = this.center();
83
162
  if (this.map)
@@ -93,16 +172,12 @@ class OlMapComponent {
93
172
  if (this.map)
94
173
  this.updateRotation(rotation);
95
174
  });
96
- }
97
- ngAfterViewInit() {
98
- this.initMap();
99
- }
100
- ngOnDestroy() {
101
- this.destroyMap();
175
+ // Cleanup when component is destroyed
176
+ this.destroyRef.onDestroy(() => this.destroyMap());
102
177
  }
103
178
  initMap() {
104
179
  const container = this.mapContainerRef().nativeElement;
105
- this.ngZone.runOutsideAngular(() => {
180
+ this.zoneHelper.runOutsideAngular(() => {
106
181
  const view = new View({
107
182
  center: fromLonLat(this.center(), this.projection()),
108
183
  zoom: this.zoom(),
@@ -111,14 +186,14 @@ class OlMapComponent {
111
186
  });
112
187
  this.map = new OLMap({ target: container, view, layers: [] });
113
188
  this.mapService.setMap(this.map);
114
- view.on('change:center', () => this.ngZone.run(() => this.emitViewChange()));
115
- view.on('change:resolution', () => this.ngZone.run(() => this.emitViewChange()));
116
- this.map.on('click', (e) => this.ngZone.run(() => this.mapClick.emit({
117
- coordinate: e.coordinate,
189
+ view.on('change:center', () => this.zoneHelper.runInsideAngular(() => this.emitViewChange()));
190
+ view.on('change:resolution', () => this.zoneHelper.runInsideAngular(() => this.emitViewChange()));
191
+ this.map.on('click', (e) => this.zoneHelper.runInsideAngular(() => this.mapClick.emit({
192
+ coordinate: toLonLat(e.coordinate, this.projection()),
118
193
  pixel: e.pixel,
119
194
  })));
120
- this.map.on('dblclick', (e) => this.ngZone.run(() => this.mapDblClick.emit({
121
- coordinate: e.coordinate,
195
+ this.map.on('dblclick', (e) => this.zoneHelper.runInsideAngular(() => this.mapDblClick.emit({
196
+ coordinate: toLonLat(e.coordinate, this.projection()),
122
197
  pixel: e.pixel,
123
198
  })));
124
199
  });
@@ -126,7 +201,7 @@ class OlMapComponent {
126
201
  }
127
202
  destroyMap() {
128
203
  if (this.map) {
129
- this.ngZone.runOutsideAngular(() => {
204
+ this.zoneHelper.runOutsideAngular(() => {
130
205
  this.map.setTarget(undefined);
131
206
  this.map.dispose();
132
207
  });
@@ -135,18 +210,37 @@ class OlMapComponent {
135
210
  }
136
211
  }
137
212
  updateCenter(center) {
138
- if (this.map) {
139
- const projectedCenter = fromLonLat(center, this.projection());
140
- this.ngZone.runOutsideAngular(() => this.map.getView().setCenter(projectedCenter));
213
+ if (!this.map)
214
+ return;
215
+ const view = this.map.getView();
216
+ const projectedCenter = fromLonLat(center, this.projection());
217
+ const currentCenter = view.getCenter();
218
+ // Only update if center is significantly different (prevents interfering with animations)
219
+ if (!currentCenter ||
220
+ Math.abs(currentCenter[0] - projectedCenter[0]) > 1 ||
221
+ Math.abs(currentCenter[1] - projectedCenter[1]) > 1) {
222
+ this.zoneHelper.runOutsideAngular(() => view.setCenter(projectedCenter));
141
223
  }
142
224
  }
143
225
  updateZoom(zoom) {
144
- if (this.map)
145
- this.ngZone.runOutsideAngular(() => this.map.getView().setZoom(zoom));
226
+ if (!this.map)
227
+ return;
228
+ const view = this.map.getView();
229
+ const currentZoom = view.getZoom();
230
+ // Only update if zoom is different (prevents interfering with animations)
231
+ if (currentZoom !== zoom) {
232
+ this.zoneHelper.runOutsideAngular(() => view.setZoom(zoom));
233
+ }
146
234
  }
147
235
  updateRotation(rotation) {
148
- if (this.map)
149
- this.ngZone.runOutsideAngular(() => this.map.getView().setRotation(rotation));
236
+ if (!this.map)
237
+ return;
238
+ const view = this.map.getView();
239
+ const currentRotation = view.getRotation();
240
+ // Only update if rotation is significantly different
241
+ if (Math.abs(currentRotation - rotation) > 0.001) {
242
+ this.zoneHelper.runOutsideAngular(() => view.setRotation(rotation));
243
+ }
150
244
  }
151
245
  emitViewChange() {
152
246
  const view = this.map?.getView();
@@ -172,7 +266,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
172
266
 
173
267
  // Provider functions
174
268
  function provideOpenLayers(...features) {
175
- return makeEnvironmentProviders([OlMapService, ...features.flatMap((f) => f.providers)]);
269
+ return makeEnvironmentProviders([
270
+ OlMapService,
271
+ OlZoneHelper,
272
+ ...features.flatMap((f) => f.providers),
273
+ ]);
176
274
  }
177
275
 
178
276
  // @angular-helpers/openlayers/core
@@ -181,4 +279,4 @@ function provideOpenLayers(...features) {
181
279
  * Generated bundle index. Do not edit.
182
280
  */
183
281
 
184
- export { OlMapComponent, OlMapService, provideOpenLayers };
282
+ export { OlMapComponent, OlMapService, OlZoneHelper, provideOpenLayers };