Symfony2 and AngularJS

48
Symfony2 and AngularJS Antonio Perić-Mažar 16.05.2014, PHPday Verona https://joind.in/talk/view/11290

description

These are my slide from talk at PHPDay in Verona 20014. Forget about classic website where UX is not so important. We are living in time where usability is one of the important thing if you are building some business client oriented web service. In locastic we are working on CRM that is based on Symfony2 as backend and AngularJS as frontend solution. How to connect this two frameworks? What are best practices? What are disadvantageous? How to take best from both worlds? These are topics I will cover in my talk with real examples.

Transcript of Symfony2 and AngularJS

Page 1: Symfony2 and AngularJS

Symfony2 and AngularJS

Antonio Perić-Mažar

16.05.2014, PHPday Verona

https://joind.in/talk/view/11290

Page 2: Symfony2 and AngularJS

About me• Antonio Perić-Mažar,

mag. ing. comp.• CEO @ locastic• Software developer, Symfony2• Sylius Awesome Contributor :)

• www.locastic.com• [email protected]• twitter: @antonioperic

Page 3: Symfony2 and AngularJS

Who we are?• locastic (www.locastic.com)• Web and mobile development• UI/UX design• Located in Split, Croatia

Page 4: Symfony2 and AngularJS

Our works?

Page 5: Symfony2 and AngularJS
Page 6: Symfony2 and AngularJS

Symfony2 PHP Framework, server side MVC, HTTP Toolbox, methodology One of the most advanced PHP framework Strong community Easy to test it

Page 7: Symfony2 and AngularJS

Symfony2 Bundles Components Routing Templating (Twig) Forms etc

Page 8: Symfony2 and AngularJS

AngularJS Client-side JavaScript framework Prescriptive, MVC (MVVM) Makes creating UI easier thought data-binding Good organizing and architecture Learning curve, easy to start hard to master $scope, modules, controllers, providers, services

Page 9: Symfony2 and AngularJS

AngularJS CoreTwo-way data binding

JS:<span id=”someId”></span>document.getElementById('someId').text = 'locastic';

NG<span>{{someName}}<span>

var someName = 'locastic';

Page 10: Symfony2 and AngularJS

AngularJS Core

<html ng-app>

<head>

<script src=”angular.js”></script>

<script src=”controller.js”></script>

<head>

<body>

<div ng-controller=”HelloController”>

<p>{{ greeting.text}}, World</p>

</div>

</body>

</html>

// controller.js

function HelloController($scope) {

$scope.gretting = {text: 'Hello'}

}

HTML template

Page 11: Symfony2 and AngularJS

AngularJS Core Deep Linking Dependency Injection Directives

<div class=”container”>

<div class=”inner>

<ul>

<li>Item

<div class=”subitem”>Item2</div>

</li>

</ul>

</div>

</div>

<dropdown>

<item>Item 1>

<subitem >Item 2</subitem>

</item>

</dropdown>

Testable

Page 12: Symfony2 and AngularJS

+

SPA (Singe Page App)

Page 13: Symfony2 and AngularJS

SPA Aka SPI (Single Page interface) desktop apps UX HTML / JS / CSS / etc in single page load fast AJAX and XHR

Page 14: Symfony2 and AngularJS
Page 15: Symfony2 and AngularJS

SPA Arhitecture

Backend (rest api) with Symfony2Frontend with AngularJs

Separation or combination?

Page 16: Symfony2 and AngularJS

UI == APP

Page 17: Symfony2 and AngularJS

Symfony2 AngularJS

Usage, language Backend, PHP Frontend, Javascript

Dependency Injection Yes Yes

Templating Twig HTML

Form component Yes Yes

Routing component Yes Yes

MVC Yes Yes

Testable Yes Yes

Services Yes Yes

Events Yes Yes

i18n Yes Yes

Dependency management Yes Yes

etc ... ...

Page 18: Symfony2 and AngularJS

RESTful wsSimpler than SOAP & WSDLResource-oriented (URI)

Principles:

HTTP methods (idempotent & not) stateless directory structure-like URIs XML or JSON (or XHTML)

GET (vs HEAD), POST, PUT (vs PATCH), DELETE, OPTIONS

Page 19: Symfony2 and AngularJS

Building Rest Api with SF2

There is bundle for everything in Sf2. Right?So lets use some of them!

Page 20: Symfony2 and AngularJS

Building Rest Api with SF2What we need?JMSSerializerBundle FOSRestBundleNelmioApiDocBundle

Page 21: Symfony2 and AngularJS

