@angular-helpers/openlayers 0.1.1 → 0.3.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.
- package/README.md +79 -0
- package/fesm2022/angular-helpers-openlayers-controls.mjs +496 -107
- package/fesm2022/angular-helpers-openlayers-core.mjs +130 -32
- package/fesm2022/angular-helpers-openlayers-interactions.mjs +583 -10
- package/fesm2022/angular-helpers-openlayers-layers.mjs +318 -49
- package/fesm2022/angular-helpers-openlayers-overlays.mjs +437 -7
- package/package.json +3 -2
- package/types/angular-helpers-openlayers-controls.d.ts +161 -23
- package/types/angular-helpers-openlayers-core.d.ts +72 -7
- package/types/angular-helpers-openlayers-interactions.d.ts +315 -6
- package/types/angular-helpers-openlayers-layers.d.ts +61 -10
- package/types/angular-helpers-openlayers-overlays.d.ts +196 -11
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
115
|
-
view.on('change:resolution', () => this.
|
|
116
|
-
this.map.on('click', (e) => this.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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([
|
|
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 };
|