En Angular, comme dans d'autres frameworks ou librairies, pour rendre nos composants les plus légers possibles, nous essayons de les diviser. Cette division s'opère autour de composants plus petits et de préférence génériques dans le but de pouvoir les réutiliser et ainsi gagner en temps de dévelopement.
Malheureusement, très souvent entre deux pages de notre application, le composant peut avoir un style un peu différent.
Comment alors customiser le style de ce type de composant en fonction des pages où il est appelé?
En Angular, plusieurs choix s'offrent à nous pour répondre à cette problématique:
- les propriétés (@Input)
- les directives
- les sélecteurs CSS des Web Components
- les variables CSS
Cet article met en avant l'utilisation des sélecteurs CSS des Web Components et des variables CSS appliqués dans un cadre Angular.
Les différentes encapsulations d'un composant Angular
Avant de se lancer directement dans l'utilisation des sélecteurs CSS, il est requis de comprendre une notion importante d'un composant Angular: l'encapsulation
En Angular il existe trois types d'encapsulation pour un composant
- encapsulation.None
- encapsulation.Emulated
- encapsulation.ShadowDom
Encapsulation.None
Ce système d'encapsulation, comme son nom l'indique, ne fournit aucune encapsulation CSS, ce qui signifie que tous les styles fournis au composant sont applicables à n'importe quel élément HTML de l'application quel que soit son composant hôte.
En d'autres termes, le style de composant est partagé à toute la page où il est appelé, ce qui peut résulter à des classes surchargées si on ne fait pas attention au nommage de nos classes.
Encapsulation.Emulated
Ce système d'encapsulation émule un comportement d'encapsulation natif du Shadow Dom en ajoutant un attribut spécifique à l'élément hôte du composant mais également à tous les sélecteurs CSS du composant.
C'est le comportement par défaut d'Angular.
Attention, ici aucun Shadow Dom n'est créé, il s'agit juste d'une encapsulation CSS, le style du composant n'est donc, par définition, pas partagé.
ngcontent-xyr-c12 est un exemple d'attribut que rajoute Angular afin d'encapsuler le CSS propre au composant.
Encapsulation.ShadowDom
Ce système d'encapsulation utilise l'API Shadow Dom native du navigateur pour encapsuler le style du composant. Angular va donc créer un Shadow Root attaché à l'élément hôte du composant et l'utiliser pour encapsuler tous le style du composant.
Les différents sélecteurs CSS
Il existe trois grands sélecteurs CSS qui seront d'une grande importance pour styliser les composants:
- :host
- :host-context
- ::slotted
Les sélecteurs CSS :host et :host-context ne peuvent s'utiliser uniquement si l'encapsulation de votre composant se trouve dans le mode par défaut ou le mode shadow dom.
Le sélecteur CSS ::slotted ne peut s'utiliser que dans un mode shadow dom.
Le sélecteur :host
Lorsque l'on crée un composant, celui-ci est associé dans un élément qui correspond au sélecteur de ce composant. Cet élément dans lequel le template du composant est rendu est appelé l'élément hôte. Le sélecteur de pseudo-classe :host peut être utilisé pour créer des styles qui ciblent l'élément hôte lui-même, par opposition aux éléments ciblant l'intérieur de l'hôte.
Exemple
app-button {
width: 30%;
}
Dans ce cadre on souhaite sizer notre composant AppButtonComponent. On souhaite donc cibler l'élément hôte lui même. Pour parvenir à sizer correctement ce composant, il est nécessaire d'appeler le sélecteur :host dans le fichier de style qui lui est associé (button.component.css).
:host {
display: block;
width: 100%
}
Astuce: Par défaut un composant possède la propriété de style display: inline, si l'on souhaite sizer correctement notre composant, il faut setter cette propriété à block ou inline-block.
Attention: Tout style placé sur le sélecteur du composant overridera le style déclaré dans le :host
Pour atteindre des éléments enfants de manière plus précise, :host peut s'associer avec d'autres sélecteurs.
:host span {}
Ici on sélectionne tous les éléments span du composant AppButtonComponent
Le sélecteur :host-context
Parfois, il est utile d'appliquer du style en fonction du contexte où est appelé le composant.
Par exemple, si le composant AppButtonComponent est appelé dans un formulaire qui possède la classe 'disabled', alors celui-ci doit s'adapter à son contexte et appliquer un style particuler
Le sélecteur de pseudo-classe host-context permet de chercher parmis ses ancètres un sélecteur particulier (comme une classe)
:host-context(.disable) {
color: grey;
}
Dans l'exemple ci-dessus, si le composant est appelé dans un élément qui possède la classe .disable, alors la couleur du texte de notre composant sera en gris.
Bien évidemment, nous pouvons combiner les sélecteurs
:host-context(form.disable) {
color: grey;
}
Dans l'exemple ci-dessus, si le composant est appelé dans un élément form qui possède la classe .disable, alors la couleur du texte de notre composant sera en gris.
Le sélecteur ::slotted
Le sélecteur ::slotted, a pour objectif de cibler n'importe quel élément placé à l'intérieur d'une balise slot d'un template HTML.
Comme expliqué précédemment, ce sélecteur ne peut s'utiliser uniquement si le mode d'encapsulation du composant est Shadow Dom
Quand ce mode d'encapsulation est activé, notre composant Angular va se comporter exactement comme un Web Component. Cela signifie donc qu'il va utiliser les APIs natives du navigateur.
Dans le composant AppButtonComponent, on peut donc utiliser la "template spécification" et écrire
<button>
<slot></slot>
</button>
Dans ce cadre d'utilisation, le comportement de la balise slot aura le même comportement que la balise ng-content
Ce qui signifie que l'utilisation du composant AppButtonComponent se fera de la manière suivante:
<app-button>
Click me
</app-button>
Comment nous assurer que le contenu de notre bouton aura toujours le même style?
En utilisant le sélecteur ::slotted dans le style du composant AppButtonComponent
::slotted(*) {
color: red
}
Dans ce cas tout le contenu de la balise slot sera en couleur rouge et donc la couleur du texte 'Click me' sera de même.
Evidemment comme avec les autres sélecteurs, on peut coupler les différents sélecteurs pour avoir une sélection plus précise.
::slotted(span.red) {
color: red
}
Dans ce cas, seule la balise span avec la classe red contenue dans le slot aura la couleur rouge.
Les variables CSS
Les variables CSS, appelées également custom properties en anglais, sont des entités définies par les développeurs ou les utilisateurs d'une page web. Ces variables contiennent des valeurs spécifiques utilisables à travers tout le document, sont définies avec la notation suivante --my_variable: value et peuvent être accessibles grâce au mot clé var(--my_variable, defaultValue).
Ces variables CSS peuvent être très utiles quand on souhaite customiser une propriété en fonction d'un environnement précis.
Pour passer une variable CSS à un composant custom, ici le composant AppButtonComponent, il suffit de la déclarer sur le sélecteur du composant AppButtonComponent
app-button {
--text-color: green;
}
Une fois initialisé, dans le fichier CSS du composant AppButtonComponent, il suffit d'y accéder grâce au mot clé var
:host {
color: var(--text-color, blue);
}
Conclusion
Les sélecteurs de pseudo classes sont très puissants et permettent de modifier le style d'un composant custom rapidement et proprement.
Néanmoins si le style de votre composant a besoin de plus de dynamisme ou même si la logique de votre composant a besoin d'être étendu, il sera judicieux de partir sur une solution basée sur les directives.