Usually data for Vue apps come from APIs, but ocassionally it may happen that you're rendering a Twig template with all the data you need and which you want to pass to your Vue app - without implementing an additional API.
Use case
Think of a controller to edit a user, where the actual form is a standalone Vue app:
namespace App\Controller;
class EditRulesController
{
public function indexAction(User $user)
{
// additional and mighty code
return $this->render('user/edit.html.twig', [
'user' => $user
]);
}
}
In addition your twig template (user/edit.html.twig
):
{% block body %}
<h1>Editing user {{user.name}}</h1>
<div id="user-edit-app"></div>
{% endblock }
To make your form reactive instead of just a static standard form you may use a Vue app:
new Vue({
el: '#user-edit-app',
data: {
username: ''
},
template: '<div class="user-edit-form">' +
'<input type="text" v-model="username">' +
'<span style="display: block;">How cool is your name: {{coolness}}</span>' +
'<input type="submit" v-on:click="send">' +
'</div>',
computed: {
coolness: function() {
switch(this.username.toLowerCase()) {
case 'nehalist':
case 'kevin':
return 'Pretty cool';
case 'bob':
return 'Awesome!';
case 'ajit pai':
return 'Terrible';
default:
return 'Not cool';
}
}
},
methods: {
send: function() {
// send your form ...
}
}
});
Probably the mightiest thing I've ever implemented.
After your app is rendered you'll see something like this:
The problem now: since you've already rendered your twig template you do already know the name
of the user you're editing. But how to get the name (or even the entire User
object) into your vue app and your form? Glady that's pretty easy.
Utilising beforeMount
and data
attributes to inject data
First let's inject the data to our app container (in the twig template):
{% block body %}
<h1>Editing user {{user.name}}</h1>
<div id="user-edit-app" data-name="{{ user.name }}"></div>
{% endblock }
To get access to our data within our view app we're going to utilise Vue's beforeMount
method:
new Vue({
el: '#user-edit-app',
data: {
username: ''
},
// [...] `template` & `computed` truncated
beforeMount: function() {
this.username = this.$el.attributes['data-name'].value;
}
});
That's it. Now our app renders with the username input already being filled with the proper name.
Note: This method does not work with server-side rendering, since the beforeMount
method is never called.
Getting the entire user object
In case you're wanting the entire user object within your Vue app you need to serialize your user object and parse it within your Vue app.
You're either using the Serializer component, or implement a very simple toArray
method within your entity. For the sake of simplicity we're going for latter in this article, but in the real world I'd suggest using serializers for this:
// src/Entity/User.php
class User
{
protected $name;
// [...] truncated setters, ...
public function getName()
{
return $this->name;
}
public function toArray()
{
return [
'name' => $this->getName()
];
}
}
Your twig template now might look like this:
{% block body %}
<h1>Editing user {{user.name}}</h1>
<div id="user-edit-app" data-user="{{ user.toArray|json_encode }}"></div>
{% endblock }
The json_encode
is a Twig function to return a value as JSON.
All you need to now is to slightly change our Vue apps beforeMount
method:
new Vue({
el: '#user-edit-app',
data: {
user: null,
username: ''
},
// [...] `template` & `computed` truncated
beforeMount: function() {
this.user = JSON.parse(this.$el.attributes['data-user'].value);
this.username = this.user.name;
}
});
It might be a good idea to validate the data-
attributes before assigning it to your Vue instance, but basically this is enough to inject data without needing an additional API.