ArcGIS JS API 4.x not supported with VueJS 3.0?

7851
14
Jump to solution
10-28-2020 06:52 AM
Jay_Gregory
Occasional Contributor III

Esri's JS API worked beautifully with VueJS 2.x - however trying to incorporate into a VueJS 3.0 application is proving to be challenging, and I'm wondering if the incompatibilities are irreconcilable without significant changes to the API. 

Although I can't know for sure, I think the issue revolves around that Vue3.0 wraps all reactive objects in a JavaScript Proxy object, which allows it to intercept / trap getters and setters. 

From Vue's own documentation:

The use of Proxy does introduce a new caveat to be aware with: the proxied object is not equal to the original object in terms of identity comparison (===). For example:  The original and the wrapped version will behave the same in most cases, but be aware that they will fail operations that rely on strong identity comparisons.

Creating a simple map with a feature layer, for example, if the feature layer is a reactive object in the VueJS 3.0 ecosystem, gives some strange errors and requires a workaround.  The following code is using the new Composition API in Vue (that is why I reference everything with the value property but it is the correct programming pattern here), and throws an error at the last line - the layer is never added to the map. To those familiar with the Composition API, using a reactive object (instead of ref like in the code) generates the same issues. 

let featurelayer = ref(null)
featurelayer.value = new FeatureLayer(url);
map.add(featurelayer.value);

 Uncaught (in promise) TypeError: 'get' on proxy: property '__accessor__' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '#<a>' but got '[object Object]')

However, if I change the last line to 

map.add(featurelayer.value.load());

The layer is successfully added.  An acceptable but annoying workaround as an application scales. 

There are other issues however for which I have been unable to find a workaround.  

For example:

mapView.whenLayerView(featurelayer.value).then((layerView) => {
   console.log(layerView);
});‍‍‍‍‍‍‍‍‍

