Crea un albero a selezione multipla nidificato con in angolare: parte 2
In questa serie di tutorial sullo sviluppo web, stiamo creando un’alternativa all’HTML SELEZIONARE controllo più adatto alla visualizzazione dei dati gerarchici. Nel ultima rata, abbiamo appreso i diversi tipi di MatTrees in Angular e quindi configurato un Nested Tree per mostrare tre livelli di informazioni sul veicolo: produttore, modello e livello di allestimento. Oggi incorporeremo le caselle di controllo nel nostro albero in modo che l’utente possa selezionare gli elementi.
Come impostare il nodo padre di TreeControl
Il TreeControl gestisce solo l’espansione e il collasso dei nodi e non gli stati delle caselle di controllo. Per questo motivo, dobbiamo tenere traccia dei figli e dei genitori di ciascun nodo. Per fare ciò, aggiungeremo alcune proprietà opzionali al Nodo Veicolo interfaccia – vale a dire selezionato, indeterminato, e genitore:
interface VehicleNode { name: string; id?: number; children?: VehicleNode[]; selected?: boolean; indeterminate?: boolean; parent?: VehicleNode; }
A rigor di termini, non c’è motivo per cui questi debbano essere parte dell’interfaccia del nodo, ma è molto più semplice che memorizzarli separatamente, il che richiederebbe il collegamento ai nodi associati.
I bambini sono già membri dell’originale DATI_ALBERO array, ma i genitori dei nodi non sono stati impostati. Dovremmo farlo il prima possibile, quindi il costruttore è il posto perfetto per questo:
constructor() { this.dataSource.data = TREE_DATA; Object.keys(this.dataSource.data).forEach(key => { this.setParent(this. dataSource.data[key]); }); } setParent(node: VehicleNode, parent: VehicleNode = null) { node.parent = parent; if (node.children) { node.children.forEach( childNode => { this.setParent(childNode, node); }); } }
Il setParent() ha bisogno di invocarsi ricorsivamente per ogni figlio in modo che nessun nodo venga trascurato.
Leggi: Dieci modi per utilizzare Angular JS
Posizionamento della casella di controllo nell’HTML
Questo sarebbe un momento opportuno per visualizzare il file modello. Pianificheremo di inserire le caselle di controllo appena prima del nome del nodo, quindi vedi se riesci a individuarle di seguito:
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl" class="example-tree"> <!-- This is the tree node template for leaf nodes --> <!-- There is inline padding applied to this node using styles. --> <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle> {{node.name}} </mat-tree-node> <!-- This is the tree node template for expandable nodes --> <mat-nested-tree-node *matTreeNodeDef="let node; when: hasChild"> <div class="mat-tree-node"> <button mat-icon-button matTreeNodeToggle [attr.aria-label]="'Toggle ' + node.name"> <mat-icon class="mat-icon-rtl-mirror"> {{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}} </mat-icon> </button> {{node.name}} </div> <!-- There is inline padding applied to this div using styles. This padding value depends on the mat-icon-button width. --> <div [class.example-tree-invisible]="!treeControl.isExpanded( node)" role="group"> <ng-container matTreeNodeOutlet></ng- container> </div> </mat-nested-tree-node> </mat-tree>
Usando il pronome “loro” sopra, suggerivo che ci sarebbero stati due nomi di nodo: uno per i nodi espandibili e un altro per i nodi foglia. Esistono due tipi perché i nodi espandibili includono un pulsante per espandere e comprimere le sezioni dell’albero. Il pulsante è designato utilizzando il matTreeNodeToggle direttiva.
Per distinguere tra i nodi radice e foglia, MatTree si basa sul hasChild() metodo che dobbiamo fornire. Viene fornito con due argomenti: the indice del nodo e oggetto nodo. Ecco la nostra implementazione:
hasChild = (_: number, node: VehicleNode) => !!node.children && node.children.length > 0;
Dopo aver inserito le caselle di controllo all’interno degli elementi del nodo dell’albero, il markup aggiornato sarà simile a questo:
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl" class="example-tree"> <!-- This is the tree node template for leaf nodes --> <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle> <li class="mat-tree-node"> <mat-checkbox class="checklist-leaf-node" (change)="itemToggle($event.checked, node)" [checked]="node.selected">{{no de.name}}</mat-checkbox> </li> </mat-tree-node> <!-- This is the tree node template for expandable nodes --> <mat-nested-tree-node *matTreeNodeDef="let node; when: hasChild"> <li> <div class="mat-tree-node"> <button mat-icon-button matTreeNodeToggle [attr.aria-label]="'toggle ' + node.name"> <mat-icon class="mat-icon-rtl-mirror"> {{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}} </mat-icon> </button> <mat-checkbox [checked]="node.selected" [indeterminate]="node. indeterminate && !node.selected" (change)="itemToggle($event. checked, node)">{{node.name}} </mat-checkbox> </div> <ul [class.example-tree-invisible] ="!treeControl.isExpanded( node)"> <ng-container matTreeNodeOutlet></ng- container> </ul> </li> </mat-nested-tree-node> </mat-tree>
Con tutto a posto, non resta che aggiungerne un po’ CSS per mettere a punto le immagini:
.example-tree-invisible { display: none; } .example-tree ul, .example-tree li { margin-top: 0; margin-bottom: 0; list-style-type: none; } .checklist-leaf-node { padding-left: 40px; }
Leggi: Crea un MatMenu con le caselle di controllo
Come tenere traccia degli stati delle caselle di controllo
Se nessuno tiene traccia delle caselle di controllo selezionate, l’applicazione ti consentirà di attivare le caselle di controllo, ma non manterrà gli stati gerarchici che ti aspetteresti da un albero. Le regole per gli stati delle caselle di controllo sono le seguenti:
- Facendo clic su un nodo espandibile si dovrebbero selezionare e deselezionare tutti i relativi nodi figlio di conseguenza.
- Facendo clic su un nodo figlio, espandibile o foglia, la casella di controllo del nodo padre dovrebbe diventare indeterminata (se ci sono alcuni fratelli non selezionati) o selezionata (se anche tutti i fratelli sono selezionati).
Nel modello, l’evento di modifica della casella di controllo è associato a voceToggle() metodo. Riceve due parametri: se la casella di controllo è selezionata o meno e il nodo. Lì, tracciamo lo stato selezionato del nodo e procediamo a fare lo stesso per tutti i suoi figli (discendenti) e genitori (antenati):
private itemToggle(checked: boolean, node: VehicleNode) { node.selected = checked; if (node.children) { node.children.forEach(child => { this.itemToggle(checked, child); }); } this.checkAllParents(node); }
Il checkAllParents() ha il compito extra di impostare il indeterminato attributo oltre a selezionato. Per farlo chiama l’array alcuni() e ogni() funzioni rispettivamente. Il primo restituisce true se è selezionato almeno un nodo figlio, mentre il secondo richiede che tutti i nodi figlio siano selezionati. I discendenti del nodo genitore vengono recuperati per gentile concessione di treeControl’s getDescendants() metodo. Nota che il checkAllParents() Il metodo deve chiamare se stesso in modo ricorsivo per farsi strada fino in cima alla gerarchia:
private checkAllParents(node: VehicleNode) { if (node.parent) { const descendants = this.treeControl.getDescendants(node.parent); node.parent.selected = descendants.every(child => child.selected); node.parent.indeterminate = descendants.some(child => child.selected); this.checkAllParents(node. parent); } }
Recupero dei nodi selezionati
Dopo aver affrontato il problema di rendere selezionabili i nodi, ha senso solo fare qualcosa con loro. In questo caso, posizioneremo un pulsante nella parte inferiore del modulo che visualizza i nodi selezionati:
<button class="submit-button" mat-raised-button (click)="submit()">Submit</button> <p #outputDiv></p>
Nel Invia() metodo, possiamo utilizzare l’incredibile Array.riduci() funzione per eseguire l’iterazione su tutti i nodi radice, recuperare i loro discendenti e mappare i nomi dei nodi selezionati al nostro array di risultati:
@ViewChild('outputDiv', {static: false}) public outputDivRef: ElementRef<HTMLParagraphElement>; public submit() { let result = this.dataSource.data.reduce( (acc: string[], node: VehicleNode) => acc.concat(this.treeControl .getDescendants(node) .filter(descendant => descendant.selected) .map(descendant => descendant.name)) , [] as string[]); this.outputDivRef. nativeElement.innerText="You " + (result.length > 0 ? 'selected ' + result.join(', ') : 'have not made a selection') + '.'; }
Ecco un esempio di output:
Come sempre, c’è una demo dell’albero a selezione multipla nidificato su stackblitz.
Andare avanti con MatTree e Angular
Finora in questa serie, abbiamo creato un MatTree nidificato e lo abbiamo trasformato in un controllo a selezione multipla aggiungendo caselle di controllo. Nella prossima e ultima puntata, apriremo l’albero all’interno di un MatMenu come sostituto di MatSelect.
Leggi: Disegnare pulsanti di opzione e caselle di controllo nei moduli HTML