Advertisement
  1. Code
  2. Angular

Utiliser les Directives d'AngularJS Pour Étendre HTML

Scroll to top
Read Time: 11 min

() translation by (you can also view the original English article)

Un atout principal d'AngularJS est la possibilité d'étendre les capacités du HTML pour les besoins d'interactivités des pages d'aujourd'hui. Dans cet article, je vais montrer que l'utilisation des Directives d'AngularJS va rendre le développement plus rapide et plus facile, tout en gardant un code maintenable.

Préparation

Etape 1: le template HTML

Pour garder les choses simples, tout notre code sera placé dans un seul fichier HTML. Créez le et ajoutez ce code de base:

1
<!DOCTYPE html> <html> <head> </head> <body> </body> </html>

Maintenant, ajoutez angular.min.js depuis le CDN Google dans le tag <head> du document:

1
 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script>

Etape 2: Création du module

On va maintenant créer le module pour notre directive. Je vais l'appeler example, choississez le nom que vous voulez, gardez juste à l'esprit que ce nom sera utilisé comme namespace pour la directive.

Mettez ce code dans un tag script à la fin de la section <head>:

1
var module = angular.module('example', []);

Il n'y a pas de dépendances donc le second argument d'angular.module() est un tableau vide mais il ne doit pas être retiré sous peine de recevoir une erreur $injector:nomod. Car si angular.module() est appelé avec un seul argument, la méthode agit comme un getter et retourne une référence à un objet existant et n'en crée pas un nouveau.

Il faut aussi ajouter l'attribut ng-app="example" au tag <body> pour que l'application fonctionne. Le fichier contient désormais le code suivant:

1
<!DOCTYPE html>
2
<html>
3
    <head>
4
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script> <script> var module = angular.module('example', []); </script> 
5
    </head> 
6
    <body ng-app="example"> 
7
    </body> 
8
</html>

La Directive d'attribut: 1337 C0NV3R73R

Pour débuter, nous allons créer une directive simple dont le fonctionnement est proche de ngBind, et qui va changer le text vers le language leet.

Etape 1: Déclaration de la directive

Les directives sont déclarées en utilisant la méthode module.directive():

1
module.directive('exampleBindLeet', function () {

Le premier argument est le nom de la directive. Il doit être en camelCase mais comme HTML n'est pas sensible à la casse, il faudra utiliser une version minuscule, séparée par des tirets (example-bind-leet) dans le code HTML.

La fonction passée en deuxième argument doit retourner un objet décrivant la directive. Pour le moment, on aura une seule propriété: la fonction Link:

1
    return {
2
  	link: link
3
	};
4
});

Etape 2: la fonction Link

Vous pouvez soit définir la fonction dans le return, soit dans l'objet qui est retourné. Elle est utilisée pour manipuler le DOM de l'élément sur lequel notre directive est appliquée et est appelée avec trois arguments:

1
function link($scope, $elem, attrs) {

$scope est l'objet scope d'Angular, $elem est l'élément DOM sur lequel la directive est appliquée (il s'agit d'un objet jqLite, la version allégée de jQuery intégrée à Anuglar qui ne reprend que les fonctions les plus utilisées) et attrs qui est un objet avec tous les attributs de l'élément (avec nom normalisé, donc example-bind-leet est disponible sous attrs.exampleBindLeet).

Le code le plus simple de cette fonction pour notre directive pourrait ressembler à ceci:

1
    var leetText = attrs.exampleBindLeet.replace(/[abegilostz]/gmi, function (letter) {
2
	    return leet[letter.toLowerCase()];
3
    });
4
5
	$elem.text(leetText);
6
}

On remplace certaines lettres du texte fournis dans l'attribut example-bind-leet par leur équivalent du tableau leet. Le tableau contient ceci:

1
var leet = {
2
    a: '4', b: '8', e: '3',
3
	g: '6', i: '!', l: '1',
4
	o: '0', s: '5', t: '7',
5
	z: '2'
6
};

Vous devez le placer au début de votre code, dans le tag <script>. Il s'agit là de la forme la plus basique de conversion leet, puisqu'on ne remplace que dix caractères.

Ensuite, on utilise la méthode text() de jqLite pour placer le texte leet dans l'élément sur lequel la directive est appliquée.

Maintenant vous pouvez tester le code en ajoutant ce HTML dans le <body> du document:

1
<div example-bind-leet="This text will be converted to leet speak!"></div>

Le résultat devrait ressembler à ceci:

Toutefois, ce n'est pas exactement comme cela que fonctionne la directive ngBind. On va changer ça dans les prochaines étapes.

Etape 3: le Scope

Tout d'abord, ce qui est passé à l'attribut example-bind-leet devrait être une référence à une variable du scope, pas le texte que l'on veut convertir. Pour y parvenir, on va devoir créer un scope isolé pour notre directive.

On peut l'obtenir en ajoutant un objet scope à l'objet retourné par la fonction de construction de notre directive:

