front-end developer
ou
Présentation des concepts clé d'AngularJS ...
... et 14 exercices pour les mettre en pratique
14 exos = 14 branches git
git checkout -f step-#
git checkout -f step-0
Charger angularjs dans votre page ...
<html>
<head>
<script src="path/to/angular.js"></script>
</head>
<body>
</body>
</html>
... le rendre actif sur celle-ci ...
<html ng-app>
<head>
<script src="path/to/angular.js"></script>
</head>
<body>
</body>
</html>
... et vérifier que ca marche
<html ng-app>
<head>
<script src="path/to/angular.js"></script>
</head>
<body>
{{ 1 + 1 }}
</body>
</html>
Bonjour {{greeting}}
<html>
Bonjour <span id="name"></span>
<input type="text"/>
</html>
window.onload = function(){
var span = document.getElementById('name');
var input = document.getElementsByTagName('input')[0];
input.onkeyup = function(){
if (span.textContent || span.textContent === "") {
span.textContent = input.value;
} else if(span.innerText || span.innerText === "") { // IE
span.innerText = input.value;
}
};
};
<html>
Bonjour <span id="name"></span>
<input type="text"/>
</html>
$(document).ready(function() {
var $input = $('input');
var $span = $('#name');
$input.keyup(function (event) {
$span.text(event.target.value);
});
});
<html ng-app>
<div>
<input type="text" ng-model="name">
<p>Bonjour {{name}}</p>
</div>
</html>
Changer dynamiquement le nom par le contenu d'un champ de saisie
index.html
js/lib
{{name}}
{{name}}
<html ng-app>
<div ng-controller="myController">
<input type="text" ng-model="name">
<p>Hello {{name}}</p>
</div>
</html>
function myController($scope){
$scope.name = 'World';
}
Gardez les mêmes variables {{name}}
mais leur affecter des valeurs différentes
app/js/app.js
et y écrire vos 2 controllers
<html ng-app="myApp">
<div ng-controller="myController">
<input type="text" ng-model="greeting">
<p>{{greeting}}</p>
</div>
</html>
var myApp = angular.module('myApp', []);
myApp.controller('myController', function($scope){
$scope.greeting = 'Hello World';
});
git checkout -f step-1
Itère dans une collection et génére un template par élément
<ul>
<li ng-repeat="fruit in fruits">
/* répétition du template li par élément fruit */
{{ fruit.name }}
</li>
</ul>
Afficher la liste des films en utilisant la directive ng-repeat
movie-list.html
moviesController
pour la liste des films
[
{
"id":1,
"title":"Avatar",
"releaseYear":"2010",
"poster":"img/avatar.jpg",
"directors":"James Cameron",
"actors":"Sam Worthington, Zoe Saldana, Sigourney Weaver, Stephen Lang, Michelle Rodriguez",
"synopsis":"Sur la lointaine planète de Pandora, Jake Sully, un héros malgré lui, se lance dans une quête de rédemption, de découverte, d'amour inattendu, dont l'issue sera un combat héroïque pour sauver toute une civilisation.",
"rate":"3"
},
{
"id":2,
"title":"Seigneur des Anneaux : La Communauté de l'Anneau",
"releaseYear":"2003",
"poster":"img/seigneurdesanneaux1.jpg",
"directors":"Peter Jackson",
"actors":"Elijah Wood, Sean Astin, Ian McKellen, Sala Baker, Viggo Mortensen",
"synopsis":"Frodon le Hobbit hérite de l'Anneau Unique, un instrument de pouvoir absoluqui permettrait à Sauron, le Seigneur des ténèbres, de régner sur la Terre du Milieu. Commence alors un vaste périple visant à la destruction de l'objet.",
"rate":"5"
},
{
"id":3,
"title":"The Grudge",
"releaseYear":"2004",
"poster":"img/thegrudge.jpg",
"directors":"Takashi Shimizu",
"actors":"Sarah Michelle Gellar, Jason Behr, Clea DuVall, Kadee Strickland, Bill Pullman",
"synopsis":"Dans ce qui paraît être une paisible maison de Tokyo se cache un épouvantable fléau. Quiconque franchit le seuil de la demeure est aussitôt frappé par une malédiction qui ne tardera pas à le tuer dans un sentiment d'indicible rage...",
"rate":"4"
},
{
"id":4,
"title":"Yip Man 2",
"releaseYear":"2010",
"poster":"img/yipman.jpg",
"directors":"Wilson Yip",
"actors":"Donnie Yen, Sammo Hung Kam-Bo, Simon Yam, Lynn Hung, Xiaoming Huang",
"synopsis":"Film biographique sur la vie de Ip Man, pionnier du Wing Chun et maitre de Bruce Lee.",
"rate":"5"
}
]
git checkout -f step-2
<li ng-click="faireUnTruc(arg1, arg2, ...)"></li>
function myController($scope){
$scope.faireUnTruc = function(arg1, arg2, ...){
/* faire un truc */
};
}
git checkout -f step-3
Ajout d'un modal Twitter Bootstrap
Afficher une window.alert()
avec quelques infos du films saisies dans le formulaire
movieFormController
pour gérer le formulaire$scope
Ajouter un film dans la liste des films et l'afficher dans la page
$scope
array.push(object)
pour ajouter le film dans le tableau $scope.movies
git checkout -f step-3-solution
$scope
Un évènement peut être "diffusé" aux $scope
enfants ...
$scope.$broadcast('myEvent', param1, param2, ...);
... ou "émis" aux $scope
parents
$scope.$emit('anotherEvent', param1, param2, ...);
L'écoute des évènements se fait comme suit :
$scope.$on('yetAnotherEvent', function(event, param1, param2, ...){
console.log(param1);
});
Ajouter un film dans la liste des films et l'afficher dans la page en utilisant les évènements du scope
git checkout -f step-3-scope-event
Pour effectuer des appels XHR, Angular dispose du service $http
...
... injectable comme le $scope
function myController($scope, $http){
};
Usages
$http.get(url, options)
$http.post(url, data, options)
$http.put(url, data, options)
$http.delete(url, options)
Gérer les retours
$http.get(url, options)
.success(function(data, status, headers, config){
/* do something */
})
.error(function(data, status, headers, config){
/* handle error */
});
Récupérer les films à partir du serveur. Idem pour l'ajout de film
GET /server/api/movies
POST /server/api/movies
git checkout -f step-4
http://localhost:3001/#/home
http://localhost:3001/#/movies
http://localhost:3001/#/movies/edit/1
$routeProvider
Permet de configurer le template HTML à charger avec son contrôleur associé pour une URL donnée
$routeProvider
.when('/route1', {
templateUrl: 'path/to/view1.html',
controller : 'view1Controller'
})
.when('/route2', {
templateUrl: 'path/to/view2.html',
controller : 'view2Controller'
})
.when('/route2/:id', {
templateUrl: 'path/to/view3.html',
controller : 'view3Controller'
})
.otherwise({
redirectTo: '/route1'
});
Dans la factory config de votre module
/* Dans le fichier de définition de votre module app.js*/
var angularMovieApp = angular.module('angularMovieApp', []);
angularMovieApp.config(function($routeProvider) {
/* routes configurations */
});
A utiliser avec la directive ng-view
<body>
<header>
/* Common part for all pages.*/
</header>
<section ng-view>
/* this is the dynamic part */
</section>
<footer>
/* Common part for all pages.*/
</footer>
</body>
Attention : Vous ne pouvez utiliser qu'une seule directive ng-view
La navigation d'une page à l'autre peut être améliorée. Utiliser le service $routeProvider
et la directive ng-view
pour ne garder qu'un fichier index.html
controller
du service $routeProvider
, soit la directive ng-controller
href
des liens de la barre de menu supérieuregit checkout -f step-5
git checkout -f step-5-delete
Un bouton de suppression vient d'être ajouté pour chaque film. Implémentez son action
DELETE /server/api/movies/:id
array.splice(from, nbToDelete)
pour retirer le film de $scope.movies
git checkout -f step-5-delete-solution
git checkout -f step-5-edit
Un bouton de modification vient d'être ajouté pour chaque film. Implémentez son action
/#/movies/edit/:id
PUT /server/api/movies
GET /server/api/movies/:id
$routeParams
dans le controller pour récupérer l'id du film à modifier$location.path('/path')
pour naviguer vers /#/path
git checkout -f step-5-edit-solution
$scope
: seule source de vérité(60 à 80% de votre code applicatif)
<input type="text" ng-model="name">
<input type="email" ng-model="user.mail">
<input ng-model="name"
required
ng-minlength="3"
ng-maxlength="10"
ng-pattern="[^a-d]"
>
$setValidity(validationErrorKey, isValid)
$setValidity('email', false) // email invalide
Ensemble d'inputs
Nommer vos formulaires et vos champs de saisies avec l'attribut name
<form name="myForm">
<input name="myInput">
</form>
myForm.myInput.$error.required // état de validité d'une contrainte
myForm.myInput.$invalid // état de validité d'un champ de saisie
myForm.$dirty && myForm.$valid // vérifier si le formulaire est vierge et valide
Validez le formulaire de création d'un film
<div class="control-group error">
<label class="control-label" >
<input >
<span class="help-block">...</span>
</div>
error
si le champ est invalide
<div ng-class="{className : boolean}"></div>
help-bloc
) si le champ est invalide
<div ng-show="boolean"></div>
ng-disabled
<button ng-disabled="boolean"></button>
git checkout -f step-6
Fournit des tâches spécifiques à l'application
Exemple : $scope, $http
myModuleApp.factory("CountService", function (dep1, dep2, ...) {
// privée
var counter = 0;
// publique
return {
increment : function(){
counter = counter + 1;
};
};
});
function myController($scope, CountService){
CountService.increment();
};
Actuellement, nous utilisons le service $http en l'injectant dans les controllers et nous dupliquons à chaque fois l'url de l'API. Améliorons ceci en créant notre propre service pour nous connecter au back end pour les opérations de CRUD sur les films.
Movies
$http.get("/server/api/movies")
, j'aimerai appeler Movies.fetch();
git checkout -f step-7
Objet vous permettant d'intérargir avec un back end RESTful
$resource(url, paramDefaults, actions);
/user/:username
{username : 'Dupont', order : 'age'}
/user/Dupont?order=age
{
action1: {method:?, params:?, isArray:?},
action2: {method:?, params:?, isArray:?},
...
}
{
'get': {method:'GET'},
'save': {method:'POST'},
'query': {method:'GET', isArray:true},
'remove': {method:'DELETE'},
'delete': {method:'DELETE'}
};
Les ressources récupérées à l'aide de $resource().get()
dispose de méthodes
$save, $update, $remove, $delete
permettant d'effectué les opérations POST, PUT, DELETE
var Car = $resource('/car/:carId', {carId :'@id'});
var car = Car.get({carId:123}, function() {
car.brand = "Toyota";
car.$save();
});
Note : La valeur du paramètre peut être préfixé par @. Cela signifie que cette valeur est extraite de l'objet. Utile pour les opérations de type POST, PUT, DELETE
Dans le service Movies
, remplacer $http
par l'utilisation du service
$resource
. Vous pouvez ainsi gérer tous les appels suivants avec une seule URL paramétrée
Formate les données avant de les afficher
hello
HELLO
1368730200
Jeudi 16 mai 2013 20H50
1234.562342
1 234,56 €
[Thierry, David, Nelly, Alex, Claire]
[Alex, Claire, David, Nelly, Thierry]
[pommes, pâtes, farine, PQ]
[PQ]
$scope.amount = 1234.56;
<div>{{amount}}</div> 1234.56
<div>{{amount | currency}}</div> $1,234.56
<div>{{amount | currency:"USD$"}}</div> USD$1,234.56
date
Formate une date selon un certain format et selon une locale
<div>{{uneDate | date:format}}</div>
Ce filtre accepte un format (string) en argument.
Actuellement, la date et le prix des films ne sont pas visuellement acceptable.
filter
Permet de filtrer un sous ensemble d'une collection et retourne ce sous ensemble.
<div>{{collection | filter:predicat}}</div>
Ce filtre accepte un predicat (string) en argument. Toute chaine de caractère ou propriété qui contient ce prédicat sera retournée dans une nouvelle collection.
git checkout -f step-8
Une barre de recherche est disponible. Implémentez une fonction de recherche en utilisant le filtre filter
orderBy
Tri une collection selon un predicat
<div>{{collection | orderBy:predicat:reverse}}</div>
Ce filtre accepte un predicat (string) en argument. Ce prédicat désigne la propriété sur lequel est fait le tri. Un deuxième argument (boolean) permet d'inverser le sens du tri
2 boutons de tri sont disponibles. Utilisez le filtre orderBy
pour implémentez les tris correspondants
git checkout -f step-8-solution
angularMovieApp.filter('greet', function () {
return function(inputValue, arg1, arg2, ....) {
return 'Hello ' + inputvalue;
};
});
<div>{{name | greet}}</div>
<div>{{name | greet:arg1:arg2}}</div>
git checkout -f step-9
Créer un filtre permettant d'afficher le nombre d'étoiles correspondant à la note.
Dans le répertoire img, se trouve une image no-poster.jpg. Créer un filtre qui affiche cette image lorsqu'aucun poster n'est disponible pour un film.
git checkout -f step-9-solution
Etendre le langage HTML
Créer ses propres composants
Change la structure du DOM de manière conditionnel
<section ng-switch on="value">
<div ng-switch-when="one">Valeur1</div>
<span ng-switch-when="two">Valeur2</span>
<div ng-switch-default >Default</div>
</section>
git checkout -f step-10
Vous allez permettre à l'utilisateur de basculer vers une vue plus compacte de la liste des films.
ng-switch
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>Titre</th>
<th>Réalisateur</th>
<th>Année de sortie</th>
<th>Note</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="movie in movies | orderBy:tri:reverse | filter : search ">
<td>{{$index + 1}}</td>
<td>{{movie.title}}</td>
<td>{{movie.directors}}</td>
<td>{{movie.releaseYear}}</td>
<td>{{movie.rate | stars}}</td>
</tr>
</tbody>
</table>
git checkout -f step-10-solution
<ul class="nav-tabs">
<li class="active">
<a href="#">Primary tab</a>
</li>
<li>
<a href="#">Primary tab</a>
</li>
<li>
<a href="#">Primary tab</a>
</li>
</ul>
<tabs>
<pane title="Primary tab" active>
<!-- contenu -->
</pane>
<pane title="Secondary tab">
<!-- contenu -->
</pane>
<pane title="some other tab">
<!-- contenu -->
</pane>
</tabs>
<calendar></calendar>
<progress-bar></progress-bar>
<dialog></dialog>
<datepicker></datepicker>
<accordion></accordion>
myModuleApp.directive("MaDirective", function (dep1, dep2) {
return {
restrict : 'E',
template : "<div></div>",
templateUrl : "/path/to/ma-directive.html",
replace : true,
transclude : false,
scope : false,
require : false,
controller : function($scope, $element, $attrs){...},
compile : function(tElement, tAttrs, transclude){...},
link : function (scope, element, attrs){...}
};
});
<div navbar ></div>
<navbar></navbar>
<div class="navbar" ></div>
<!-- directive : navbar -->
myModuleApp.directive("MaDirective", function (dep1, dep2) {
return {
template : "<div></div>",
};
});
myModuleApp.directive("MaDirective", function (dep1, dep2) {
return {
template : "<div></div>",
templateUrl : "/path/to/ma-directive.html",
};
});
myModuleApp.directive("MaDirective", function (dep1, dep2) {
return {
template : "<div></div>",
templateUrl : "/path/to/ma-directive.html",
replace : true,
};
});
myModuleApp.directive("MaDirective", function (dep1, dep2) {
return {
template : "<div></div>",
templateUrl : "/path/to/ma-directive.html",
replace : true,
restrict : 'EACM',
};
});
myModuleApp.directive("MaDirective", function (dep1, dep2) {
return {
template : "<div></div>",
templateUrl : "/path/to/ma-directive.html",
replace : true,
restrict : 'EACM',
scope : true,
};
});
myModuleApp.directive("MaDirective", function (dep1, dep2) {
return {
template : "<div></div>",
templateUrl : "/path/to/ma-directive.html",
replace : true,
restrict : 'EACM',
scope : {
data1 : '@',
data2 : '=',
data3 : '&'
},
};
});
myModuleApp.directive("MaDirective", function (dep1, dep2) {
return {
template : "<div></div>",
templateUrl : "/path/to/ma-directive.html",
replace : true,
restrict : 'EACM',
scope : true,
transclude : true,
};
});
myModuleApp.directive("MaDirective", function (dep1, dep2) {
return {
template : "<div></div>",
templateUrl : "/path/to/ma-directive.html",
replace : true,
restrict : 'EACM',
scope : true,
transclude : true,
compile : function(tElement, tAttrs, transclude){
return function(scope, iElement, iAttrs, controller){
}
}
};
});
myModuleApp.directive("MaDirective", function (dep1, dep2) {
return {
template : "<div></div>",
templateUrl : "/path/to/ma-directive.html",
replace : true,
restrict : 'EACM',
scope : true,
transclude : true,
compile : function(tElement, tAttrs, transclude){
return function(scope, iElement, iAttrs, controller){
}
}
controller : function($scope, $element, $attrs, $transclude){...}
};
});
$scope.$watch()
et $scope.$apply()
pour surveiller et mettre à jour manuellement une valeur
Thierry Lau - @laut3rry