2

Let's say I have CountrySelect and CitySelect, where in CountrySelect are listed all countries and in CitySelect are only listed those cities of currently selected country...

What would the proper way to pass newly selected country. As I have read in vuejs documentation:

In Vue, the parent-child component relationship can be summarized as props down, events up. The parent passes data down to the child via props, and the child sends messages to the parent via events. Let’s see how they work next.

So in this case, I would detect change of select box inside of CountrySelect and fire event changed and along with it a country object. Then the parent component of CountrySelect would listen for this event and then when that happens will update its attribute country. Attribute country is "observed" in parent component and changing its value will cause for DOM to update, so HTML attribute on <city-select> tag called country would change. But then how would I detect property change of CitySelect component, since I need to reload cities via AJAX. I think of putting country property in watch object in CitySelect but that does not seems like an elegant solution to me, it doesn't feels quire right to do...

<template>
    <parent>
        <country-select name="country_id" v-model="country"></country-select>
        <city-select name="city_id" :country="country" v-model="city"></city-select>
    </parent>
<template>

<script>
    export default {
        data: function() {
            return {
                country: null,
                city: null,
            };
        }
    }
</script>

Other way around that I think is if do something like this in parent:

this.$refs.citySelect.emit('countryChanged', this.country)

or:

this.$refs.citySelect.countryChanged(this.country)

I do not know if these two are possible... So what would be the correct way for CountrySelect component to tell to its sibling component CitySelect to update cities list...

0

1 Answer 1

5

By passing country as a property to your CitySelect component, you are already doing what you need to do. When country changes, the changed value will be passed to the CitySelect component. You simply need to make sure your CitySelect component is aware of the changes.

Here is a working example.

console.clear()

// Simulated service for retrieving cities by country
const CityService = {
  cities: [
    {id: 1, country: "USA", name: "New York"},
    {id: 2, country: "USA", name: "San Francisco"},
    {id: 3, country: "USA", name: "Boston"},
    {id: 4, country: "USA", name: "Chicago"},
    {id: 5, country: "England", name: "London"},
    {id: 6, country: "England", name: "Bristol"},
    {id: 7, country: "England", name: "Manchester"},
    {id: 8, country: "England", name: "Leeds"},
  ],
  getCities(country){
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(this.cities.filter(city => city.country === country)), 250)
    })
  }
}

Vue.component("CountrySelect",{
  props: ["value"],
  template: `
  <select v-model="selectedCountry">
    <option v-for="country in countries" :value="country">{{country}}</option>
  </select>
  `,
  data(){
    return {
      countries: ["USA", "England"]
    }
  },
  computed:{
    selectedCountry:{
      get(){return this.value},
      set(v){this.$emit('input', v)}
    }
  }
})

Vue.component("CitySelect", {
  props: ["value", "country"],
  template: `
  <select v-model="selectedCity">
    <option v-for="city in cities" :value="city">{{city.name}}</option>
  </select>
  `,
  data(){
    return {
      cities: []
    }
  },
  computed:{
    selectedCity:{
      get(){return this.value},
      set(v){this.$emit('input', v)}
    }
  },
  watch:{
    country(newCountry){
      // simulate an AJAX call to an external service
      CityService.getCities(newCountry).then(cities => this.cities = cities)
    }
  }
})

new Vue({
  el: "#app",
  data:{
    country: null,
    city: null
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
  <country-select v-model="country"></country-select>
  <city-select :country="country" v-model="city"></city-select>
  <hr>
  Selected Country: {{country}} <br>
  Selected City: {{city}}
</div>

Sign up to request clarification or add additional context in comments.

4 Comments

So you have created computed property that uses country property and because that, filteredCities is recalculateded each time country is changed, because it depends on it? And when filteredCities is recalculated the DOM is updated... But I want to use AJAX, and in that case how would I fetch new list of cities based on country id (/api/lists/countries/3/cities) ....?
@clzola I was just working on that :) Updated with a simulated AJAX call.
Aha, so it is just as I mentioned in my question, to "watch" property :D thanks
@clzola I think a watch is the most appropriate way here. You could use a computed, but async computed values are generally a bad idea.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.