Nika Kurashvili
Nika Kurashvili

Reputation: 6462

vue nexttick for map

I have a <div id="map" />

I have a mounted method in which I initialize map.

mounted(){
 this.map = L.map(this.mapId, { preferCanvas: true }).setView(
  [this.initialLatitudeOriginalPoint, this.intiialLongitudeOriginalPoint],
        3
 )
L.tileLayer(
        tileconfig + '?access_token=' + key
        { maxZoom: 25, id: 'mapbox.streets' }
      ).addTo(this.map)
}

I have a prop called markers. and I have a watcher for it with immediate true

markers:{
      handler(newVal, oldVal){
        this.showMarkers()
      },
      immediate: true
    },

Question- Looks like watcher gets called first. before mounted. Then showMarkers function throws error as map won't be defined because watcher got first before mounted. I really need immediate true for that watcher. Is there any way I can know that until map is not defined, watcher should wait?

What I think of: I think of using nextTick. but I don't understand one point about it. If I write this.$nextTick in my watcher's handler function, when will that nextTick callback be called? after dom got updated in this specific component or even in parent and parents of parents? if it doesn't care current component, then nextTick might be a little bit wrong in my case. I just want to understand this last thing about nextTick. any clue?

Upvotes: 0

Views: 508

Answers (1)

skirtle
skirtle

Reputation: 29092

Firstly, you are correct that immediate will cause the watch to be triggered before the component is mounted. The role of watch is primarily to perform updates to properties based on other properties. So it will wait for all the basic properties (props, data, etc.) to be initialised but it assumes that rendering will need the output of the watch to render correctly. In your case this isn't true.

The way I would write this is:

mounted () {
  // ... map stuff ...

  this.showMarkers()
},

watch: {
  markers: 'showMarkers'
}

If there needs to be a nextTick in there I'd be inclined to do that inside showMarkers rather than making it the caller's problem.

However, you don't have to do it this way. You can do it using immediate and nextTick, as suggested in the question.

So the key thing is to understand when exactly nextTick is called.

If a rendering dependency changes Vue will not re-render the component immediately. Instead it's added to a queue. Once all your other code has finished running Vue will process that queue. This is more complicated than it sounds as various components within the parent/child hierarchy may need updating. Updating a parent may result in the child no longer existing.

Once the queue has been processed and the DOM updated, Vue will call any nextTick callbacks. Any further changes that occur inside the callback will be added to a new rendering queue.

There are a couple of key points to note here.

  1. While Vue may have updated the DOM, the browser won't actually paint all the new nodes until the JavaScript code has finished. That includes any nextTick callbacks. So if nextTick triggers further updates the user will not see the interim state of the page.
  2. All of this is assuming that Vue is tracking the updates and performing the rendering. If you're doing rendering outside of Vue, as you are via Leaflet, then Vue can't do as much to help you.

So let's consider the following:

markers:{
  async handler(newVal, oldVal){
    await this.$nextTick()  
    this.showMarkers()
  },
  immediate: true
},

I've used async/await here rather than passing a callback but the end result is the same.

When the handler is called the first time it is before the component has rendered. The $nextTick will be called after all rendering has completed (including other components). So long as other components don't assume that the markers already exist this shouldn't be a problem, they'll still be created before the user sees anything.

When markers subsequently changes it may not actually trigger any rendering within Vue. Unless you're using markers somewhere within one of your templates it won't be a rendering dependency so no new rendering will be triggered. That's not necessarily a problem as $nextTick will still work even if there's nothing for Vue to render.

Upvotes: 2

Related Questions