Comment styliser ses composants partagés avec Angular
Tags: angular, css, webdev, productivity
Published: 2022-05-17 09:40:11


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é.


![Picture to see how is create the attribute in DOM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zhw1kcrs4zuixm9z29m8.png)

__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.


![Picture to see how is shadow dom created in Angular](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nu6f0c1qp8ocs2j1y1xy.png)


## 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**

```css
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).

```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.

```css
: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)

```css
: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

```css
: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

```html
<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:

```html
<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

```css
::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.

```css
::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

```css
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**

```css
:host {
  color: var(--text-color, blue);
}
```

Dans le cas ci-dessus, la couleur du texte sera la valeur de la variable _text-color_ si elle est founie et si ce n'est pas le cas, la valeur de la propriété _color_ aura la valeur par défaut, dans ce cas ci: 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.