Depuis la sortie de Angular 1.1.5 (Triangle Squarification), la directive ngIf
a été ajoutée au framework AngularJs pour permettre de créer ou supprimer un bout de DOM en fonction de l’évaluation d’une expression booléenne, comportement auparavant possible uniquement grâce à la directive ngSwitch
.
La directive ngIf
s’utilise de manière similaire à la directive ngShow
. Voici deux exemples qui produisent visiblement le même résultat.
ngIf
<!-- Exemple avec ngIf --> <label>Afficher / masquer : <input type="checkbox" ng-model="show" /></label> <span ng-if="show">Ceci est un test</span>
ngShow
<!-- Exemple avec ngShow --> <label>Afficher / masquer : <input type="checkbox" ng-model="show" /></label> <span ng-show="show">Ceci est un test</span>
Quelle différence ?
Dans les faits, ces deux directives ne se comportent absolument pas de la même manière. ngShow se contente d’afficher ou masquer un élément du DOM en lui ajoutant ou supprimant la classe CSS ng-hide
définie par défaut de la manière suivante :
.ng-hide { display:none !important; }
L’élément en question reste bien systématiquement présent dans le DOM, seule sa visibilité varie grâce à cette classe CSS.
Concernant la directive Angular ngIf
, cette dernière procède à une suppression ou un ajout dans le DOM de l’élément concerné, en fonction de l’évaluation de l’expression booléenne associée.
ngIf differs from ngShow and ngHide in that ngIf completely removes and recreates the element in the DOM rather than changing its visibility via the display css property.
La première différence qui saute aux yeux est l’impact au niveau des sélecteurs de type pseudo-classe (pseudo-class selector) que ce soit pour le CSS ou via une directive faisant usage de jQuery ou jqlite.
A common case when this difference is significant is when using css selectors that rely on an element’s position within the DOM, such as the :first-child or :last-child pseudo-classes
.menu li { display: inline-block; margin: 10px 0; padding: 5px; } .menu li:first-child { margin-left: 30px; } .menu li:last-child { margin-right: 30px; }
<label>Je suis un administrateur : <input type="checkbox" ng-model="user.isAdmin" /></label> <ul class="menu"> <li>Menu 1</li> <li>Contact</li> <li ng-show="user.isAdmin">Menu Admin</li> </ul>
Dans l’exemple ci-dessus, on ajoute une marge spécifique au premier et au dernier élément du menu. Mais avec l’utilisation de la directive ngShow
on n’obtient pas le rendu initialement souhaité par notre intégrateur. Le menu spécifique à l’administrateur, bien que non visible pour un utilisateur lambda, reste bien présent dans le DOM et l’entrée contact n’est dès lors pas ciblée par notre pseudo-classe :last-child
.
Impact sur les performances
Dans la majorité des cas, l’utilisation de ngIf
par rapport à ngShow
n’a pas d’impact perceptible sur les performances. Il est pourtant légitime de se poser la question lorsqu’on travaille au sein d’une liste importante en terme de volumétrie, typiquement au sein d’un ngRepeat
sur plusieurs centaines voire milliers d’éléments.
Prenons l’exemple suivant. Nous avons une liste importante (> 1000 éléments) et nous avons deux types d’informations à afficher ; le nom de l’élément et potentiellement sa description.
<ul> <li ng-repeat="element in elements"> <span>{{ element.name }}</span> <!-- Avec la directive ngShow --> <span ng-show="element.showDescription">{{ element.description }}</span> <!-- Avec la directive ngIf --> <span ng-if="element.showDescription">{{ element.description }}</span> </li> </ul>
Deux cas très différents se profilent. Soit la condition d’affichage / masquage est susceptible de varier régulièrement en fonction des interactions de l’utilisateur, soit elle varie rarement.
Par exemple, si l’affichage / masquage de la description est déclenché suite au cochage / décochage d’une option en haut de page, il semble plus avantageux d’utiliser la directive ngShow
. L’élément est déjà inséré dans le DOM et il ne reste plus qu’au navigateur à recalculer sa visibilité suite à l’ajout ou la suppression de la classe ng-hide
. D’autre part l’utilisation de ngIf a pour effet de créer un scope spécifique à l’élément ciblé ce qui entraîne nécessairement sa destruction lorsque la condition booléenne est évaluée à false
, et donc un temps de traitement associé.
En revanche, si la condition d’affichage de la description dépend par exemple des droits associés à l’utilisateur connecté, mieux vaut utiliser la directive ngIf
. Ainsi, si l’utilisateur n’a pas le droit de voir la description, le DOM est très largement allégé (et donc l’emprunte mémoire associée). C’est d’autant plus vrai que l’élément ciblé par la directive ngIf/ngShow/ngHide est important.
Attention toutefois, il peut être bien plus avantageux de travailler à limiter le nombre de $watch
générés au sein d’un ng-repeat en codant sa propre directive qui ne crée aucun watchers ou en faisant usage de directives optimisées telles que la désormais fameuse BindOnce.
ngIf et scope enfant (child scope)
Je l’ai évoqué un peu plus haut, contrairement à ngShow ou ngHide, ngIf crée un scope enfant pour l’élément ciblé.
Note that when an element is removed using ngIf its scope is destroyed and a new scope is created when the element is restored. The scope created within ngIf inherits from its parent scope using prototypal inheritance.
Ainsi, sur le bout de code qui suit, le binding au model ne se comporte pas de la même manière alors que la seule différence d’implémentation réside au niveau de l’utilisation d’un ngShow
VS ngIf
.
function MyCtrl($scope) { $scope.show = true; $scope.value = "gouzigouza"; }
<div ng-app> <div ng-controller="MyCtrl"> <label>Afficher / masquer</label> <input type="checkbox" ng-model="show" /> <div> <label>Scope parent</label> <input type="text" ng-model="value" /> </div> <div ng-show="show"> <label>ngShow</label> <input type="text" ng-model="value" /> </div> <div ng-if="show"> <label>ngIf</label> <input type="text" ng-model="value" /> </div> </div> </div>
Vous pouvez voir le résultat sur le fiddle ci-dessous. Essayez de modifier la valeur des différents inputs. Vous constaterez que le troisième input
est bindé sur la même valeur que les 2 premiers, tant que sa propre valeur n’a pas été modifiée via le champ input
lui-même. Vous pouvez également constater que si la valeur de ce dernier est modifiée et que la case à cocher permettant d’activer ou non la condition du ngIf
varie, la valeur de l’input est ré-initialisée à celle du parent scope.
Conclusion
Même si les directives ngShow, ngHide et ngIf semblent produire le même résultat, leur implémentation et leur comportement diffèrent. Il est important de bien comprendre leur fonctionnement pour les utiliser à bon escient.