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

3683
12
Jump to solution
10-28-2020 06:52 AM
Jay_Gregory
Regular Contributor

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
12 Replies
Jay_Gregory
Regular Contributor

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
Regular Contributor

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
Regular Contributor

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

0 Kudos
Jay_Gregory
Regular Contributor

@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
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
Regular Contributor

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
by Anonymous User
Not applicable

No problem.  This concerns me a bit too as we use Vue for all of our apps as well.  They are currently all written in Vue 2.x, but future apps may be built with 3.x.  We use vuex heavily for state management in larger apps and if we cannot store esri objects there due the conflicts with Accessor, we may be in trouble.

Hopefully refactoring won't be too bad and will likely involve shifting things from Vue watchers to accessor watchers/watchUtils in the created() method.  I know in the past in terms of watching things like graphics array updates, we have always used esri watchers for that since they are Collections and not actually native Arrays.

The best solution is what you mentioned though, which is that hopefully esri can address this somehow.  I feel this will present some major challenges as the conflict seems to be tied to reactivity/getters/setters built-in into Accessor, which is at the core of almost everything in the JS API.  

 

Jay_Gregory
Regular Contributor

I have the same concerns with Vuex too....and agree that their modifying Accessor to support Proxy would be a tall order.  

0 Kudos