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.

C20032be8b7b4c5afb76d931878770fe?s=128

Armen Mkrtchyan

October 31, 2014
Tweet

Transcript

  1. Symfony2 and AngularJS SensioLabs

  2. Introduction

  3. Armen Mkrtchyan @iamtankist ├─ #Symfony2 ├─ #AngularJS ├─ #WebDev └─

    #DevOps
  4. Why AngularJS • Bidirectional Data-Binding • Directives • Dependency Injection

    • Enables TDD
  5. Why Symfony2 • Components • Twig, Doctrine, Monolog integration •

    Dependency Injection • Enables TDD
  6. 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
  7. Why (not) Assetic? • Assetic is an asset management framework

    • Inspired by webassets (Python) • 30+ built in filters
  8. 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.
  9. Bower

  10. Why Bower • Package manager • Powerful Console tool •

    De-facto industry standard
  11. 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
  12. bower.json { "name": "smoov.io", "version": "0.0.1", "dependencies": { "jquery": "~2.1.1",

    "bootstrap": "~3.1.1", "angular": "~1.2.16" } }
  13. Gulp

  14. Why Gulp? • Speed (Node Streams vs File System) •

    700+ registered plugins • JS Syntax
  15. 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'])
  16. Browserify

  17. Modules in Web • Global Variables var MyModule = function()

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

    module.exports = function() {}; • ES6 import { MyModule } from 'my-module'; export function mymodule() {}
  19. 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;
 }));
  20. 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
  21. Browserify + Gulp gulp.task('scripts', function() { gulp.src('main.js') .pipe(browserify()) .pipe(concat('bundle.js')) .pipe(gulp.dest('web/js'));

    });
  22. 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 }
  23. Template Conflicts

  24. Template Conflicts AngularJS {{ object.property }} TWIG {{ object.property }}

  25. 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 %}
  26. Route Generation

  27. 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
  28. FOSJsRoutingBundle • Exposes routes through whitelisting • Generates JSON structure

    with exposed routes • Comes with a command to dump routes into a file
  29. 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, ... ]
  30. 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
  31. 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'); });
  32. 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 });
  33. 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); } }; }
  34. AngularJS Usage • Inside some service // fooRepository.js return $http.get(

    $url.generate('foo_bar_buzz', {'id': id}); );
  35. Forms

  36. None
  37. 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';
 }
 }
  38. 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()];
 }
  39. Rendering The Form {% block content %}
 {{ form(form) }}


    {% endblock %}
  40. 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>
  41. 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>
  42. 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 %}
  43. 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 %}

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

    block content %}
 {{ form(form) }}
 {% endblock %}
  45. Including Form <div
 id="formContainer"
 ng-controller="myFormRenderCtrl" ng-include="formUrl"
 ></div>

  46. 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));
 }
 );
 }
 }]);
  47. i18n and l10n

  48. None
  49. angular-translate • http://angular-translate.github.io/

  50. Features • Fallback language support • JSON format support •

    Custom Loaders • Loader Cache • Highly customisable
  51. 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'); }])
  52. Config app. $translateProvider
 .preferredLanguage('en')
 .fallbackLanguage('en'); $translateProvider.useStaticFilesLoader({ }])

  53. Config app. . . $translateProvider.useLoaderCache(true); $translateProvider.useStaticFilesLoader({
 prefix: '/translations/locale-',
 suffix: '.json'


    }); }])
  54. /translations/locale-de.json {
 APP_HEADLINE: 'Großartige App',
 NAV_HOME: 'Zur Startseite',
 NAV_ABOUT: 'Über',


    APP_TEXT: 'Irgendein Text'
 }
  55. 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'])
  56. Config app. . . $translateProvider.useStaticFilesLoader({ $translateProvider.storagePrefix('app_'); $translateProvider.storageKey('locale');
 $translateProvider.useStorage('customStorage'); }])

  57. 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>
  58. Q & A SensioLabs

  59. Thanks! http://tinyurl.com/angular-symfony SensioLabs