I'm creating my own "Editor" widget and stumbled upon some very strange behavior of the `applyEdits` method on `FeatureLayer`. When using `applyEdits`, if you're trying to use `addFeatures` and then shortly after call `applyEdits` again but with `deleteFeatures` of the newly added features, it won't delete them.
I'm assuming the above happens due to `applyEdits` being an asynchronous function and thus it hasn't actually finished `addFeatures` before we're calling `applyEdits` with `deleteFeatures`.
In the code attached below I've implemented three versions of "draw a point to the screen and delete what was previously drawn".
1. `drawPointGraphicsIntended` - this draws Graphic points using the "intended" way according to documentation, but it slow and buggy (NOTE: this isn't bugged if you move the cursor very slowly)
2. `drawPointGraphicsIntendedFixed` - this draws Graphic points using the "intended" way, but it "fixed" the buggy part, by basically deleting a graphic twice.
3. `drawPointGraphicsFastAndResponsive` - this draws Graphic points onto the `view`'s graphics. This is very fast and responsive and works as the API intended.
-----------------
I'm curious if there is an actual way to use the `FeatureLayer` API to not suck. The reason why I'd prefer to use the `FeatureLayer` API is so that I can access `Graphic.layer` to get the parent Layer, if I use the `view`, it'd get the global View layer instead of the actual parent layer.
Code:
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="initial-scale=1,maximum-scale=1,user-scalable=no"
/>
<title>
Update FeatureLayer using applyEdits() | Sample | ArcGIS Maps SDK for
JavaScript 4.27
</title>
<link
rel="stylesheet"
href="https://js.arcgis.com/4.27/esri/themes/light/main.css"
/>
<script src="https://js.arcgis.com/4.27/"></script>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
</style>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"esri/Graphic",
], (Map, MapView, FeatureLayer, Graphic) => {
const pointLayer = new FeatureLayer({
source: [],
geometryType: "point",
fields: [
{
name: "ObjectID",
alias: "ObjectID",
type: "oid",
},
],
objectIdField: "ObjectID",
});
const map = new Map({
basemap: "dark-gray-vector",
layers: [pointLayer],
});
const view = new MapView({
container: "viewDiv",
map: map,
center: [-117.18, 34.06],
zoom: 14,
});
// cache for drawn graphics
let pointGraphicCache = [];
// implementation of what _should_ be used according to documentation
const drawPointGraphicsIntended = (paths) => {
pointLayer.applyEdits({
deleteFeatures: pointGraphicCache,
});
pointGraphicCache = paths[0].map(
(point) =>
new Graphic({
geometry: {
type: "point",
longitude: point[0],
latitude: point[1],
},
})
);
pointLayer.applyEdits({
addFeatures: pointGraphicCache,
});
};
// implementation of how it should be used according to documentation, but fixed
const drawPointGraphicsIntendedFixed = (paths) => {
pointLayer.applyEdits({
deleteFeatures: pointGraphicCache.map((data) => {
data.deleted = true;
return data.graphic;
}),
});
const graphics = paths[0].map((point) => ({
graphic: new Graphic({
geometry: {
type: "point",
longitude: point[0],
latitude: point[1],
},
}),
deleted: false,
}));
pointGraphicCache = graphics;
pointLayer
.applyEdits({
addFeatures: graphics.map((g) => g.graphic),
})
// after applyEdits is finished, check if it got deleted during execution
.then(() => {
const graphicsToDelete = graphics
.filter((g) => g.deleted)
.map((g) => g.graphic);
if (graphicsToDelete.length) {
pointLayer.applyEdits({
deleteFeatures: graphicsToDelete,
});
}
});
};
// implementation of what is _expected_ of how the behavior of the map should work/look
const drawPointGraphicsFastAndResponsive = (paths) => {
for (const graphic of pointGraphicCache) {
view.graphics.remove(graphic);
}
pointGraphicCache = paths[0].map(
(point) =>
new Graphic({
geometry: {
type: "point",
longitude: point[0],
latitude: point[1],
},
})
);
for (const graphic of pointGraphicCache) {
view.graphics.add(graphic);
}
};
// draw point on pointer move
view.on("pointer-move", (event) => {
const mapPoint = view.toMap(event);
const paths = [[[mapPoint.longitude, mapPoint.latitude]]];
// NOTE: change execution method here
drawPointGraphicsIntended(paths);
// drawPointGraphicsIntendedFixed(paths);
// drawPointGraphicsFastAndResponsive(paths);
});
});
</script>
</head>
<body>
<div id="viewDiv"></div>
</body>
</html>