Top Banner
Building SPA with Symfony2 and AngularJS Antonio Perić-Mažar 02.10.2014, ZgPHP Conference 2014 https://joind.in/12007
47

Building Single Page Application (SPA) with Symfony2 and AngularJS

Jan 11, 2015

Download

Technology

Antonio Peric

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. How to connect Symfony2 as backend and AngularJS as frontend solution? 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.
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Building Single Page Application (SPA) with Symfony2 and AngularJS

Building SPA with Symfony2 and AngularJS

Antonio Perić-Mažar

02.10.2014, ZgPHP Conference 2014

https://joind.in/12007

Page 2: Building Single Page Application (SPA) with 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: Building Single Page Application (SPA) with Symfony2 and AngularJS

Who we are?

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

Page 4: Building Single Page Application (SPA) with Symfony2 and AngularJS

Our works?

Page 5: Building Single Page Application (SPA) with Symfony2 and AngularJS
Page 6: Building Single Page Application (SPA) with 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 ... ...Detailed comparison: http://vschart.com/compare/angularjs/vs/symfony

Page 7: Building Single Page Application (SPA) with 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 8: Building Single Page Application (SPA) with Symfony2 and AngularJS
Page 9: Building Single Page Application (SPA) with Symfony2 and AngularJS

UI == APP

Page 10: Building Single Page Application (SPA) with Symfony2 and AngularJS

SPA Arhitecture

Backend (rest api) with Symfony2

Frontend with AngularJs

Separation or combination?

Page 11: Building Single Page Application (SPA) with Symfony2 and AngularJS

SPA Arhitecture

Backend (rest api) with Symfony2

Frontend with AngularJs

Separation or combination?

Page 12: Building Single Page Application (SPA) with Symfony2 and AngularJS

RESTful wsSimpler than SOAP & WSDL

Resource-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 13: Building Single Page Application (SPA) with Symfony2 and AngularJS

Building Rest API with SF2

There is bundle for everything in Sf2. Right?

So lets use some of them!

Page 14: Building Single Page Application (SPA) with Symfony2 and AngularJS

Building Rest API with SF2

What we need?

JMSSerializerBundle

FOSRestBundle

NelmioApiDocBundle

Page 15: Building Single Page Application (SPA) with Symfony2 and AngularJS

Building Rest API with SF2

JMSSerializerBundle

(de)serialization

via annotations / YAML / XML / PHP

integration with the Doctrine ORM

handling of other complex cases (e.g. circular references)

Page 16: Building Single Page Application (SPA) with 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 17: Building Single Page Application (SPA) with 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 18: Building Single Page Application (SPA) with 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 19: Building Single Page Application (SPA) with Symfony2 and AngularJS

Building Rest API with SF2

Page 20: Building Single Page Application (SPA) with Symfony2 and AngularJS

Templating

TWIG <3

Page 21: Building Single Page Application (SPA) with 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 22: Building Single Page Application (SPA) with Symfony2 and AngularJS

Templating

Problem:

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

Page 23: Building Single Page Application (SPA) with Symfony2 and AngularJS

Templating{% verbatim %}

{{ message }}

{% endverbatim %}

Page 24: Building Single Page Application (SPA) with 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 25: Building Single Page Application (SPA) with Symfony2 and AngularJS

Templating

Using 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 26: Building Single Page Application (SPA) with 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 27: Building Single Page Application (SPA) with Symfony2 and AngularJS

Templating

Frontend developers still can use their tools like:

Bower Grunt Etc.

Page 28: Building Single Page Application (SPA) with Symfony2 and AngularJS

Managing routesClient side:

ngRoute

independent since Angular 1.1.6

hashbang #! & HTML5 mode

<base href="/">

$locationProvider

.html5Mode(true)

.hashPrefix('!');

Also good for SEO!

Page 29: Building Single Page Application (SPA) with Symfony2 and AngularJS

Managing routeshttp://localhost/todos

http://localhost/#todos

Resolving conflicts

Fallback, managing 404

angular:

path: '/{route}'

defaults: { _controller: LocasticAngularBundle:Default:index}

requirements:

route: ".+"

Page 30: Building Single Page Application (SPA) with Symfony2 and AngularJS

