Slide 1

Slide 1 text

Symfony2 and AngularJS SensioLabs

Slide 2

Slide 2 text

Introduction

Slide 3

Slide 3 text

Armen Mkrtchyan @iamtankist ├─ #Symfony2 ├─ #AngularJS ├─ #WebDev └─ #DevOps

Slide 4

Slide 4 text

Why AngularJS • Bidirectional Data-Binding • Directives • Dependency Injection • Enables TDD

Slide 5

Slide 5 text

Why Symfony2 • Components • Twig, Doctrine, Monolog integration • Dependency Injection • Enables TDD

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Why (not) Assetic? • Assetic is an asset management framework • Inspired by webassets (Python) • 30+ built in filters

Slide 8

Slide 8 text

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.

Slide 9

Slide 9 text

Bower

Slide 10

Slide 10 text

Why Bower • Package manager • Powerful Console tool • De-facto industry standard

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

bower.json { "name": "smoov.io", "version": "0.0.1", "dependencies": { "jquery": "~2.1.1", "bootstrap": "~3.1.1", "angular": "~1.2.16" } }

Slide 13

Slide 13 text

Gulp

Slide 14

Slide 14 text

Why Gulp? • Speed (Node Streams vs File System) • 700+ registered plugins • JS Syntax

Slide 15

Slide 15 text

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'])

Slide 16

Slide 16 text

Browserify

Slide 17

Slide 17 text

Modules in Web • Global Variables var MyModule = function() {}; • AMD define(['my-module'], function(MyModule){ return function() {}; });

Slide 18

Slide 18 text

Modules in the Wild • CommonJS var MyModule = require('my-module'); module.exports = function() {}; • ES6 import { MyModule } from 'my-module'; export function mymodule() {}

Slide 19

Slide 19 text

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;
 }));

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Browserify + Gulp gulp.task('scripts', function() { gulp.src('main.js') .pipe(browserify()) .pipe(concat('bundle.js')) .pipe(gulp.dest('web/js')); });

Slide 22

Slide 22 text

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 }

Slide 23

Slide 23 text

Template Conflicts

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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 %}

Slide 26

Slide 26 text

Route Generation

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

FOSJsRoutingBundle • Exposes routes through whitelisting • Generates JSON structure with exposed routes • Comes with a command to dump routes into a file

Slide 29

Slide 29 text

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, ... ]

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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'); });

Slide 32

Slide 32 text

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 });

Slide 33

Slide 33 text

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); } }; }

Slide 34

Slide 34 text

AngularJS Usage • Inside some service // fooRepository.js return $http.get( $url.generate('foo_bar_buzz', {'id': id}); );

Slide 35

Slide 35 text

Forms

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

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';
 }
 }

Slide 38

Slide 38 text

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()];
 }

Slide 39

Slide 39 text

Rendering The Form {% block content %}
 {{ form(form) }}
 {% endblock %}

Slide 40

Slide 40 text

Rendered Form 


 
 Username
 
 


 


 Sign up!


 


Slide 41

Slide 41 text

AngularJS Friendly Form 


 
 Username
 
 


 


 Sign up!


 


Slide 42

Slide 42 text

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 -%}
 
 {%- endif -%}
 {%- endblock form_start %}

Slide 43

Slide 43 text

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 %}


Slide 44

Slide 44 text

Rendering - AngularJS Way {% form_theme form "form_angular_layout.html.twig" %} {% block content %}
 {{ form(form) }}
 {% endblock %}

Slide 45

Slide 45 text

Including Form

Slide 46

Slide 46 text

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));
 }
 );
 }
 }]);

Slide 47

Slide 47 text

i18n and l10n

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

angular-translate • http://angular-translate.github.io/

Slide 50

Slide 50 text

Features • Fallback language support • JSON format support • Custom Loaders • Loader Cache • Highly customisable

Slide 51

Slide 51 text

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'); }])

Slide 52

Slide 52 text

Config app. $translateProvider
 .preferredLanguage('en')
 .fallbackLanguage('en'); $translateProvider.useStaticFilesLoader({ }])

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

/translations/locale-de.json {
 APP_HEADLINE: 'Großartige App',
 NAV_HOME: 'Zur Startseite',
 NAV_ABOUT: 'Über',
 APP_TEXT: 'Irgendein Text'
 }

Slide 55

Slide 55 text

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'])

Slide 56

Slide 56 text

Config app. . . $translateProvider.useStaticFilesLoader({ $translateProvider.storagePrefix('app_'); $translateProvider.storageKey('locale');
 $translateProvider.useStorage('customStorage'); }])

Slide 57

Slide 57 text

Custom Service app.factory('customStorage', function ($window) {
 return {
 set: function (name, value) {
 },
 get: function (name) {
 return $window.appSettings[name];
 }
 };
 }); 
 var appSettings = {"app_locale": "de"}

Slide 58

Slide 58 text

Q & A SensioLabs

Slide 59

Slide 59 text

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