I know this is a late message, but you can achieve this by doing the following:
const getAllLayerViews = mapView.map.allLayers.map(layer => mapView.whenLayerView(layer));
const layerViews = await Promise.all(getAllLayerViews);
layerViews.forEach(layerView => {
// monitor layer loading status
const watchUpdatesHandle = watch(
() => layerView.updating,
updating => {
const layer = getLayerFromId(layerView.layer.id, layers);
if (!layer) {
return;
}
controller.broadcast("layerLoadingStatus", {
layer: layer,
isLoading: updating
});
// if it finished loading, then broadcast visible features
if (!updating && isQueryable(layerView.layer)) {
const allQuery = new Query();
allQuery.geometry = mapView.extent;
allQuery.spatialRelationship = "intersects";
layerView.layer
.queryFeatures(allQuery)
.then(visibleFeatures => {
controller.broadcast("visibleFeaturesChanged", {
layer: layer,
features: visibleFeatures
});
})
.catch(console.error);
}
}
);
// clean up when the layer is destructed
layerView.addHandles(watchUpdatesHandle);
});
This snippet starts off by watching the loading status of all layers available on your webmap (using ESRI's `watch` utility - very useful and more resources about that here: https://developers.arcgis.com/javascript/latest/api-reference/esri-core-reactiveUtils.html)
Then once the layer is no longer loading, check if the `layer` supports the `queryFeatures` method, if it does, then we create a new filter which uses the extent/viewport of the map itself in the `Query`. Finally, broadcast these changes to your UI so that it informs you of what is currently visible in the map view.