Molti linguaggi di programmazione moderni, come Java e Python, sono orientati agli oggetti (OO). Altri sono guidati dagli eventi. Angular, invece, essendo un Framework, segue un proprio modello, impiegando un’architettura basata su Component. Con ciò intendiamo che un’applicazione Angular è composta da molti blocchi riutilizzabili chiamati Components. Ognuno è composto da diversi file, che includono un file di classe .ts, un modello .html e forse un file di test .css e spec.ts. L’idea alla base dell’architettura basata su componenti di Angular è valida; costruendo pezzi riutilizzabili, le nostre applicazioni possono ridurre notevolmente la quantità di duplicazioni.
In molti casi, possiamo pianificare quali pezzi trasformare in componenti. Altre volte, ci rendiamo conto che avremo bisogno di funzionalità simili in alcuni punti della nostra applicazione. E questo è l’oggetto del tutorial: come estrarre il TypeScript, HTML e CSS che dobbiamo replicare e come dividerlo in compartimenti in un nuovo componente. A causa delle regole di ambito imposte da Angular, cosa lasciare dentro e cosa togliere può essere una domanda difficile a cui rispondere. Come caso di studio, prenderemo il menu a discesa personalizzato che abbiamo implementato nel Attivazione/disattivazione della visibilità degli elementi in Angular 11 con NgClass articolo e trasformandolo in un componente in modo da poterlo riutilizzare in seguito.
Scambio del menu a discesa esistente con il nostro nuovo componente
Di solito è meglio evitare di fare tutto in una volta; così facendo probabilmente si produrranno molti bug che saranno difficili da rintracciare. Invece, affronta il compito usando piccoli passi creando il tuo nuovo componente e sostituendo gli elementi esistenti con esso.
Usando l’Angular CLI, il comando sarebbe:
ng g c shared/components/dropdown-menu
Il “cg” è l’abbreviazione di “Generate Component”, quindi, finché sai cosa stai digitando, va bene usare la forma abbreviata. Il comando precedente posizionerà il nostro nuovo componente del menu a discesa in “srcappsharedcomponents
Con il nostro componente in posizione, siamo pronti per inserirlo nel nostro modello. Per fare ciò, commenteremo semplicemente il markup in questione e aggiungeremo il componente del menu a discesa:
<mat-toolbar color="primary"> <a [routerLink]="['/']">Home</a><a [routerLink]="['/survey']"> Survey</a> <div class="menu-container" (click)="menuOpened = true" (mouseenter)="menuOpened = true" (mouseleave)="menuOpened = false"> <span>Menu</span> <dropdown-menu [ngClass]="{ 'menu-opened': menuOpened }" class="dropdown-menu"></ dropdown-menu> <!-- <div [ngClass]="{ 'menu-opened': menuOpened }" class="dropdown-menu"> <a *ngFor="let ic of investmentClasses" (click)="onClick($event)"> <mat-icon mat-list-icon>{{ ic.icon }}</mat-icon> <span class="dropdown-menu-item">{{ ic.text }}</span> </a> </div> --> </div> </mat-toolbar> <div class="container"> <router-outlet></router- outlet> </div>
Per modellare il nostro nuovo componente come l’elemento o gli elementi che stiamo sostituendo, possiamo copiare la classe “menu a discesa”. Per impostazione predefinita, il menu a discesa ha una visualizzazione di “none”, quindi dovremmo anche spostarci su ngClass. Ciò cambierà la proprietà di visualizzazione in “flex” sull’evento hover del DIV contenitore di menu:
Comunicare con il nostro nuovo componente
La sfida più grande quando si introduce un nuovo componente in un modello è il passaggio di dati tra il componente padre e figlio. Nel nostro caso, i dati devono viaggiare in modo bidirezionale: il menu a discesa deve sapere quali elementi mostrare e il genitore deve essere informato della selezione dell’utente.
Iniziamo impostando le voci del menu a discesa.
In Angular, il modo per passare i dati direttamente a un componente è utilizzare un decoratore @Input(). Aggiungendo [menuItems]="
al tag del menu a discesa, la variabile di input menuItems del nostro componente sarà impostata sulle investmentClasses di AppComponent.
Nel DropdownMenuComponent, ora dobbiamo aggiungere la variabile menuItems:
export class DropdownMenuComponent { @Input() public menuItems: Array<MenuItem> = []; //... }
Vogliamo che i menuItems abbiano un formato molto specifico in modo che le proprietà non manchino nel modello. Per fare ciò, possiamo definire l’interfaccia MenuItem:
export interface MenuItem { icon: string; id: string; value: string, }
L’interfaccia MenuItem viene esportata da DropdownMenuComponent in modo che possa essere utilizzata da AppComponent (sebbene ciò non sia strettamente necessario, purché le proprietà dell’elemento corrispondano):
import { MenuItem } from "./shared/components/dropdown-menu/dropdown-menu.component"; export class AppComponent { public menuOpened = false; public investmentClasses: MenuItem[] = [ { icon: "euro_symbol", value: "currencies", id: "currency" }, //... ]; }
Notifica al genitore della selezione dell’utente
Proprio come il decoratore @Input() accetta dati dal componente genitore, l’analogo @Output() emette dati al genitore. Per questo motivo, deve essere istanziato in una nuova istanza di EventEmitter:
export class DropdownMenuComponent { @Input() public menuItems: Array<MenuItem> = []; @Output() public itemSelected = new EventEmitter<number>(); //... }
Ciò che scegli di emettere al genitore dipende dal formato dei dati. Poiché stiamo usando un Array, il modo più semplice per accedere a un elemento è tramite il suo indice numerico. Un modo semplice per farlo è aggiungerlo al ciclo ngFor. Ciò ci consente di passarlo al gestore di eventi onClick:
<a *ngFor="let mi of menuItems;let i = index" (click)="onClick($event, i)">
Ora possiamo emettere l’indice al genitore:
public onClick(event: MouseEvent, index: number) { event.stopPropagation(); this.itemSelected.emit(index); }
Nel frattempo, nel modello del genitore, possiamo aggiungere un gestore per l’elemento selezionato come segue:
(itemSelected)="onItemSelected($event)"
Ecco il codice per il metodo onItemSelected() che mostra come recuperare l’elemento investmentClasses pertinente:
public onItemSelected(index: number) { this.menuOpened = false; alert('Selected item: ' + this.investmentClasses[index].value); }
Disegnare il componente del menu a discesa
Per mantenere un corretto incapsulamento dei componenti, qualsiasi classe definita all’interno del modello del menu a discesa dovrebbe essere spostata nel relativo file .css:
a { text-decoration: none; display: inline-flex; cursor: pointer; align-items: center; padding: 0 0.8rem; } a:not(:last-child) { border-bottom: 2px solid gray; } a:hover { background-color: #a12f42; } .dropdown-menu-item { display: block; margin: 1.33rem 0rem; font-weight: bold; }
La dimostrazione
Sopra codeandbox.io, troverai la demo con tutto il codice presentato in questo articolo.
Conclusione
Ogni volta che devi riutilizzare elementi HTML in pagine diverse, o anche all’interno della stessa pagina, è il momento di incapsulare la sua funzionalità all’interno di un componente personalizzato. Come abbiamo visto qui oggi, non è poi così difficile; devi solo adottare un approccio frammentario.