throws the following error:

 Uncaught (in promise) e {name: "view:no-layerview-for-layer", details: {…}, message: "No layerview has been found for the layer". 

Using featurelayer.value.load() or featurelayer.value.createLayerView() does not work.  

So ultimately I'm curious if anyone knows what is happening with these seeming incompatibilities, and more specifically, if the Esri JS API team would ever consider making the changes necessary to play well with a (increasing in popularity) framework such as VueJS 3.x.

Tags (4)
0 Kudos
1 Solution

Accepted Solutions
by Anonymous User
Not applicable

So I think there are some issues here in the patterns being used.  One of the nice things about the Composition API and Vue3 in general is you can explicitly choose what is reactive and what isn't.  In my opinion, I don't think you want to make your view and layer reactive.

If you really do need to handle changes on the view or layer, you can either use the Accessor watch method, or watchUtils.  Then anything on the Vue side that needs to be reactive should be reactive.  This way, you're not creating extra overhead or reinventing the wheel on watching/handling changes to these objects.

I was able to get your sample working without throwing errors with the following code in App.vue:

CalebMackey1_0-1615997750314.png

 

 

import Map from "@arcgis/core/Map";
import MapView from "@arcgis/core/views/MapView";
import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
import { defineComponent, nextTick } from "vue";


export default defineComponent({
  
  setup() {
    /**
     * do we really want vue to make these reactive? Are they really going to change?
     * this will add extra overhead. If we need to handle reactive changes on esri stuff,
     * we can take advantage by using "esri/core/watchUtils" or by using the Accessor.watch()
     */
    // let layer = ref(null);
    // let view = ref(null);

    //same issues with reactive object as well
    ///let app = reactive({ zones: null, view:null });
    
    // instead declare them as static data properties
    const map = new Map({
      basemap: "topo-vector",
    });

    const view = new MapView({
      container: "esri-map", // just pass this here to set required property
      map,
      zoom: 5,
      center: [-95, 39],
      popup: {
        autoOpenEnabled: false,
      },
    });

    const layer = new FeatureLayer({
      portalItem: {
        id: "9e2f2b544c954fda9cd13b7f3e6eebce",
      },
      outFields: ["*"],
      title: "Recent Earthquakes",
    });

    map.add(layer)

    return {
      view,
      map,
      layer
    }

  },

  created(){
    // verify we have access to view, layer
    console.log('view: ', this.view)
    console.log('map: ', this.map)
    
    /**
     * the map actually won't render from our setup() because the
     * "esri-map" div does not exist yet, so we must re-set it here 
     */
    nextTick(()=> this.view.container = this.$refs.esriMap)

    // here is where we can actually test things out
    this.view.whenLayerView(this.layer).then((layerView) => {
      console.log("layerview generated");
      console.log(layerView);
    });
  }
});

 

View solution in original post

Tags (2)
0 Kudos
14 Replies
Jay_Gregory
Occasional Contributor III

Noah SagerJim Barry‌ - Curious if anyone on the API team is aware of this and/or knows of any workarounds or resolutions.  

0 Kudos
Jay_Gregory
Occasional Contributor III

Andy Gup‌ - Will 4.18 maybe address this?

0 Kudos
AndyGup
Esri Regular Contributor

Hi Jay, thanks for reporting this, we are aware. There's a possibility that a general fix already being working on for Accessor in 4.18 might fix your issue. I'm not sure exactly when the fix will be checked in, but you will be able to test it out on /next build prior to the production release: https://github.com/Esri/feedback-js-api-next.

Jay_Gregory
Occasional Contributor III

Thanks Andy!  I'm glad you're aware of it, and it's on your fix radar.  

0 Kudos
Jay_Gregory
Occasional Contributor III

@AndyGup - 

Andy, correct me if I'm wrong, but this didn't seem to be resolved with 4.18.  I threw together a quick github repo to reproduce errors @ https://github.com/crackernutter/vuejs3-arcgisapi-test

Curious if you think this might be resolved at 4.19?  I was waiting to develop a couple apps until VueJS 3.x compatibility with your API was resolved.  If this isn't in the pipeline for the near term that's fine - I would just love to know.  I would hate to move forward with VueJS 2.x development only to have this issue resolved in March.

0 Kudos
baohuachu2
New Contributor II

in 4.24   it is not solved.

0 Kudos
Jay_Gregory
Occasional Contributor III

@baohuachu2 Yeah, I'm unsure if Esri will change their API to support native Proxy objects like Vue uses.  Some of the additional code examples at the bottom of this thread show some good patterns for working with Vue 3.x and AGIS JS API.  In short, don't set your esri objects (map view, map, etc) to be vue reactive in your individual components, but instead use the AGIS JS API to handle the reactivity of Esri objects.  You can create non reactive objects and return them in the setup function to use through the rest of your component.  I don't think this kind of option existed for Vue2. 

If you are using Vuex, I believe I ended up using toRaw and markRaw (https://vuejs.org/api/reactivity-advanced.html#toraw) to handle reactivity of Esri objects in Vuex.  Hope this helps.  At this point it's been a while so I forget the exact details.  🙂

0 Kudos
by Anonymous User
Not applicable

So I think there are some issues here in the patterns being used.  One of the nice things about the Composition API and Vue3 in general is you can explicitly choose what is reactive and what isn't.  In my opinion, I don't think you want to make your view and layer reactive.

If you really do need to handle changes on the view or layer, you can either use the Accessor watch method, or watchUtils.  Then anything on the Vue side that needs to be reactive should be reactive.  This way, you're not creating extra overhead or reinventing the wheel on watching/handling changes to these objects.

I was able to get your sample working without throwing errors with the following code in App.vue:

CalebMackey1_0-1615997750314.png

 

 

import Map from "@arcgis/core/Map";
import MapView from "@arcgis/core/views/MapView";
import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
import { defineComponent, nextTick } from "vue";


export default defineComponent({
  
  setup() {
    /**
     * do we really want vue to make these reactive? Are they really going to change?
     * this will add extra overhead. If we need to handle reactive changes on esri stuff,
     * we can take advantage by using "esri/core/watchUtils" or by using the Accessor.watch()
     */
    // let layer = ref(null);
    // let view = ref(null);

    //same issues with reactive object as well
    ///let app = reactive({ zones: null, view:null });
    
    // instead declare them as static data properties
    const map = new Map({
      basemap: "topo-vector",
    });

    const view = new MapView({
      container: "esri-map", // just pass this here to set required property
      map,
      zoom: 5,
      center: [-95, 39],
      popup: {
        autoOpenEnabled: false,
      },
    });

    const layer = new FeatureLayer({
      portalItem: {
        id: "9e2f2b544c954fda9cd13b7f3e6eebce",
      },
      outFields: ["*"],
      title: "Recent Earthquakes",
    });

    map.add(layer)

    return {
      view,
      map,
      layer
    }

  },

  created(){
    // verify we have access to view, layer
    console.log('view: ', this.view)
    console.log('map: ', this.map)
    
    /**
     * the map actually won't render from our setup() because the
     * "esri-map" div does not exist yet, so we must re-set it here 
     */
    nextTick(()=> this.view.container = this.$refs.esriMap)

    // here is where we can actually test things out
    this.view.whenLayerView(this.layer).then((layerView) => {
      console.log("layerview generated");
      console.log(layerView);
    });
  }
});

 

Tags (2)
0 Kudos
Jay_Gregory
Occasional Contributor III

Thank you Caleb!  This is incredibly useful - I will dig into the code.  I was afraid I'd have to go the "change pattern" route by using Esri's baked in watchUtils.  I'm concerned that as the application scales, using two different reactivity programming patterns (one for Esri JS API objects, one for the rest) might prove unwieldy and confusing.  Mostly as I look to refactor a sizable Vue 2.x application that relied heavily on reactivity of various Esri JS objects (Vue 2.x's reactivity did work with Esri's JA API), I'm just grappling with having to swap out Vue's reactivity with Esri's.  Not impossible, but also not fun.  

Various components that watch for a layer's visibility or opacity change, when graphics are added to a graphicsLayer, when a search source changes in a search widget, passing objects between components as props are all pieces that might need to be refactored for this new pattern.  But who knows - maybe it'll be simpler than I think.  I was just hoping that Esri would fix it, but totally understand that it quite clashes with the way they've built their API.  

0 Kudos