If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeOur app is really starting to come together. I think it's time to make our data dynamic: the categories on the sidebar are hardcoded and the products aren't even loading yet. Let's make some Ajax calls!
To make those, we're going to use a library called Axios. To install it, open a new terminal tab and run:
yarn add axios --dev
The --dev
part isn't very important.
The other popular option for Ajax calls is to use fetch()
instead of Axios. fetch()
is actually a built-in JavaScript function, which means you don't need any outside library. However, if you need to support IE 11, then you will need a polyfill to use it. Both Axios and fetch are great options.
For our first trick, let's load products onto the page. Our app already has an API - powered by API Platform - and you can see its docs by going to /api
. Scroll down to the section about products and expand the endpoint for GET /api/products
.
The best way to see how this works is... to try it! Let's see... hit Execute and... here's the response body. We already have a set of products in the database.
If you haven't used API Platform before - it's no problem. But, the structure with @id
, @context
, hydra:member
and other keys might look odd. This is JSON, but it's using a format called JSON-LD Hydra, which is basically JSON with extra metadata: each response will have the same structure with extra fields to give you more info. It's super handy.
Now, notice that the URL to the endpoint is /api/products
. But if we put /api/products
in our browser... we don't see JSON! It's the same documentation page! That's because API Platform realizes - by reading the Accept
header - that we're requesting this from a browser and so it returns HTML. When we request this from Axios with no Accept
header, we'll get back JSON.
But if you ever want to see the JSON in a browser to see how it looks, there's a hack: add .jsonld
to the end of the URL. This is our endpoint.
Let's go all the way back to our homepage and... I'll re-open the browser dev tools.
Ok, when we load products, which component is going to need that data? The top-level products.vue
component renders the sidebar and catalog. We could load the products here... but we won't actually need them. Hold Command or Ctrl and click <catalog
to jump to that component.
Ah, this is the component that needs the products data.
Here's the goal: as soon as Vue loads this component, we'll start the Ajax call so that we can load the products as quickly as possible. Fortunately, Vue allows us to run code during its startup process, and there are two main "hook" points: mounted
and created
. We'll talk more about these later but Vue considers your component mounted
when it's actually added to the page - like, in products.js
when we call .$mount()
.
To run code right after our component is mounted, all we need to do is create a function called mounted()
. Inside, we'll make the Ajax call.
How? First, at the top of the script
section import axios from 'axios'
.
... lines 1 - 22 | |
<script> | |
import axios from 'axios'; | |
... lines 25 - 42 | |
</script> |
Then, using Axios is beautifully simple: axios.get('/api/products')
. And like every Ajax library, this will return a Promise, which you can learn all about in a JavaScript Tutorial here on SymfonyCasts.
To use the Promise, add .then()
, and pass an arrow function with response
as the argument. Let's console.log(response)
to see what it looks like.
... lines 1 - 36 | |
mounted() { | |
axios.get('/api/products').then((response) => { | |
console.log(response); | |
}); | |
}, | |
... lines 42 - 44 |
Testing time! Back over on the browser, click to view the console. Thanks to hot module replacement... that already ran! But to make the flow more realistic, let's refresh the page.
Now... boom! The log shows up almost instantly. The response
is an object with headers
, status
and other things. What we want is data
. One of the nice features of Axios is that it decodes the JSON automatically.
When you're working with JSON-LD Hydra like this, the collection of items is stored on a hydra:member
property. Yep, it's an array with 12 products. We have product data!
Next, this is working great, but I'm going to choose a slightly different syntax for handling Promises: async and await. Then, we'll use our brand new data to render those products onto the page.
Hey David,
Actually, all our JS dependencies are dev dependencies if you take a look at package.json file :) That's because technically there are dev deps, nothing is revealed to the user directly, everything is passed though Webpack Encore, i.e. precompiled by Webpack Encore, and technically Webpack Encore decides what to reveal to the end user. And so, it's not that much important where to put that JS dependency - into "dependencies" or in "devDependencies" section. You can do whatever you like, but you need to understand this concept with Webpack Encore. And it's kind of a good practice to put everything in devDependencies when you're handling your assets via Webpack Encore.
I hope this helps!
Cheers!
Yeah thank you. That's interesting indeed. Now I'm slightly less ignorant than I was a couple minutes ago!
Hey David,
Haha, great! :) But in short, that doesn't matter much and works both ways, depends on your preferences ;)
Cheers!
Hi guys.
I got an error at the last step:
`xhr.js:184 GET http://127.0.0.1:8000/api/products 500 (Internal Server Error)
createError.js:16 Uncaught (in promise) Error: Request failed with status code 500
at createError (createError.js:16)
at settle (settle.js:17)
at XMLHttpRequest.handleLoad (xhr.js:69)`
Probably because of database connection:hydra:description: "An exception occurred in driver: SQLSTATE[HY000] [2002] Connection refused"
But locally via browser it works! http://127.0.0.1:8000/api/products
Any ideas?
Hey triemli!
Hmm, that's super weird. So.... if you browser to http://127.0.0.1:8000/api/products it works... but if you send an AJAX request to POST http://127.0.0.1:8000/api/products it fails with "Connection refused?". There should really be no difference between the two from an infrastructure standpoint: in both cases, your browser is making a web request to the same backend. Do you get this error consistently?
Cheers!
Actually mu fault, I didn't see the mmigrations there.
I created docker-compose yaml with:
version: '3'
services:
mysql:
image: mysql:5.7
restart: always
volumes:
- ./data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: "root"
MYSQL_USER: 'root'
MYSQL_PASSWORD: 'root'
networks:
- backend
ports:
- 3306:3306
networks:
backend:
driver: bridge
Up it, made migrations and success xD
Ah sorry, misinformed a bit:
/api/products just page works. On json also connection refused:
` "@context": "/api/contexts/Error",
"@type": "hydra:Error",
"hydra:title": "An error occurred",
"hydra:description": "An exception occurred in driver: SQLSTATE[HY000] [2002] Connection refused",
"trace": [
{
"namespace": "",
"short_class": "",
"class": "",
"type": "",
"function": "",
"file": "/mnt/d/domains/vue/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php",
"line": 93,
"args": []
},`
But where it goes to connect?
Actually we don't up docker or any database services.
Ensure the database is there.
Update the .env with valid database connection information.
Afterwards load the fixtures.
php bin/console doctrine:fixtures:load
Now you can proceed (or at least I could)
// package.json
{
"devDependencies": {
"@symfony/webpack-encore": "^0.30.0", // 0.30.2
"axios": "^0.19.2", // 0.19.2
"bootstrap": "^4.4.1", // 4.5.0
"core-js": "^3.0.0", // 3.6.5
"eslint": "^6.7.2", // 6.8.0
"eslint-config-airbnb-base": "^14.0.0", // 14.1.0
"eslint-plugin-import": "^2.19.1", // 2.20.2
"eslint-plugin-vue": "^6.0.1", // 6.2.2
"regenerator-runtime": "^0.13.2", // 0.13.5
"sass": "^1.29.0", // 1.29.0
"sass-loader": "^8.0.0", // 8.0.2
"vue": "^2.6.11", // 2.6.11
"vue-loader": "^15.9.1", // 15.9.2
"vue-template-compiler": "^2.6.11", // 2.6.11
"webpack-notifier": "^1.6.0" // 1.8.0
}
}
just wondering why we
yarn add axios --dev
. I thought the --dev meant you only want it in the dev environment. I see thatyarn help add
says it "save[s] package to yourdevDependencies
" but I'm not really sure what that means in practical terms.