1.3 Axios, components and props - VVJS/intro GitHub Wiki

If you think you have messed something up, or are joining in now, checkout tag 1.3:

git checkout 1.3 --force

Importing Axios

Let's being by adding a script section to the Home.vue file, which can be found in /src/views/. Usually, it would go between the template and style sections, but it's up to you.

<script>
import axios from 'axios'

export default {
    name: 'App'
}
</script>

Axios provides a simple interface for making HTTP requests. You can think of it as By importing it in our Home component (import axios from 'axios'), we can access axios anywhere in the script tag.

The Mounted Method

Let's add a mounted method to start hitting the API. The mounted method is called after the vue component is loaded into the DOM, and the component is available as this.$el. We will use mounted for simplicity's sake, but if you would like to see the rest of the lifecycle hooks you can find them here.

Our new asynchronous mounted method will be placed after the data method in our component. We will be using this hardcoded URL for now, but in a real app we might get the users location and use that to find the sport places around them. Edit your scripts section so it looks like this:

<script>
import axios from 'axios'

export default {
    name: 'App',
    data() {
        return {
            sportPlaces: []
        }
    },
    async mounted() {
        const { data } = await axios.get(
            'https://sportplaces-api.herokuapp.com/api/v1/places?radius=99&origin=-73.5826985,45.5119864'
        )
        console.log(data)
    }
}
</script>

Load up your view, and open the developer tools window in the browser. If you are using chrome, with the vue app page focussed, press F12 or right click and select Inspect Element or Inspect. Once you have that open, click on the Console tab at the top.

Now save your vue component and refresh your page. If all is working correctly, we should see some results.

As you may have noticed, we'd added sportPlaces to our data method previously. Now let's replace console.log(data) with this.sportPlaces = data.data.features and we will have the sportPlaces available in the view.

    async mounted() {
        const { data } = await axios.get(
            'https://sportplaces-api.herokuapp.com/api/v1/places?radius=99&origin=-73.5826985,45.5119864'
        )
        this.sportPlaces = data.data.features;
    }

Let's iterate over them using the v-for directive we learned earlier.
Replace the the v-layout element with:

<v-layout row wrap>
    <v-flex xs12 md6 v-for="place in sportPlaces.slice(0, 20)" :key="place.properties.uuid">
        <v-list-tile avatar>
            <v-list-tile-avatar>
                <img :src="getPhotoUrl(place)">
            </v-list-tile-avatar>
            <v-list-tile-content>
                <v-list-tile-title class="capitalize title">
                    {{ place.properties.name }}
                </v-list-tile-title>
            </v-list-tile-content>
        </v-list-tile>
    </v-flex>
</v-layout>

Once you save this, you will notice an error in your log.

vue.runtime.esm.js?2b0e:587 [Vue warn]: Property or method "getPhotoUrl" is not 
defined on the instance but referenced during render. Make sure that this 
property is reactive, either in the data option, or for class-based components, 
by initializing the property. See: 
https://vuejs.org/v2/guide/eactivity.html#Declaring-Reactive-Properties.

found in

---> <App> at src/views/Home.vue
       <VContent>
         <VApp>
           <App> at src/App.vue
             <Root>

This is because we have not created the getPhotoUrl method yet. Before doing this, let's create a new component for our place items.

Components

Make a new file in the /src/components/ directory called ListItemPlace.vue. For the template, copy over the v-flex element, but leave the v-for behind. It should look like this:

<template>
    <v-flex class="px-1" xs12 md6>
        <v-list-tile avatar>
            <v-list-tile-avatar>
                <img :src="getPhotoUrl(place)">
            </v-list-tile-avatar>
            <v-list-tile-content>
                <v-list-tile-title class="capitalize title">
                    {{ place.properties.name }}
                </v-list-tile-title>
            </v-list-tile-content>
        </v-list-tile>
    </v-flex>
</template>

Component Props

In the script section, we will introduce a new property called props. This is an array of strings which we can use to pass data into our new component from a parent. You can learn more about props here.

<script>
export default {
    name: 'PlaceListItem',
    props: ['place'],
    methods: {
        getPhotoUrl(place) {
            return `https://s3-us-west-2.amazonaws.com/meetups.hc/VVJS/intro/${place.properties.google_place_id}.jpg`
        }
    }
}
</script>

The getPhotoUrl method is a simple method to extract the photo from the google maps photo reference. There is also a backup picture, in case the API does not have a picture for the given place. Let's also slap a bit of style at the end of the vue file there:

<style scoped>
/deep/ .list__tile {
    border-radius: 5px;
    border: 1px solid #dfdfdf;
    margin-bottom: 8px;
}
/deep/ .list__tile:hover {
    background-color: #0082c385;
}
</style>

Now to display this new component in our Home view, we must first import it. Back in Home.vue, add the new import after the axios one:

...
<script>
import axios from 'axios'
import listItemPlace from '@/components/ListItemPlace.vue'
...

and add the new components property to your component:

    async mounted() {
        ...
    },
    components: {
        listItemPlace
    }
}

Remove the old style tag from Home.vue, and we can now use our new component in the template section. Replace the inside of the v-layout element with:

<list-item-place :place="place" v-for="place in sportPlaces.slice(0, 20)" 
    :key="place.properties.uuid"></list-item-place>

Save it and check out your page! It's coming along now. You should get something like this:

Sanity check: When you are all done, you should have something like this:

Home.vue

<template>
    <v-container fluid>
        <v-slide-y-transition mode="out-in">
            <v-layout row wrap>
                <list-item-place :place="place" v-for="place in sportPlaces.slice(0, 20)"
                    :key="place.properties.uuid"></list-item-place>
            </v-layout>
        </v-slide-y-transition>
    </v-container>
</template>

<script>
import axios from 'axios'
import listItemPlace from '@/components/ListItemPlace.vue'

export default {
    name: 'App',
    data() {
        return {
            sportPlaces: []
        }
    },
    async mounted() {
        const { data } = await axios.get(
            'https://sportplaces-api.herokuapp.com/api/v1/places?radius=99&origin=-73.5826985,45.5119864'
        )
        this.sportPlaces = data.data.features
    },
    components: {
        listItemPlace
    }
}
</script>

ListItemPlace.vue

<template>
    <v-flex class="px-1" xs12 md6>
        <v-list-tile avatar>
            <v-list-tile-avatar>
                <img :src="getPhotoUrl(place)">
            </v-list-tile-avatar>
            <v-list-tile-content>
                <v-list-tile-title class="capitalize title">
                    {{ place.properties.name }}
                </v-list-tile-title>
            </v-list-tile-content>
        </v-list-tile>
    </v-flex>
</template>

<script>
export default {
    name: 'PlaceListItem',
    props: ['place'],
    methods: {
        getPhotoUrl(place) {
            return `https://s3-us-west-2.amazonaws.com/meetups.hc/VVJS/intro/${place.properties.google_place_id}.jpg`
        }
    }
}
</script>

<style scoped>
/deep/ .list__tile {
    border-radius: 5px;
    border: 1px solid #dfdfdf;
    margin-bottom: 8px;
}
/deep/ .list__tile:hover {
    background-color: #0082c385;
}
</style>

Next step

⚠️ **GitHub.com Fallback** ⚠️