I have a JS API 4.x application that needs to load arbitrary layers provided by users and display popups for these layers. Several of these layers are tile layers. Part of the app is a custom TypeScript widget to add these layers to the map but in it I'm having trouble setting the popup template. I want to use the feature service sublayer of the tile layer to access feature info and display the popups (like in this post).
This code finds the fieldInfos for a layer (query the service for all its fields and returns them in an array of field objects with attributes of type, name and alias):
private async _getFieldInfo(layer: LayerRef): Promise<Array<{fieldName: string, label: string}>> {
...
const response = await request(fieldUrl, {query: {f: "json"}, responseType: "json"}).then(response => response.data);
const fields = response.fields;
...
const fieldInfos: {fieldName: string, label: string}[] = [];
response.type === "Feature Layer" ? fields.map((field: {name: string, type: string, alias: string}) =>
...
fieldInfos.push({fieldName: field.name, label: field.alias}) : null) : null;
return fieldInfos;
}
Then as part of my add layer function, I have:
const fieldInfos = await this._getFieldInfo(layerRef);
layer.when(function(layer: TileLayer){
const featsSublayer = layer.findSublayerById(0)
featsSublayer.load();
featsSublayer.popupEnabled = true;
featsSublayer.popupTemplate = {
title: layerRef.name,
content: [{
type: "fields",
fieldInfos: fieldInfos,
}],
outFields: ["*"]
}
});
I'm getting a type error that prevents me from compiling on the fieldInfos variable in the content object of the sublayer popup template:
Type '{ type: "fields"; fieldInfos: { fieldName: string; label: string; }[]; }' is not assignable to type 'Content'.
Object literal may only specify known properties, and 'fieldInfos' does not exist in type 'Content'.ts(2322)
Can anyone point me in the right direction to get my popup template working?
Solved! Go to Solution.
So the first snippet looks like it fixed the FieldsContent issue but there is a similar issue with the PopupTemplate. Can you try explicitly declaring the PopupTemplate class as well?
const fieldInfos = await this._getFieldInfo(layerRef);
layer.when(function(layer: TileLayer){
const featsSublayer: Sublayer = layer.findSublayerById(0)
featsSublayer.load();
featsSublayer.popupEnabled = true;
const fieldContent = new FieldsContent({
fieldInfos: fieldInfos
})
featsSublayer.popupTemplate = new PopupTemplate({
title: layerRef.name,
content: [fieldContent],
outFields: ["*"]
})
});
This is likely an autocasting issue that happens occasionally when using TypeScript. I suggest explicitly declaring the FieldsContent class:
https://developers.arcgis.com/javascript/latest/api-reference/esri-popup-content-FieldsContent.html
Thanks for the reply. I tried this:
const fieldInfos = await this._getFieldInfo(layerRef);
layer.when(function(layer: TileLayer){
const featsSublayer: Sublayer = layer.findSublayerById(0)
featsSublayer.load();
featsSublayer.popupEnabled = true;
const fieldContent = new FieldsContent({
fieldInfos: fieldInfos
})
featsSublayer.popupTemplate = {
title: layerRef.name,
content: [fieldContent],
outFields: ["*"]
}
});
and I'm getting a type error on featsSublayer.popupTemplate now:
Type '{ title: string; content: FieldsContent[]; outFields: string[]; }' is missing the following properties from type 'PopupTemplate': actions, expressionInfos, fieldInfos, lastEditInfoEnabled, and 16 more.ts(2740)
I also tried this:
const fieldInfos = await this._getFieldInfo(layerRef);
layer.when(function(layer: TileLayer){
const featsSublayer = layer.findSublayerById(0)
featsSublayer.load();
featsSublayer.popupEnabled = true;
featsSublayer.popupTemplate = {
title: layerRef.name,
content: new FieldsContent({
fieldInfos: fieldInfos,
}),
outFields: ["*"]
}
});
and get this error on content:
Type 'FieldsContent' is not assignable to type 'string | Function | Promise<any> | Content[]'.
Type 'FieldsContent' is missing the following properties from type 'Content[]': length, pop, push, concat, and 26 more.ts(2322)
So the first snippet looks like it fixed the FieldsContent issue but there is a similar issue with the PopupTemplate. Can you try explicitly declaring the PopupTemplate class as well?
const fieldInfos = await this._getFieldInfo(layerRef);
layer.when(function(layer: TileLayer){
const featsSublayer: Sublayer = layer.findSublayerById(0)
featsSublayer.load();
featsSublayer.popupEnabled = true;
const fieldContent = new FieldsContent({
fieldInfos: fieldInfos
})
featsSublayer.popupTemplate = new PopupTemplate({
title: layerRef.name,
content: [fieldContent],
outFields: ["*"]
})
});
Thank you so much, this has resolved the type errors. However I'm still not getting my popups.
const fieldInfos = await this._getFieldInfo(layerRef);
layer.when(function(layer: TileLayer){
const featsSublayer: Sublayer = layer.findSublayerById(0)
featsSublayer.load();
featsSublayer.popupEnabled = true;
const fieldContent = new FieldsContent({
fieldInfos: fieldInfos
})
featsSublayer.popupTemplate = new PopupTemplate({
title: layerRef.name,
content: [fieldContent],
outFields: ["*"]
});
});
this._addToMap([layer]);
When I add a layer using this code and log map.layers to console, I can see my layer is there, but in its sublayers array, the sublayer shows:
loaded: false
popupEnabled: true
PopupTemplate: null
I tried moving the sublayer.load, and popupTemplate declaration to happen after the layer is added to map, but it still doesn't seem to be loading or setting the popupTemplate.
Glad we fixed the type errors. If you can create a simplified sample that reproduces the popup functionality I can take a closer look. Just add the layer and popup on codepen or jsbin, no typescript needed. It sounds like it may now be an asynchronous issue somewhere.
Before making the sample can you try adding the layer inside of the layer.when promise right after setting the popupTemplate?
const fieldInfos = await this._getFieldInfo(layerRef);
layer.when(function(layer: TileLayer){
const featsSublayer: Sublayer = layer.findSublayerById(0)
featsSublayer.load();
featsSublayer.popupEnabled = true;
const fieldContent = new FieldsContent({
fieldInfos: fieldInfos
})
featsSublayer.popupTemplate = new PopupTemplate({
title: layerRef.name,
content: [fieldContent],
outFields: ["*"]
});
this._addToMap([layer]); // MOVE IT HERE
});
So I tried moving the load() and _addToMap calls around a bunch and none of the combinations resulted in the popups working. Most recently I tried putting everything within the when and addToMap after popupenabled with the same result:
async add(layerRef: LayerRef): Promise<void> {
layerRef.status = "active"
if (layerRef.type === "TileLayer") {
const layer = new TileLayer({
url: layerRef.url,
id: layerRef.group + "-" + layerRef.name,
});
const fieldInfos = await this._getFieldInfo(layerRef);
layer.when(function(layer: TileLayer){
const featsSublayer: Sublayer = layer.findSublayerById(0)
const fieldContent = new FieldsContent({
fieldInfos: fieldInfos
})
featsSublayer.popupTemplate = new PopupTemplate({
title: layerRef.name,
content: [fieldContent],
outFields: ["*"]
});
featsSublayer.load();
featsSublayer.popupEnabled = true;
this._addToMap([layer]);
});
I'm having a hard time setting up a pen, but I've got this: https://codepen.io/broms/pen/dyvQBVN that loads a TileLayer. I can't figure out why findSublayerById isn't working. I log the layer to console and I can see the sublayer there, but I'm getting an error setting popupTemplate that sublayer is undefined.
EDIT: I've got the pen to where it's displaying popups but I'm having trouble setting the content of the popups. Also not sure how to duplicate this in my widget. I think it's like you said, some async stuff isn't lined up right. Do you have any tips on how to debug this? I'm really in the dark, I can't even get console logging to work within my viewmodel methods.
I got the codepen sample to work by setting type to "fields" for the fieldContent:
var fieldContent = {
type: "fields",
fieldInfos: [{
fieldName: "NAME",
label: "Name",
type: "text"
}, {
fieldName: "CONDITION",
label: "Condition"
}]
}
So at least we know that the functionality is possible. It does sound like an asynchronous issue which can be hard to debug, especially over a forum. Here is an article that has some recommendations for debugging asynchronous issues. Here is ArcGIS JS API specific documentation about async programming patterns. If your code is somewhere public like github I can take a closer look as well.
Thanks for the replies, I really appreciate your help.
I looked at it again today and it's working now. I'm not really sure which change did the trick and I also cleared my cache since working on it last, but whatever the case, I'm very pleased to have this bug in my app fixed, so thank you very much once again.
For reference, this is the add layer code block in its current working form:
async add(layerRef: LayerRef): Promise<void> {
layerRef.status = "active"
if (layerRef.type === "TileLayer") {
const layer = new TileLayer({
url: layerRef.url,
id: layerRef.group + "-" + layerRef.name,
});
const fieldInfos = await this._getFieldInfo(layerRef);
layer.when(function(layer: TileLayer){
const fieldContent = new FieldsContent({
fieldInfos: fieldInfos
});
const featsSublayer: Sublayer = layer.findSublayerById(0)
featsSublayer.popupTemplate = new PopupTemplate({
title: layerRef.name,
content: [fieldContent],
outFields: ["*"]
});
featsSublayer.load();
featsSublayer.popupEnabled = true;
});
this._addToMap([layer]);