Building Rest API with SF2JMSSerializerBundle (de)serialization via annotations / YAML / XML / PHP integration with the Doctrine ORM handling of other complex cases (e.g. circular references)

Page 22: Symfony2 and AngularJS

Building Rest Api with SF2Locastic\Bundle\TodoBundle\Entity\Todo:

# exclusion_policy: ALL exclusion_policy: NONE properties:# description:# expose: true createdAt:# expose: true exclude: true deadline: type: DateTime<'d.m.Y. H:i:s'># expose: true done:# expose: true serialized_name: status

Page 23: Symfony2 and AngularJS

Building Rest Api with SF2fos_rest:

disable_csrf_role: ROLE_API

param_fetcher_listener: true

view:

view_response_listener: 'force'

formats:

xml: true

json: true

templating_formats:

html: true

format_listener:

rules:

- { path: ^/, priorities: [ html, json, xml ], fallback_format: ~, prefer_extension: true }

exception:

codes:

'Symfony\Component\Routing\Exception\ResourceNotFoundException': 404

'Doctrine\ORM\OptimisticLockException': HTTP_CONFLICT

messages:

'Symfony\Component\Routing\Exception\ResourceNotFoundException': true

allowed_methods_listener: true

access_denied_listener:

json: true

body_listener: true

Page 24: Symfony2 and AngularJS

Building Rest Api with SF2/**

* @ApiDoc( * resource = true, * description = "Get stories from users that you follow (newsfeed)", * section = "Feed", * output={ * "class" = "Locastic\Bundle\FeedBundle\Entity\Story" * }, * statusCodes = { * 200 = "Returned when successful", * 400 = "Returned when bad parameters given" * } * ) * * @Rest\View( * serializerGroups = {"feed"} * ) */public function getFeedAction(){ $this->get('locastic_auth.auth.handler')->validateRequest($this->get('request'));

return $this->getDoctrine()->getRepository('locastic.repository.story')->getStories($this->get('request')->get('me'));}

Page 25: Symfony2 and AngularJS

Building Rest Api with SF2

Page 26: Symfony2 and AngularJS

Templating

TWIG <3

Page 27: Symfony2 and AngularJS

Templating<!DOCTYPE html>

<html> <head> <meta charset="UTF-8" /> <title>{% block title %}Welcome!{% endblock %}</title> {% block stylesheets %} <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->

{#<link rel="stylesheet" href="{{ asset('css/normalize.css') }}">#} <link rel="stylesheet" href="{{ asset('css/main.css') }}">

<!-- load bootstrap and fontawesome via CDN --> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" /> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.0/css/font-awesome.css" />

<script src="{{ asset('js/vendor/modernizr-2.6.2.min.js') }}"></script> {% endblock %} <link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" /> </head> <body> {% block body %}{% endblock %} {% block javascripts %}

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script> <script src="https://code.angularjs.org/1.2.16/angular-route.min.js"></script> <script src="{{ asset('js/main.js') }}"></script>

{% endblock %} </body></html>

Page 28: Symfony2 and AngularJS

TemplatingProblem:{{ interpolation tags }} - used both by twig and AngularJS

Page 29: Symfony2 and AngularJS

Templating{% verbatim %} {{ message }}{% endverbatim %}

Page 30: Symfony2 and AngularJS

Templating

var phpDayDemoApp = angular.module('phpDayDemoApp', [],

function($interpolateProvider) {

$interpolateProvider.startSymbol('[['); $interpolateProvider.endSymbol(']]');});

Now we can use{% block content %}

[[ message ]] {# rendered by AngularJS #}

{% end block %}

Tweak Twig lexer delimiters? Bad idea.

Page 31: Symfony2 and AngularJS

TemplatingUsing assetic for minimize

{% javascripts

"js/angular-modules/mod1.js" "#s/angular-modules/mod2.js" "@AngBundle/Resources/public/js/controller/*.js"

output="compiled/js/app.js"

%}

<script type="text/javascript" src="{{ asset_url }}"></script>

{% endjavascripts %}

Page 32: Symfony2 and AngularJS

TemplatingUsing assetic for minimize

Since Angular infers the controller's dependencies from the names of arguments to the controller's constructor function, if you were to minify the JavaScript code for PhoneListCtrl controller, all of its function arguments would be minified as well, and the dependency injector would not be able to identify services correctly.

Use an inline annotation where, instead of just providing the function, you provide an array. This array contains a list of the service names, followed by the function itself.

function PhoneListCtrl($scope, $http) {...}phonecatApp.controller('phpDayCtrl', ['$scope', '$http', PhoneListCtrl]);

Page 33: Symfony2 and AngularJS

Managing routesClient side:

ngRoute

independent since Angular 1.1.6

hashbang #! & HTML5 mode

<base href="/">

$locationProvider

.html5Mode(true)

.hashPrefix('!');

Page 34: Symfony2 and AngularJS

Managing routeshttp://localhost/todoshttp://localhost/#todos

Resolving conflictsFallback, managing 404

Page 35: Symfony2 and AngularJS

Managing routesClient side:

// module configuration...$routeProvider.when('/todos/show/:id', { templateUrl : 'todo/show', controller : 'todoController'})

// receive paramssfugDemoApp.controller('todoController', function($scope, $http, $routeParams){

$scope.todo = {};

$http .get('/api/todo/show/' + $routeParams.id) .success(function(data){ $scope.todo = data['todo']; });

});