Managing routes – client 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 31: Building Single Page Application (SPA) with Symfony2 and AngularJS

Managing routesThink of using FOSJsRoutingBundle for Frontend route managament

<script src="{{ asset('bundles/fosjsrouting/js/router.js') }}"></script>

<script src="{{ path('fos_js_routing_js', {"callback": "fos.Router.setData"}) }}"></script>

my_route_to_expose_with_defaults:

pattern: /blog/{page}

defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }

options:

expose: true

It's as simple as calling:

→ Routing.generate('my_route_to_expose_with_defaults', {'page': 3})

Or if you want to generate absolute URLs:

→ Routing.generate('my_route_to_expose_with_defaults', {'page': 3}, true)

More info: https://github.com/FriendsOfSymfony/FOSJsRoutingBundle

Page 32: Building Single Page Application (SPA) with Symfony2 and AngularJS

Managing routes – server 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 33: Building Single Page Application (SPA) with Symfony2 and AngularJS

Translations

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

JMSTranslationBundle

Page 34: Building Single Page Application (SPA) with Symfony2 and AngularJS

Forms

Symfony Forms <3

We don't want to throw them away

Build custom directive

Page 35: Building Single Page Application (SPA) with 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 36: Building Single Page Application (SPA) with Symfony2 and AngularJS

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

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

Page 37: Building Single Page Application (SPA) with 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 38: Building Single Page Application (SPA) with Symfony2 and AngularJS

Forms

Suprise!!!

Page 39: Building Single Page Application (SPA) with Symfony2 and AngularJS

Forms

Template 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 40: Building Single Page Application (SPA) with Symfony2 and AngularJS

Submitting forms? When the AngularJS $http service POSTs data the header application/x-www-form-

urlencoded is never set (unlike jQuery’s $.ajax()). Also, the $http data is not serialized when sent. Both of these facts mean that the $_POST variable is never set properly by php. Without the $_POST variable Symfony’s built in form handling cannot be used.

The fix is actually pretty simple: angular needs to forced into setting a header the data needs to be serialized and the data needs to be normalized into a multidimensional array.

Page 41: Building Single Page Application (SPA) with Symfony2 and AngularJS

Submitting forms? var postData = {

formtype_name: {

id: some_id,

name: some_name

}

};

$http({

method: "POST",

url: url,

headers: {

'Content-Type': 'application/x-www-form-urlencoded'

},

data: $.param(postData)

});

Page 42: Building Single Page Application (SPA) with Symfony2 and AngularJS

Same-origin policyFor security reasons, web browsers prevent JavaScript to Ajax requests (XMLHttpRequest) to other areas

( Same-origin policy ).

An example of exception thrown by the browser:

XMLHttpRequest cannot load http://api.mondomaine.com/v1/maressource.json.

Invalid HTTP status code 405

Using JSOP (Json with Padding) – easy with FOSRestApi Configure server (simple and stupid)

<VirtualHost *:80> ServerName mon-appli-angular.com DocumentRoot /var/www/some-ng-app/ Alias /api /var/www/some-ng-app/ <Directory xxxx> </Directory> </VirtualHost> </VirtualHost>

Use CorsCORS (Cross-origin resource sharing) is an elegant and standardized response to allow Cross-domain requests.Be careful though, the CORS mechanism is not supported by all browsers (guess which ) …One can easily implement the CORS mechanism in Symfony2 with the bundle NelmioCORSBundle . This bundle will add seamlessly headers "CORS" in outgoing HTTP requests from our Symfony application.

Page 43: Building Single Page Application (SPA) with Symfony2 and AngularJS

Testing

Symfony and AngularJS are designed to test. So write test

Behat

PHPUnit

PHPSpec

Jasmine

Or whatever you want just write tests

Page 44: Building Single Page Application (SPA) with 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 45: Building Single Page Application (SPA) with Symfony2 and AngularJS

And remember

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

Page 46: Building Single Page Application (SPA) with Symfony2 and AngularJS

Thank you!

Page 47: Building Single Page Application (SPA) with Symfony2 and AngularJS

QA

Please rate my talkhttps://joind.in/12007

follow me on twitter: @antonioperic