Professional Documents
Culture Documents
Building Spa With Symfony2 and Angularjs: Antonio Perić-Mažar
Building Spa With Symfony2 and Angularjs: Antonio Perić-Mažar
AngularJS
Antonio Perić-Mažar
• www.locastic.com
• antonio@locastic.com
• twitter: @antonioperic
Who we are?
• locastic (www.locastic.com)
• Web and mobile development
• UI/UX design
• Located in Split, Croatia
Our works?
Symfony2 AngularJS
Detailed
etc comparison: http://vschart.com/compare/angularjs/vs/symfony
... ...
SPA
Aka SPI (Single Page interface)
desktop apps UX
HTML / JS / CSS / etc in single page load
fast
AJAX and XHR
UI == APP
SPA Arhitecture
Principles:
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:
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
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'));
}
Building Rest API with SF2
Templating
TWIG <3
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 -->
<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>
Templating
Problem:
{{ interpolation tags }} - used both by twig and AngularJS
Templating
{% verbatim %}
{{ message }}
{% endverbatim %}
Templating
"js/angular-modules/mod1.js"
"#s/angular-modules/mod2.js"
"@AngBundle/Resources/public/js/controller/*.js"
output="compiled/js/app.js"
%}
{% endjavascripts %}
Templating
Using assetic for minimize
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.
Bower
Grunt
Etc.
Managing routes
Client side:
ngRoute
independent since Angular 1.1.6
<base href="/">
$locationProvider
.html5Mode(true)
.hashPrefix('!');
Resolving conflicts
Fallback, managing 404
angular:
path: '/{route}'
defaults: { _controller: LocasticAngularBundle:Default:index}
requirements:
route: ".+"
Managing routes – client side
// module configuration...$routeProvider.when('/todos/show/:id', {
templateUrl : 'todo/show',
controller : 'todoController'
})
$scope.todo = {};
$http
.get('/api/todo/show/' + $routeParams.id)
.success(function(data){
$scope.todo = data['todo'];
});
});
Managing routes
Think 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
}
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
Translations
AngularJS has its own translation system
I18N/L10N . But it might be interesting to monitor
and centralize translations from your backend
Symfony.
JMSTranslationBundle
Forms
Symfony Forms <3
We don't want to throw them away
Build custom directive
Forms
sfugDemoApp.directive('ngToDoForm', function() {
return {
restrict: 'E',
template: '<div class="todoForm">Form will be!</div>'
}
});
locastic_show_form:
pattern: /form/show.html
defaults:
_controller: LocasticWebBundle:Default:renderForm
$http({
method: "POST",
url: url,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: $.param(postData)
Same-origin policy
For security reasons, web browsers prevent JavaScript to Ajax requests (XMLHttpRequest)
to other areas ( Same-origin policy ).
An example of exception thrown by the browser:
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 Cors
CORS (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 ) …
Testing
Symfony and AngularJS are designed to test. So write test
Behat
PHPUnit
PHPSpec
Jasmine
…
Or whatever you want just write tests
Summary
The cleanest way is to separate backend and frontend. But there is some
advantages to use both together.
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
And remember