1. expression dans HTML = watcher ajouté au scope.
{{ 'variable = ' + maVar }}
...
// code explicatif non fonctionnel !!
$scope.$watch(function() {
return $parse(" 'variable = ' + maVar ")($scope);
}, function(newValue, oldValue) {
// met à jour le contenu de l'élément du DOM ('element.text()') avec nouvelle valeur.
});
$scope.$watch(function() {
return $parse(" getScore() ")($scope);
}, function(newValue, oldValue) {
// met à jour le contenu de l'élément du DOM ('element.text()') avec nouvelle valeur.
});
$scope.$watch(function() {
return $parse(" {'text-success': awesome, 'text-large': giant } ")($scope);
}, function(newValue, oldValue) {
// met à jour les classes de l'élément du DOM ('element.addClass()' / 'element.removeClass()') avec nouvelle valeur.
});
$scope.$watch(function() {
return $parse(" isTermsOk ")($scope);
}, function(newValue, oldValue) {
// met à jour le DOM ('element.html()') en ajoutant ou supprimant les éléments enfants suivant si true/false.
});
2. Quand un "digest cycle" tourne, les expressions sont parsées et si résultat diffère du précédent, la fonction associée est lancée (dirty checking).
// code explicatif non fonctionnel !!
var Scope = function() {
this.$$watchers = [];
this.$watch = function(watcherFn, listenerFn) {
this.$$watchers.push({
watcherFn: watcherFn,
listenerFn: listenerFn
});
};
this.$digest = function() {
this.$$watchers.foreach(function(watcher) {
var newValue = watcher.watcherFn();
var oldValue = watcher.last;
if(newValue !== oldValue) {
watcher.listenerFn(newValue, oldValue);
watcher.last = newValue;
}
});
};
}
Matthieu Lux - ngEurope
3. Un "digest cycle" n'est jamais lancé par $scope.$digest() mais par $scope.$apply().
// code explicatif non fonctionnel !!
var Scope = function() {
//....
this.$apply = function(exprFn) {
try {
exprFn();
} finally {
this.$digest();
}
};
}
app.controller('MainCtrl', function($scope, $timeout) {
$scope.myVar = 'titi';
setTimeout(function() {
$scope.myVar = 'toto';
}, 2000);
// > la vue ne sera pas mise à jour..
// Angular n'est pas averti de la maj !
setTimeout(function() {
$scope.$apply(function() {
$scope.myVar = 'toto';
});
}, 4000);
// > la vue sera mise à jour !
$timeout(function() {
$scope.myVar = 'toto';
},6000);
// > la vue sera mise à jour !
});
Dans l'écosystéme Angular, le $apply est appelé frequemment sans qu'on le voit:
ngClick, ngDblclick, ngMouseenter, ...$timeout, $interval, $q, $http, ...$apply.$apply est principalement utilisé pour l'intégration de libraries externes à Angular.watchers (10 fois max).Possibilité de faire des watchers en JS:
app.controller('MainCtrl', function($scope) {
$scope.myVar = 'titi';
$scope.$watch(function() {
return $scope.myVar;
}, function(newValue, oldValue) {
// la variable myVar vient de changer !
});
var unbinder = $scope.$watch('myVar', function(newValue, oldValue) {
// la variable myVar vient de changer !
});
// plus tard, on supprime le watcher:
unbinder();
});
controllers, filtres, services,..Dans la pratique, chaque provider posséde une méthode $get qui retourne l'instance et peut comporter des propriétés ou méthodes servant à configurer l'instanciation du service associé.
Il existe 5 méthodes pour déclarer un service mais dans tous les cas, ils s'injectent et s'utilisent de la même manière et il en existe toujours une seule instance.
factory() et service() sont juste des manières plus simples d’écrire un provider().
$get obligatoire retournant l'instance du service.var app = angular.module('myapp');
app.provider('MyService', function() {
var scale = 1;
this.setScale = function(newScale) {
scale = newScale;
};
this.$get = function ($window) {
return {
getScreenWidth: function() {
return $window.innerWidth * scale;
},
getScreenHeight: function() {
return $window.innerHeight * scale;
}
};
};
});
app.config(function(MyServiceProvider) {
MyServiceProvider.setScale(10);
});
app.controller('AppCtrl', function($scope, MyService) {
var _width = MyService.getScreenWidth();
});
provider().$get précédente.app.factory('MyService', function($window) {
var scale = 1;
return {
getScreenWidth: function() {
return $window.innerWidth * scale;
},
getScreenHeight: function() {
return $window.innerHeight * scale;
}
};
});
app.controller('AppCtrl', function($scope, MyService) {
var _width = MyService.getScreenWidth();
});
provider().new pour créer l'instance du service.return un objet, tout ce qui est attaché à this sera accessible par le biais du service.app.service('MyService', function($window) {
var scale = 1;
this.getScreenWidth = function() {
return $window.innerWidth * scale;
};
this.getScreenHeight = function() {
return $window.innerHeight * scale;
}
});
app.controller('AppCtrl', function($scope, MyService) {
var _width = MyService.getScreenWidth();
});
object ou bien de type primitif (booléen, string, number).provider lors de la phase de configuration du module.services.app.value('GoogleMapAPI_KEY', '5VUhR4kqvVvhXRH4BIAN')
app.value('VideoData', {
title: 'Ters Akan şelale!',
description: 'Bu işte bir yanlışlık var diye İngiltere',
url: 'http://www.dailymotion.com/video/x28i2n3_ters-akan-selale_news'
})
app.controller('AppCtrl', function($scope, VideoData) {
$scope.video = VideoData;
});
value() mais est accessible lors de la phase de configuration du module.app.constant('UselessData', {
scale: 42
});
app.config(function(MyServiceProvider, UselessData) {
MyServiceProvider.setScale(UselessData.scale);
});
app.controller('AppCtrl', function($scope, UselessData) {
$scope.scale = UselessData.scale;
});
playerApp.value('Params', {
URL_API: 'https://api.dailymotion.com/videos',
URL_EMBED: 'http://www.dailymotion.com/embed/video',
VIDEOS_PER_PAGE: 12,
VIDEOS_AUTOPLAY: true,
});
playerApp.factory('Dailymotion', function($http, $sce, Params) {
var getApiUrl = function(terms, page) {
var api_params = '?fields=id,title,description,duration_formatted,created_time&sort=relevance';
var api_limit = '&limit=' + Params.VIDEOS_PER_PAGE;
var api_page = '&page=' + page;
var api_search = '&search=' + terms.split(' ').join('+');
var api_jsonp = '&callback=JSON_CALLBACK';
return Params.URL_API + api_params + api_limit + api_page + api_search + api_jsonp;
};
var Service = {
getResults: function(terms, page) {
return $http({
method: 'JSONP',
url: getApiUrl(terms, page),
cache: true,
transformResponse: function (data, headers) {
data.nbPages = Math.ceil(data.total / data.limit);
return data;
}
});
},
getIframeUrl: function(videoId) {
var url = Params.URL_EMBED + '/' + videoId + '?autoPlay=' + Params.VIDEOS_AUTOPLAY;
return $sce.trustAsResourceUrl(url);
}
};
return Service;
});
playerApp.controller('VideoPlayerModalCtrl', function($scope, $modalInstance, Dailymotion, video) {
$scope.video = video;
$scope.iframeUrl = Dailymotion.getIframeUrl(video.id);
$scope.close = function () {
$modalInstance.close();
};
});
En pratique,factory()etvalue()sont utilisés dans la plupart du cas.
$http, $location, $filter, $window, $timeout, $interval, $animate, $rootScope, $document,..
Providers associés pour les configurer au démarrage:$httpProvider, $locationProvider, $filterProvider, $animateProvider,..
"Chaque requête d’un client vers un serveur doit contenir toute l’information nécessaire pour permettre au serveur de comprendre la requête, sans avoir à dépendre d’un contexte conservé sur le serveur. Cela libère de nombreuses interactions entre le client et le serveur."
Ajout d'un cookie avec un ID de session sur le navigateur de l'utilisateur aprés l'authentificaion.
Le serveur lit ce cookie à chaque requête suivante pour identifier l'utilisateur.
Génération d'un "passwordDigest" coté client à partir du login + pwd + salt qui est ajouté à chaque requête REST.
Possibilité de faire nativement avec Symfony2
Requête d'authentification qui retourne un "JWT access token" à durée limité qui est ajouté à chaque requête REST.
Bundle Symfony2 LexikJWTAuthenticationBundle.
Solution de plus en plus utilisée et trés bien pour les clients mobiles.
watchers (scroll infini,..) en découpant son application.watchers sur les tableaux.ngRepeat avec 'track by'.ngIf / ngSwitch et ngShow / ngHide.{{ ::title }}
$scope.$digest() au lieu de $scope.$apply(). [Avec précaution!]scope courant et ses descendants plutôt que toute l'application.filters.Sur la prod, passer debugInfoEnabled à false:
app.config(function($compileProvider) {
$compileProvider.debugInfoEnabled(false);
});Plus sur ngmodules.org
Surcouches d'Angular pour applications mobiles hybrides:
Equivalent de Composer pour les libraires Front.
bower init // démarrage d'un projet bower
bower install jquery --save // installation nouvelle librairie front
bower install jquery#2.15.5 --save // installation d'une version spécifique
bower uninstall restangular --save // desinstallation une librairie
bower list [--offline] [--paths] // listing des librairies installées
bower search bxslider // recherche d'une librairie
bower update angular --save // mise à jour d'une librairie déjà installé
bower info angular-google-maps // liste les versions disponibles
bower cache clean // suppression cache bower
Compass est une librairie SASS comprenant de nombreux mixins.@mixin box-shadow($properties...) {
-moz-box-shadow: $properties;
-webkit-box-shadow: $properties;
-o-box-shadow: $properties;
box-shadow: $properties;
}
$color_start: #f5f3f4;
$color_hover_start: #F4F4F4;
@for $i from 0 through 15 {
$darken_value: min(($i + 1) * 6, 100);
$darken_value_more: min($darken_value + 8, 100);
$darken_value_more_more: min($darken_value + 20, 100);
&.level-#{$i} {
background: darken($color_start, $darken_value);
border: 1px solid darken($color_start, $darken_value_more);
@include box-shadow(inset 0px 0px 10px 0px darken($color_start, $darken_value_more_more));
&:hover {
border-color: #666666;
}
.options {
border-bottom: 1px solid darken($color_start, $darken_value_more);
}
}
}
.level-0 {
background: #e7e2e5;
border: 1px solid #d5ccd0;
-moz-box-shadow: inset 0px 0px 10px 0px #b9abb2;
-webkit-box-shadow: inset 0px 0px 10px 0px #b9abb2;
-o-box-shadow: inset 0px 0px 10px 0px #b9abb2;
box-shadow: inset 0px 0px 10px 0px #b9abb2; }
.level-0:hover {
border-color: #666666; }
.level-0 .options {
border-bottom: 1px solid #d5ccd0; }
.level-1 {
background: #d9d2d5;
border: 1px solid #c7bbc1;
-moz-box-shadow: inset 0px 0px 10px 0px #ab9aa2;
-webkit-box-shadow: inset 0px 0px 10px 0px #ab9aa2;
-o-box-shadow: inset 0px 0px 10px 0px #ab9aa2;
box-shadow: inset 0px 0px 10px 0px #ab9aa2; }
.level-1:hover {
border-color: #666666; }
.level-1 .options {
border-bottom: 1px solid #c7bbc1; }
.level-2 {
background: #cbc1c6;
border: 1px solid #b9abb2;
-moz-box-shadow: inset 0px 0px 10px 0px #9d8993;
-webkit-box-shadow: inset 0px 0px 10px 0px #9d8993;
-o-box-shadow: inset 0px 0px 10px 0px #9d8993;
box-shadow: inset 0px 0px 10px 0px #9d8993; }
.level-2:hover {
border-color: #666666; }
.level-2 .options {
border-bottom: 1px solid #b9abb2; }
...
Gruntfile.jsConcaténer des fichiers en un seul (JS,..).
Copier des fichiers (fonts, templates,..).
Réécrie toutes les injections de dépendance pour la minification.
app.controller('MainCtrl', function($scope, MyCustomService) {
/* code du controlleur */
});
app.controller('MainCtrl', ['$scope', 'MyCustomService', function($scope, MyCustomService) {
/* code du controlleur */
}]);
function MainCtrl($scope, MyCustomService) {
/* code du controlleur */
}
MainCtrl.$inject = ['$scope', 'MyCustomService'];
app.controller('MainCtrl', MainCtrl);
Uglify un/des fichiers JS.
Compile ses fichiers SASS en CSS.
Minifie un/des CSS.
Minifie les templates HTML angular et les insert directement dans $templateCache.
Evite de nombreuses requêtes ajax.
app.run(["$templateCache", function($templateCache) {
$templateCache.put("src/app/templates/page1.tpl.html",
"Page1: {{ myVar }}
"
);
$templateCache.put("src/app/templates/page2.tpl.html",
"Page2: {{ myVar }}
"
);
}]);
Tourne en tâche de fond et exécute d'autres tâches quand certains fichiers sont modifiés.
Exemples d'utilisation classique:
A vous de créer vos propres tâches qui sont des enchainements des autres tâches.
controller, directive, filter,..).app.controller('app.library.PhotosListCtrl', function(...) {});angular.bootstrap() au lieu de ng-app pour faire un beau loading.