Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Life on the edge between AngularJS and Symfony2

Life on the edge between AngularJS and Symfony2

Describes some tools for Symfony2 developers that might automate processes on Front-End side. Also solves some AngularJS and Symfony2 integration problems.

Avatar for Armen Mkrtchyan

Armen Mkrtchyan

October 31, 2014
Tweet

More Decks by Armen Mkrtchyan

Other Decks in Technology

Transcript

  1. 1. Assetic - no more* 2. Introduce Bower + Gulp

    + Browserify 3. Solve Template issues 4. Solve Routing issues 5. Forms 6. Translations * applies only to specific target audience The Plan
  2. Why (not) Assetic? • Assetic is an asset management framework

    • Inspired by webassets (Python) • 30+ built in filters
  3. Removing Assetic • Remove dependency from composer.json "symfony/assetic-bundle": "~2.3" •

    Remove Bundle from app/AppKernel.php new Symfony\Bundle\AsseticBunle\AsseticBundle(); • Remove assetic sections from config.yml and config_dev.yml • Run composer update and you’re done.
  4. Installing Bower • System wide installation npm install -g bower

    • Create .bowerrc in the root of your project { "directory": "vendors_js" } • Create bower.json in the root of your project • Execute bower install
  5. Why Gulp? • Speed (Node Streams vs File System) •

    700+ registered plugins • JS Syntax
  6. gulpfile.js // Plugins var gulp = require('gulp'), less = require('gulp-less');

    // Tasks gulp.task('stylesheets', function() { gulp.src(['path/to/main.less']) .pipe(less({ compress: true })) .on('error', function (){ console.log(); }) .pipe(gulp.dest('web/css')); }); // Watchers gulp.task('watch', function() { gulp.watch(['path/to/*.less'], ['stylesheets']); }); // Entry Point gulp.task('default', ['stylesheets', 'watch'])
  7. Modules in Web • Global Variables var MyModule = function()

    {}; • AMD define(['my-module'], function(MyModule){ return function() {}; });
  8. Modules in the Wild • CommonJS var MyModule = require('my-module');

    module.exports = function() {}; • ES6 import { MyModule } from 'my-module'; export function mymodule() {}
  9. UMD (function (root, factory) {
 if (typeof define === 'function'

    && define.amd) {
 // AMD
 define(['jquery'], factory);
 } else if (typeof exports === 'object') {
 // Node, CommonJS-like
 module.exports = factory(require('jquery'));
 } else {
 // Browser globals (root is window)
 root.returnExports = factory(root.jQuery);
 }
 }(this, function ($) {
 // methods
 function myFunc(){};
 
 // exposed public method
 return myFunc;
 }));
  10. Browserify Usage • Export // greet.js
 module.exports = function (name)

    {
 console.log('Hello ' + name);
 }; • Usage // main.js
 var greet = require('./greet.js');
 greet('World'); • Generation browserify main.js -o bundle.js
  11. Browserify + AngularJS // app.js var app = angular.module('myApp', []);

    app.factory('myService', ['$http', require('./services/myService') ]); // services/myService.js module.exports = function ($http) { //awesome service goes here }
  12. Template Conflicts • Changing {{ }} to [[ ]] in

    AngularJS var myApp = angular.module('myApp', []) .config(function($interpolateProvider){ $interpolateProvider .startSymbol('[[') .endSymbol(']]'); }); • Using {% verbatim %} in TWIG {# outputs AngularJS foo.bar expression #} {% verbatim %}{{foo.bar}}{% endverbatim %}
  13. Routing • Route Definition in Symfony2 # app/config/routing.yml foo_bar_buzz: path:

    /foo/{id} defaults: {…} • Generated Url for dev front controller /app_dev.php/foo/1243?limit=10 • Generated Url for prod front controller /foo/1243?limit=10&offset=20
  14. FOSJsRoutingBundle • Exposes routes through whitelisting • Generates JSON structure

    with exposed routes • Comes with a command to dump routes into a file
  15. Exposing Routes • routing.yml my_route_to_expose: pattern: /buzz/{id} defaults: { _controller:

    FooBundle:Bar:buzz } options: expose: true • Annotations /** * @Route("/buzz/{id}", name="foo_bar_buzz", options={"expose"=true}) */ public function buzzAction($id) • config.yml fos_js_routing: routes_to_expose: [ foo_bar_buzz, ... ]
  16. Exposing Routes • Before # app/config/config.yml fos_js_routing: routes_to_expose: [ foo_bar_buzz,

    ... ] • After # app/config/config.yml imports: - { resource: routes_exposed.yml } # app/config/routes_exposed.yml fos_js_routing: routes_to_expose: - foo_bar_buzz
  17. Dump, Gulp’em all // plugin var shell = require('gulp-shell'); //

    task gulp.task('router', shell.task( 'php app/console fos:js-routing:dump —target="./r.js"' ) ); // watcher gulp.task('watch', function() { gulp.watch('app/config/routes_exposed.yml','router'); });
  18. Default Usage • Route Definition in Symfony2 # app/config/routing.yml foo_bar_buzz:

    path: /foo/bar/{id} defaults: {…} • Route usage in JavaScript Routing.generate('foo_bar_buzz', { id: 2 });
  19. AngularJS Service • Service Definition // public/app.js smoovio.factory('$url', require('./common/service/url') );

    • Service Itself // public/common/service/url.js module.exports = function () { return { generate: function (name, parameters, isAbsolute) { return Routing.generate(name, parameters, isAbsolute); } }; }
  20. Form Itself class RegistrationType extends AbstractType
 {
 public function buildForm(FormBuilderInterface

    $builder, array $options)
 {
 $builder->add('username', 'text');
 $builder->add('email', 'email');
 $builder->add('password', 'repeated', [
 'first_name' => 'password',
 'second_name' => 'confirm',
 'type' => 'password',
 ]);
 $builder->add(
 'terms',
 'checkbox',
 ['property_path' => 'termsAccepted']
 );
 $builder->add('Sign Up!', 'submit');
 }
 
 public function getName()
 {
 return 'registration';
 }
 }
  21. Symfony2 Controller /**
 * @Template()
 */
 public function registerAction(Request $request)


    {
 $form = $this->createForm(new RegistrationType(), new Registration());
 $userManager = $this->getUserManager();
 
 $form->handleRequest($request);
 
 if ($form->isValid()) {
 $userManager->createUser($form->getData());
 return $this->redirect($this->generateUrl('portal_home'));
 
 }
 
 return ['form' => $form->createView()];
 }
  22. Rendered Form <form name="registration" method="post" action="">
 <div id="registration">
 <div>
 <label

    for="registration_username" class="required">
 Username
 </label>
 <input type="text"
 id="registration_username"
 name="registration[username]"
 required="required">
 </div>
 <!-- some output skipped -->
 <div>
 <button type="submit">Sign up!</button>
 </div>
 <input type="hidden"
 id="registration__token"
 name="registration[_token]"
 value="the_token">
 </div>
 </form>
  23. AngularJS Friendly Form <form name="registration" ng-submit="submit()" method="post" action="">
 <div id="registration">


    <div>
 <label for="registration_username" class="required">
 Username
 </label>
 <input type="text"
 ng-model="data['registration[username]']"
 id="registration_username"
 name="registration[username]"
 required="required">
 </div>
 <!-- some output skipped -->
 <div>
 <button type="submit">Sign up!</button>
 </div>
 <input type="hidden"
 ng-init="data['registration[_token]'] = 'the_token'"
 id="registration__token"
 name="registration[_token]"
 value="the_token">
 </div>
 </form>
  24. Form Themes - Start {# app/Resources/views/form_angular_layout.html.twig #}
 {% extends 'form_div_layout.html.twig'

    %} {% block form_start -%}
 {% set method = method|upper %}
 {%- if method in ["GET", "POST"] -%}
 {% set form_method = method %}
 {%- else -%}
 {% set form_method = "POST" %}
 {%- endif -%}
 <form name="{{ form.vars.name }}" ng-submit="submit()" {# rest #}
 {%- if form_method != method -%}
 <input type="hidden" name="_method" value="{{ method }}" />
 {%- endif -%}
 {%- endblock form_start %}
  25. Form Themes - Attributes {# app/Resources/views/form_angular_layout.html.twig #}
 {% extends 'form_div_layout.html.twig'

    %} {% block form_start -%}
 ... {%- endblock form_start %} {% block widget_attributes -%}
 {% if "hidden" in form.vars.block_prefixes %}
 ng-init="data['{{ full_name }}'] = '{{value}}'"
 {% else %}
 ng-model="data['{{ full_name }}']"
 {% endif %}
 id="{{ id }}" name="{{ full_name }}" {# rest #} {%- endblock widget_attributes %}

  26. Rendering - AngularJS Way {% form_theme form "form_angular_layout.html.twig" %} {%

    block content %}
 {{ form(form) }}
 {% endblock %}
  27. Form Controller app.controller('myFormRenderCtrl', ['$scope', '$url', '$http', '$compile',
 function ($scope, $url,

    $http, $compile) { 
 $scope.formUrl = $url.generate('my_form_route_name');
 
 $scope.data = {};
 
 $scope.submit = function () {
 $http.post($scope.formUrl, $scope.data, {
 headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
 transformRequest: function (data) {
 return $.param(data);
 }
 }).success(
 function (response) {
 $('#formContainer').html($compile(response)($scope));
 }
 );
 }
 }]);
  28. Features • Fallback language support • JSON format support •

    Custom Loaders • Loader Cache • Highly customisable
  29. Config app.config(['$translateProvider', function ($translateProvider) { $translateProvider
 .preferredLanguage('en')
 .fallbackLanguage('en'); $translateProvider.useLoaderCache(true); $translateProvider.useStaticFilesLoader({


    prefix: '/translations/locale-',
 suffix: '.json'
 }); $translateProvider.storageKey('locale');
 $translateProvider.storagePrefix('app_');
 $translateProvider.useStorage('customStorage'); }])
  30. Copying the translations gulp.task('translations', function () {
 gulp.src([
 'path_to_bundle/Resources/translations/**'
 ])


    .pipe(gulp.dest('web/translations/'));
 }); gulp.watch([
 'path_to_bundle/Resources/translations/**'
 ], ['templates']); gulp.task('default', [..., 'translations'])
  31. Custom Service app.factory('customStorage', function ($window) {
 return {
 set: function

    (name, value) {
 },
 get: function (name) {
 return $window.appSettings[name];
 }
 };
 }); 
 <script>var appSettings = {"app_locale": "de"}</script>