/ Vue.js

Directly injecting data to Vue apps with Symfony/Twig

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:

vuenameapp-1

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.

Kevin

Kevin

I make stuff. Mostly functional, occasionally shiny, stuff.

Read More