1
module.directive('exampleBindLeet', function () {
2
    ...
3
	return {
4
		link: link,
5
		scope: {
6
7
		}
8
	};
9
);

Toutes les propriétés de cet objet seront disponible dans le scope de la directive. Leur valeur sera déterminée en fonction de la valeur donnée à la propriété. Si on utilise ‘-' la valeur sera égale à la valeur de l'attribut qui a le même nom que la propriété. Utiliser ‘=' revient à dire au compilateur que l'on attend qu'une variable du scope courant soit passée - tout comme c'est le cas pour ngBind:

1
scope: {
2
	exampleBindLeet: '='
3
}

On peut également utiliser n'importe quel nom de propriété et ajouter le nom normalisé (converti en cameCase) après - ou =:

1
scope: {
2
	text: '=exampleBindLeet'
3
}

Choisissez ce qui fonctionne le mieux pour vous. Il nous reste à changer la fonction link pour utiliser $scope à la place de attr:

1
function link($scope, $elem, attrs) {
2
    var leetText = $scope.exampleBindLeet.replace(/[abegilostz]/gmi, function (letter) {
3
		return leet[letter.toLowerCase()];
4
	});
5
6
	$elem.text(leetText);
7
}

Maintenant, utilisez ngInit ou créez un controller et changez la valeur de l'attribut example-bin-leet du div avec la variable que vous avez utilisé:

1
 <body ng-app="example" ng-init="textToConvert = 'This text will be converted to leet speak!'"> 
2
    <div example-bind-leet="textToConvert"></div> 
3
</body> 

Etape 4: détecter les changements

Mais ce n'est pas encore tout à fait comme cela que fonctionne ngBind. Pour le voir, ajoutons un champ de formulaire pour changer la valeur de textToConvert une fois que la page a été chargée:

1
<input ng-model="textToConvert">

Si vous ouvrez la page et que vous essayez de changer le texte du champ de formulaire, vous verrez que rien ne change dans notre div. La raison est que la fonction link() est appelée une seule fois par directive, au moment de la compilation. Elle ne peut donc pas changer le contenu d'un élément chaque fois qu'un changement survient dans le scope.

On va donc utiliser la méthode $scope.$watch(). Elle accepte deux paramètres: le premier est une expression Angular qui sera évaluée à chaque modification du scope, le second est la fonction callback qui sera appelée si la valeur de l'expression a changée.

Avant tout, mettons le code que nous avions dans la fonction link() dans une fonction locale:

1
function link($scope, $elem, attrs) {
2
    function convertText() {
3
		var leetText = $scope.exampleBindLeet.replace(/[abegilostz]/gmi, function (letter) {
4
			return leet[letter.toLowerCase()];
5
		});
6
7
		$elem.text(leetText);
8
	}
9
}

Après cette fonction, on va appeler $scope.$watch():

1
$scope.$watch('exampleBindLeet', convertLeet);

Si vous ouvrez la page maintenant et changez quelque chose dans le champ de formulaire, le contenu du div change également, comme attendu.

La Directive élément: la barre de progression

Nous allons maintenant écrire une directive qui va créer une barre de progression. On va utiliser un nouvel élément: <example-progress>.

Etape 1: le style

Pour que notre barre de progression ressemble à une barre de progression, on va avoir besoin d'un peu de CSS. Ajoutez ce code dans une balise <style> dans le <head> du document:

1
example-progress {
2
    display: block;
3
	width: 100%;
4
	position: relative;
5
	border: 1px solid black;
6
	height: 18px;
7
}
8
9
example-progress .progressBar {
10
	position: absolute;
11
	top: 0;
12
	left: 0;
13
	bottom: 0;
14
	background: green;
15
}
16
17
example-progress .progressValue {
18
	position: absolute;
19
	top: 0;
20
	left: 0;
21
	right: 0;
22
	bottom: 0;
23
	text-align: center;
24
}

Comme vous pouvez le voir, c'est du basique - on utilise une combinaison de position: relative et position: absolute pour positionner la barre verte et sa valeur à l'intérieur de notre élément <example-progress>.

Etape 2: les propriétés de la Directive

Cette directive va demander un peu plus d'options que les précédentes. Jetez un coup d'oeil à ce code (et ajoutez le à votre tag <script>):

1
module.directive('exampleProgress', function () {
2
    return {
3
		restrict: 'E',
4
		scope: {
5
			value: '=',
6
			max: '='
7
		},
8
		template: '',
9
		link: link
10
	};
11
});

Comme vous pouvez le voir, on continue d'utiliser un scope (avec deux propriétés cette fois - value pour la valeur courante et max pour la valeur maximale) et une fonction link(), mais il y a aussi deux nouvelles propriétés:

  • restrict: 'E' - celle-ci indique au compilateur de rechercher un élément plutôt qu'un attribut. Les valeurs possibles sont:
    • 'A' - correspond seulement au attribut (c'est le comportement par défaut, il n'est donc pas nécessaire de l'ajouter si vous souhaitez uniquement utiliser la directive avec un attribut)
    • 'E' - correspond uniquement à l'élément
    • 'C' - correspond à un nom de classe
  • Vous pouvez les combiner, par exemple ‘AEC' permet de combiner attributs, éléments et classes.
  • template: '' - ceci nous permet de changer le contenu HTML de notre élément (il y a aussi templateUri si vous souhaitez charger votre HTML depuis un ficher externe)

Bien entendu, nous n'allons pas laisser ‘template' vide. Ajoutez ce code HTML comme valeur de template:

1
<div class="progressBar"></div><div class="progressValue">{{ percentValue }}%</div>

Comme vous pouvez le constater, on peut utiliser les expressions Angular dans le template - percentValue fait partie du scope de la directive.

Etape 3: la fonction Link

Cette fonction sera similaire à celle de la directive précédente. Premièrement, créez une fonction locale qui exécutera la logique de la directive - dans ce cas, mettre à jour percentValue et adapter la largueur de div.progressBar:

1
function link($scope, $elem, attrs) {
2
    function updateProgress() {
3
		var percentValue = Math.round($scope.value / $scope.max * 100);
4
		$scope.percentValue = Math.min(Math.max(percentValue, 0), 100);
5
		$elem.children()[0].style.width = $scope.percentValue + '%';
6
	}
7
}

Vous avez peut-être remarqué que l'on utilise pas la fonction .css() pour changer la largeur du div.progressBar, jqLite ne supporte pas les sélecteurs dans .children(). On doit également utiliser Math.min() et Math.max() pour garder la valeur entre 0% et 100% - Match.max() retourne 0 si percentValue est inférieur à 0 et Math.min() retourne 100 si percentValue est supérieur à 100.

A la place de deux appels à $scope.$watch() (on doit faire attention aux changements de $scope.value et de $scope.max), on va utiliser $scope.$watchCollection(), qui a un fonctionnement similaire pour les collections de propriétés:

1
$scope.$watchCollection('[value, max]', updateProgress);

Attention, on passe une chaîne de caractère qui ressemble à un tableau comme premier paramètre, pas un tableau JavaScript.

Pour comprendre le fonctionnement, on va utiliser ngInit pour initialiser deux nouvelles variables:

1
<body ng-app="example" ng-init="textToConvert = 'This text will be converted to leet speak!'; progressValue = 20; progressMax = 100">

Et ensuite ajoutez l'élément <example-progress> en dessous du div utilisé plus tôt:

1
<example-progress value="progressValue" max="progressMax"></example-progress>

Le <body> doit maintenant contenir ceci:

1
<body ng-app="example" ng-init="textToConvert = 'This text will be converted to leet speak!'; progressValue = 20; progressMax = 100"> 
2
    <div example-bind-leet="textToConvert"></div> 
3
    <example-progress value="progressValue" max="progressMax"></example-progress> 
4
</body> 

Et ceci est le résultat:

Etape 4: ajouter les animations avec jQuery

Si vous ajoutez des champs pour progressValue et progressMax comme ceci:

1
<input ng-model="progressValue"> 
2
<input ng-model="progressMax">

Vous noterez que quand vous changez l'une des valeurs, le changement de largeur est immédiat. Pour rendre tout cela un peu plus joli, utilisons jQuery pour l'animer. Le truc intéressant lorsqu'on utilise jQuery avec AngularJS c'est que dès qu'on ajoute le script de jQuery, Angular va automatiquement l'utiliser à la place de jqLite, faisant de $elem un objet jQuery.

Commençons donc par ajouter jQuery dans le <head> du document, avant AngularJS:

1
<script src="http://code.jquery.com/jquery-2.1.0.min.js"></script>

On peut ensuite adapter notre fonction updateProgress() pour utiliser la méthode .animate() de jQuery. Changez la ligne:

1
$elem.children()[0].style.width = $scope.percentValue + '%'; 

pour:

1
$elem.children('.progressBar').stop(true, true).animate({ width: $scope.percentValue + '%' }); 

Et vous devriez avoir une magnifique barre de progression animée. Il faut utiliser la méthode .stop() pour arrêter et finir toutes animations en cours d'exécution au cas où l'une des valeurs change pendant que l'animation est en cours (essayez de la supprimer et de changer rapidement les valeurs d'input pour comprendre sa nécessité).

Bien entendu il faudrait changer le CSS et probablement utiliser d'autres fonctions pour s'adapter au style de votre application.

Conclusion

Les directives AngularJS sont de puissants outils pour tous les développeurs web. Vous pouvez créer une série de directive qui simplifieront et accéléreront votre temps de développement. Ce que vous pouvez créer n'est limiter que par votre imagination, vous pouvez pratiquement transformer tous vos templates côté serveur par des directives AngularJS.

Liens utiles

Quelques liens utiles vers la documentation AngularJS:

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.