Symfony 2 and AngularJS: Server-side rendering of form data

Now that Javascript frameworks are starting to mature and be used by a lot more companies than a few years prior, the question often comes up: How can we use our current server side form rendering while at the same time populate our client side framework with that initial data?

Well, let’s first talk about the pros and cons and then I’ll give a brief example on how you can easily do this with Symfony 2 / Twig and AngularJS.

There’s basically two schools of thought. The first is the camp that believes the client side framework should do all rendering / populating of form data. This means that when a user loads your page, the client does an AJAX call to populate the initial data after the UI has already loaded. This school of thought definitely has some merit, especially from a business point of view because more times than not this makes the user experience faster. However, there’s also a downside with slow data loads when users load your page with low end systems / mobile devices and are waiting around with a blank application, no content, and a confused look on their face. This could (and many times does) violate the cardinal rule of don’t annoy your customers. User annoyance is a for sure way to lose a sale. There is also the issue of not being able to take full advantage of server side optimizations.

The second is the camp (which… *cough* *cough* I happen to belong to) is letting your initial data load with the server side rendering, and then initializing the client side framework with it. When your page loads, your users aren’t sitting around with a WTF feeling, because if your page is loaded, your data is there. The reason I say this is because we’re at a point with the internet that your average user understands a page load vs a non page load, but they definitely don’t have any concept of well I know the app loaded just fine, I’ve just gotta wait around a few seconds until the JSON response returns. Even when the content shows up, your customer more often than not thinks the application is some how broken, loses trust, and moves on. And this is definitely what you don’t want.

The downside to this approach is unfortunately many popular Javascript frameworks including AngularJS (as of this post) view this as going against best practices. My view and others I work with view server side rendering of initial data and seeding the client side framework with it as the best of both worlds. In fact, Twitter has even changed their opinion when it comes to this as well.

OK, enough with the rambling… Let’s see some code! 😀


NOTE:

This article will assume you already have an understanding of Symfony 2 / Twig, how to override in Twig, and at least a basic understanding of what AngularJS directives are and how they work.

On our page load, we pass our form field data to AngularJS via assigning an AngularJS directive as an attribute and let AngularJS repopulate our fields. We add our init directive to the form by overriding our block’s field widget. We don’t add it via attribute in our Form class before rendering, why is beyond the scope of this article. Adding the directive will give control of our rendered form data to AngularJS and then we can continue to work with it in AngularJS as we normally would.

First create a twig file to override and add


{# Content\ProfileBundle\Resources\views\Form\fields_angularjs_init.html.twig #}

{# override the forms media widget #}
{% block _content_profile_userGallery_media_widget %}
{% spaceless %}
    {% set attr = {'data-ng-model': 'userGallery.media', 'data-initialize-directive': ''} %}
    {{ block('form_widget') }}
{% endspaceless %}
{% endblock _content_profile_userGallery_media_widget %}


Next create an AngularJS directive and add


// Content\ProfileBundle\Resources\public\js\directives.js

// change to the way your app loads directives
profileAppDirectives.directive('initializeDirective', function($parse) {
    return {
        restrict: 'A',
        require: '?ngModel',
        scope: false, // change for your scope (you need to be in the correct scope)
    
        // ...

        // the linking function will assign to your init directive 
        // as an attribute in fields_angularjs_init.html.twig
        link: function(scope, element, attrs) {
            // ...

            $parse(attrs.ngModel).assign(scope, attrs.value);
    
            // ...
        }

        // ...
    }
}

You can also add attrs.value, attrs.checked, etc. and checking conditions for the different form fields you’re dealing with.

That’s it, have fun! 😀