Page 36: Symfony2 and AngularJS

Managing routesServer side: locastic_rest_todo_getall:

pattern: /api/get-all defaults: _controller: LocasticRestBundle:Todo:getAll

locastic_rest_todo_create: pattern: /api/create defaults: _controller: LocasticRestBundle:Todo:create

locastic_rest_todo_show: pattern: /api/show/{id} defaults: _controller: LocasticRestBundle:Todo:show

Page 37: Symfony2 and AngularJS

TranslationsAngularJS has its own translation system I18N/L10N . But it might be interesting to monitor and centralize translations from your backend Symfony.

Page 38: Symfony2 and AngularJS

FormsSymfony Forms <3We don't want to throw them awayBuild custom directive

Page 39: Symfony2 and AngularJS

FormssfugDemoApp.directive('ngToDoForm', function() {

return { restrict: 'E', template: '<div class="todoForm">Form will be!</div>' }});

'A' - <span ng-sparkline></span>'E' - <ng-sparkline></ng-sparkline>'C' - <span class="ng-sparkline"></span>'M' - <!-- directive: ng-sparkline →

Usage of directive in HTML:<ng-to-do-form></ng-to-do-form>

Page 40: Symfony2 and AngularJS

FormssfugDemoApp.directive('ngToDoForm', function() {

return { restrict: 'E', templateUrl: '/api/form/show.html' }});

Page 41: Symfony2 and AngularJS

FormssfugDemoApp.directive('ngToDoForm', function() {

return { restrict: 'E', templateUrl: '/api/form/show.html' }});

locastic_show_form: pattern: /form/show.html defaults: _controller: LocasticWebBundle:Default:renderForm

public function renderFormAction(){ $form = $this->createForm(new TodoType());

return $this->render('LocasticWebBundle::render_form.html.twig', array( 'form' => $form->createView() ));}

Page 42: Symfony2 and AngularJS

FormsSuprise!!!

Page 43: Symfony2 and AngularJS

FormTemplate behind directive

<form class="form-inline" role="form" style="margin-bottom: 30px;"> Create new todo: <div class="form-group"> {{ form_label(form.description) }} {{ form_widget(form.description, {'attr': {'ng-model': 'newTodo.description', 'placeholder': 'description', 'class': 'form-control'}}) }} </div> <div class="form-group"> <label class="sr-only" for="deadline">Deadline</label> <input type="text" class="form-control" id="deadline" placeholder="deadline (angular-ui)" ng-model="newTodo.deadline"> </div> <input type="button" class="btn btn-default" ng-click="addNew()" value="add"/></form>

Page 44: Symfony2 and AngularJS

TestingSymfony and AngularJS are designed to test. So write test

BehatPHPUnitPHPSpecJasmine

…Or whatever you want just write tests

Page 45: Symfony2 and AngularJS

SummaryThe cleanest way is to separate backend and frontend. But there is some advantages to use both together.

Twig + HTML works well.

Assetic Bundle is very useful to minify bunches of Javascript files used by AngularJs

Translation in the template. the data in the API payload does not need translation in most cases. Using Symfony I18N support for the template makes perfect sense.

Loading of Option lists. Say you have a country list with 200+ options. You can build an API to populate a dynamic dropdown in angularjs, but as these options are static, you can simply build that list in twig. Forms in Symfony are pretty cool

Take advantage of the authentication feature of Symfony. You can used the same authenticated session to the API for the application, too. No need to use Oauth.

Page 46: Symfony2 and AngularJS

And remember

Keep controllers small and stupid, master Dependency injection, delegate to services and events.

Page 47: Symfony2 and AngularJS

Thank you!

Page 48: Symfony2 and AngularJS

QA

Please rate my talkhttps://joind.in/talk/view/11290

follow me on twitter: @antonioperic