Référence : https://angular.io/tutorial/first-app/first-app-lesson-02
Directive
Une directive a pour fonction d'ajouter un comportement aux éléments du DOM. Pour savoir à quels éléments la directive va s'appliquer, il faudra lui indiquer un sélecteur css (balise, class, attribut ou une combinaison de ces trois éléments).
A noter que les sélecteurs pour directive
- ne peuvent pas utiliser la descendance ni les pseudos-sélecteur à l'exception de ":not"
- utilisent très souvent des attributs qui ne sont pas des attributs classiques du html mais des attributs créés par le développeur (<coo-card helloWord><coo-card> ou helloWord est un attribut parfaitement valable)
Il existe tout un ensemble de directive du coeur (core directive : ngFor, ngIf, ...) mais on peut également créer ses propres directives.
Cf :
- https://angular.io/api?type=directive
- https://angular.io/guide/attribute-directives
- https://angular-university.io/lesson/angular-host-binding
- https://github.com/angular-university/angular-course/blob/2-directives-finished/src/app/directives/highlighted.directive.ts
Pour déclarer une directive, il faudra utiliser le décorateur @Directive.
On peut utiliser angular cli pour créer sa propre directive :
ng generate directive highlight
@Input ou binding de directive
Pour qu'un composant parent puisse passer des données à un élément enfant, il faudra utiliser du "binding de propriété". Dans le cadre de la création de sa propre directive, il faudra utiliser soit :
- l'attribut "inputs" du décorateur @Directive sous la forme d'un tableau de chaînes de caractères sous la forme "propriété: binding".
- le décorateur @Input. C'est maintenant la méhode recommandée.
Exemple :
@Directive({ selector: '[myCustomAttribute]', standalone: true }) export class customDecoratorDirective { @Input('otherCustomAttribute') msg = ''; }
Dans l'exemple ci-dessus, la directive va s'appliquer aux éléments du dom ayant l'attribut myCustomAttribute. Si jamais ces éléments possèdent également l'attribut otherCustomAttribute, alors ce dernier sera "bindé" à la propriété msg qui par défaut est égale à la chaîne de caractères vides.
@Output ou événements custom
Avec Angular, les composants enfants récupèrent des données via le binding et les inputs comme on vient de le voir et les composants enfants envoient des données vers les composants parents via les événements customs et les outputs.
La façon la plus classique de créer des événements custom se décompose en 3 étapes :
- Déclarer une propriété de type gestionnaire d'événement grâce à la classe EventEmitter et l'utilisation du décorateur @OutPut
- Gérer les événements de manière classique sur le template qui appelle une méthode de votre composant
- La méthode gestionnaire d'événement du composant "émet" une instance de l'événement custom via la méthode "emit()"
Exemple :
@Component({ selector: 'zippy', template: ` <div class="zippy"> <div (click)="toggle()">Toggle</div> <div [hidden]="!visible"> <ng-content></ng-content> </div> </div>`}) export class Zippy { visible: boolean = true; @Output() open: EventEmitter<any> = new EventEmitter(); @Output() close: EventEmitter<any> = new EventEmitter(); toggle() { this.visible = !this.visible; if (this.visible) { this.open.emit(null); } else { this.close.emit(null); } } }
Dans le code ci-dessus, on voit que le click sur la div ayant pour texte "Toggle" va appeler le gestionnaire d'événement "toggle()" qui va lui même emettre un événement custom (open ou close).
Component classique intégré dans un module
Les composants sont le bloc de construction d'interface utilisateur. Componenent hérite de la classe Directive. Une application Angular contient une arborescence de composants Angular. Autrement dit, les composants s'organisent de façon hiérarchique. Le composant racine a des composants enfants qui ont eux-même des composants enfants... Un composant est une partie isolée de l'application et l'application est un composant comme les autres.
Les composants Angular sont un sous-ensemble de directives, toujours associées à une vue (template) en ligne ou dans un fichier séparé.
Un composant doit appartenir à un NgModule (sauf s'il est autonome) pour être disponible pour un autre composant ou une autre application. Pour en faire un membre d'un NgModule, répertoriez-le dans le champ des déclarations des métadonnées du NgModule.
Signature d'un décorateur component
(alias) Component(obj: Component): TypeDecorator
Décorateur qui marque une classe en tant que composant Angular et fournit des métadonnées de configuration qui déterminent comment le composant doit être traité, instancié et utilisé lors de l'exécution.
Exemple de fichier app.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'my-first-project'; }
A la lecture de ce fichier, on comprend qu'un component comprend plusieurs propriétés :
- "selector" : Un sélecteur demande à Angular d'instancier ce composant partout où il trouve la balise correspondante dans le modèle HTML. Dans notre exemple, le sélecteur demande à Angular d'instancier ce composant chaque fois que la balise <app-root> apparaît dans un modèle. Finalent app-root devient un nouveau nom de balise (cf custom element)
- "templateUrl" indique le chemin qui conduit au template qui gère l'affichage de ce component
- "styleUrls" indique le chemin de la feuille de style qui régit le style de ce component
Enfin la classe est exportée et on a déclaré et assigné la propriété "title" qui pourra être utilisée dans le template (la vue).
Module
Fichier app.module.ts
@NgModule sert à organiser ensemble les différents éléments du module.
Un NgModule est une classe marquée par le décorateur @NgModule. Elle prend un objet de métadonnées qui décrit comment compiler le modèle d'un composant et comment créer un injecteur au moment de l'exécution. Il identifie les propres composants, directives et canaux du module, en rendant certains d'entre eux publics, via la propriété exports, afin que les composants externes puissent les utiliser. @NgModule peut également ajouter des fournisseurs de services aux injecteurs de dépendance de l'application.
import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Explications sur les propriétés de l'objet de métadonnées :
- declarations : un tableau qui comprend toutes les directives (et donc composants) qui seront utilisés dans ce module.
- imports : l'imports d'autres modules ou de component "standalone"
- providers : définit les injections de dépendance. La plupart du temps, ces dépendances sont des services que vous créez et fournissez notamment pour aller chercher des données
- bootstrap : définit la directive (ici le composant) qui sera appelé en premier pour l'ammorçage.
Modification de la feuille de style globale
Vous pouvez constater, à la lecture du fichier "angular.json" que la feuille de style globale de l'application a pour chemin "src/styles.css". Ajouter dans ce fichier le code suivant :
/* You can add global styles to this file, and also import other style files */ * { margin: 0; padding: 0; } body { font-family: 'Be Vietnam Pro', sans-serif; } :root { --primary-color: #605DC8; --secondary-color: #8B89E6; --accent-color: #e8e7fa; --shadow-color: #E8E8E8; } button.primary { padding: 10px; border: solid 1px var(--primary-color); background: var(--primary-color); color: white; border-radius: 8px; }
Modification de la feuille de style du component "app"
Vous pouvez constater, à la lecture du fichier "app.component.ts" que la feuille de style spécifique à ce component a pour chemin "./app/app.component.css". Pour une meilleure organisation de votre code, couper les css qui se trouvent dans le fichier "app.component.html" et coller (supprimer la balise <style></style>) le dans le fichier app.component.css. Profitez-en pour ajouter une propriété
margin-top: 40px;
pour le selecteur .card.highlight-card
Création d'un nouveau Component standolone "Home"
Entrer la commande ci-dessous depuis la console en vous plaçant dans le répertoire racine de votre application
ng generate component Home --standalone --inline-template --skip-tests
Vous constatez que :
- le répertoire "home" a été créé dans le répertoire "app"
- ce répertoire "home" contient deux fichiers :
- home.component.ts
Vous retrouverez la même structure de fichier que app.component.ts mis à part :- qu'il n'y a pas de template,
- que ce composant est "standalone",
- home.component.css
- home.component.ts
1.1 - Ajout du nouveau component au schéma (layout) de l'application
Dans src/app/app.module.ts, importer HomeComponent en ajoutant deux lignes (lignes 4 et 11) :
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; import { HomeComponent } from './home/home.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, HomeComponent ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Dans src/app/app.component.hml, utiliser la balise custom là où vous souhaitez voir afficher votre composant, par exemple en premier fils de la balise div qui a pour class "content" et pour role "main" :
<app-home></app-home>
Ajout d'un formulaire dans le component "Home"
Dans src/app/home/home.components.ts, dans @Component, mettre à jour la propriété "template" comme indiqué ci-dessous :
template: ` <section> <form> <input type="text" placeholder="Filter by city"> <button class="primary" type="button">Search</button> </form> </section> `,
Ajout des css pour ce formulaire
Dans src/app/home/home.components.css, ajouter le code suivant :
.results { display: grid; column-gap: 14px; row-gap: 14px; grid-template-columns: repeat(auto-fill, minmax(400px, 400px)); margin-top: 50px; justify-content: space-around; } input[type="text"] { border: solid 1px var(--primary-color); padding: 10px; border-radius: 8px; margin-right: 4px; display: inline-block; width: 30%; } button { padding: 10px; border: solid 1px var(--primary-color); background: var(--primary-color); color: white; border-radius: 8px; } @media (min-width: 500px) and (max-width: 768px) { .results { grid-template-columns: repeat(2, 1fr); } input[type="text"] { width: 70%; } } @media (max-width: 499px) { .results { grid-template-columns: 1fr; } }
Création d'un nouveau component "HousingLocation"
ng generate component HousingLocation --standalone --inline-template --skip-tests
Etant donné que nous allons utiliser le component "HousingLocation" au sein du component "Home", il faudra l'importer dans le fichier src/app/home/home.component.ts :
import { HousingLocationComponent } from '../housing-location/housing-location.component';
Ajouter HousingLocationComponent au tableau de la propriété imports de la directive @component toujours dans le fichier home.component.ts :
imports: [CommonModule, HousingLocationComponent],
enfin remplacer la propriété template dans ce même fichier :
template: ` <section> <form> <input type="text" placeholder="Filter by city"> <button class="primary" type="button">Search</button> </form> </section> <section class="results"> <app-housing-location></app-housing-location> </section> `,
CSS
Comme vous l'avez maintenant compris, on peut ajouter des css dans le fichier housing-location.component.css :
.listing { background: var(--accent-color); border-radius: 30px; padding-bottom: 30px; } .listing-heading { color: var(--primary-color); padding: 10px 20px 0 20px; } .listing-photo { height: 250px; width: 100%; object-fit: cover; border-radius: 30px 30px 0 0; } .listing-location { padding: 10px 20px 20px 20px; } .listing-location::before { content: url("/assets/location-pin.svg") / ""; } section.listing a { padding-left: 20px; text-decoration: none; color: var(--primary-color); } section.listing a::after { content: "\203A"; margin-left: 5px; }
Import du décorateur Input
Le décorateur @Input permet de définir une valeur d'entrée d'un composant. Cet input pourra être de n'importe quel type (object, string...) etsera repris dans un template
Modification de src/app/housing-location/housing-location.component.ts
import { Component, Input } from '@angular/core'; import { CommonModule } from '@angular/common'; import { HousingLocation } from '../housinglocation';// l'interface @Component({ selector: 'app-housing-location', standalone: true, imports: [CommonModule], template: ` <section class="listing"> <img class="listing-photo" [src]="housingLocation.photo" alt="Exterior photo of {{ housingLocation.name }}" /> <h2 class="listing-heading">{{ housingLocation.name }}</h2> <p class="listing-location"> {{ housingLocation.city }}, {{ housingLocation.state }} </p> </section> `, styleUrls: ['./housing-location.component.css'], }) export class HousingLocationComponent { @Input() housingLocation!: HousingLocation; }
Le ! rend obligatoire la transmission d'une valeur. Dans ce cas, il n'y a pas de valeur par défaut. Le point d'exclamation est appelé opérateur d'assertion non nul et indique au compilateur TypeScript que la valeur de cette propriété ne sera ni nulle ni indéfinie.