In questo articolo, esploreremo come implementare un textbox numerico in Angular utilizzando Kendo UI e TypeScript, con un focus sulla validazione e la gestione dell'input dell'utente. Partiremo dalle basi di Angular, per poi addentrarci nell'implementazione pratica di una direttiva per un textbox numerico personalizzato.
Introduzione ad Angular e TypeScript
Prima di immergerci nel codice, è importante avere una solida comprensione di Angular e TypeScript.
Angular è un framework per la costruzione di applicazioni web lato client (frontend). È un framework completo, dotato di un'interfaccia a riga di comando (CLI) che semplifica lo sviluppo, il testing e il deployment delle applicazioni.
TypeScript, d'altra parte, è un superset di JavaScript. Ciò significa che TypeScript aggiunge funzionalità a JavaScript, come la tipizzazione statica e le classi, rendendo il codice più manutenibile e scalabile. TypeScript è un linguaggio di programmazione che estende il linguaggio JavaScript, scritto e mantenuto da Microsoft.
Angular vs. AngularJS
È comune sentir nominare Angular e AngularJS. Ma qual è la differenza tra i due? AngularJS è l'antenato di Angular, che ne rappresenta una completa riscrittura in TypeScript. La differenza più ovvia è che AngularJS era scritto in linguaggio JavaScript puro, mentre Angular è scritto in TypeScript.
Leggi anche: "Il cosmo sul comò": Recensione
Angular vs. React e Vue.js
A differenza di Angular, React non si può definire un framework frontend vero e proprio, ma una libreria di render UI basata su componenti. Vue.js è un framework frontend scritto in linguaggio JavaScript e nato come una versione più leggera e semplice di Angular.
Strumenti e Setup Iniziale
Per iniziare a sviluppare con Angular, avrai bisogno di:
- Node.js e npm: Angular richiede Node.js per l'esecuzione e npm (Node Package Manager) per la gestione delle dipendenze.
- Angular CLI: L'interfaccia a riga di comando di Angular (CLI) è uno strumento potente per creare, sviluppare e gestire progetti Angular. Con Angular 20, la CLI supporta ora Node.js versioni 18.19.0 o successive.
Per installare Angular CLI, esegui il seguente comando nel tuo terminale:
npm install -g @angular/cliUna volta installato, puoi creare un nuovo progetto Angular con il comando:
ng new nome-progettoNaviga nella cartella del progetto appena creato:
Leggi anche: Come usare il Telecomando Kendo
cd nome-progettoPer avviare il server di sviluppo, esegui:
ng serveNormalmente passiamo da npm per eseguire gli script del progetto, perché i tool di sviluppo di ogni progetto Node sono installati localmente; con Angular, avendo il comando ng disponibile globalmente, è comune scavalcare npm e chiamare direttamente ng serve.
Direttive in Angular
Iniziamo dando una definizione formale di direttiva: una direttiva Angular è una classe TypeScript che implementa un comportamento su un elemento del DOM. Le direttive permettono di manipolare il DOM (Document Object Model) e aggiungere comportamenti personalizzati agli elementi HTML. Esistono tre tipi principali di direttive:
- Componenti: Sono direttive con un template HTML associato.
- Strutturali: Modificano la struttura del DOM, aggiungendo o rimuovendo elementi.
- Attributi: Modificano l'aspetto o il comportamento di un elemento esistente. Si tratta di direttive che vengono applicate attraverso degli attributi sui tag HTML; in genere, quando si parla semplicemente di direttiva, si fa riferimento a questo tipo.
Implementazione di un Textbox Numerico
Immaginiamo di avere nel nostro template app.component.html un form con dei campi di input che, pur essendo di tipo text, devono accettare solo valori numerici.
Possiamo implementare questa funzionalità in diversi modi:
Leggi anche: Frittelle tradizionali e moderne
1. Utilizzo di pattern e onkeypress
L'attributo pattern e il metodo match accettano come argomento una regular expression (espressione regolare). Un approccio semplice è utilizzare l'attributo pattern HTML5 insieme a un gestore di eventi onkeypress per filtrare l'input non numerico.
<input type="text" pattern="[0-9]*" onkeypress="return event.charCode >= 48 && event.charCode <= 57">Vediamo di capire bene che cosa succede qui: abbiamo un normalissimo input di tipo text con un evento onkeypress in cui ritorniamo il risultato di un'espressione che ci dice se il tasto premuto corrisponde o meno ad una cifra numerica.
Questo approccio funziona, ma ha dei limiti:
- Richiede l'aggiunta di codice JavaScript inline, che può rendere il codice meno manutenibile.
- Non fornisce un feedback visivo all'utente in caso di input non valido.
2. Creazione di una Direttiva Personalizzata
Un approccio più strutturato è creare una direttiva personalizzata. Questo ci permette di incapsulare la logica di validazione e riutilizzarla in diversi punti dell'applicazione.
Creazione della Direttiva
Esegui il seguente comando per generare una nuova direttiva:
ng generate directive numeric-textboxQuesto creerà un file numeric-textbox.directive.ts nella cartella src/app.
Implementazione della Direttiva
Apri il file numeric-textbox.directive.ts e aggiungi il seguente codice:
import { Directive, HostListener, ElementRef } from '@angular/core';@Directive({ selector: '[appNumericTextbox]'})export class NumericTextboxDirective { constructor(private el: ElementRef) { } @HostListener('input', ['event']) onInputChange(event: any) { const initalValue = this.el.nativeElement.value; this.el.nativeElement.value = initalValue.replace(/[^0-9]*/g, ''); if ( initalValue !== this.el.nativeElement.value) { event.stopPropagation(); } }}In questo codice:
- Abbiamo importato
Directive,HostListenereElementRefda@angular/core. - Abbiamo definito un selettore
[appNumericTextbox]che ci permette di applicare la direttiva a un elemento HTML utilizzando l'attributoappNumericTextbox. - Abbiamo utilizzato
HostListenerper ascoltare l'eventoinputsull'elemento. - All'interno del gestore dell'evento, abbiamo utilizzato una regular expression per rimuovere tutti i caratteri non numerici dall'input.
Utilizzo della Direttiva
Per utilizzare la direttiva, aggiungi l'attributo appNumericTextbox a un elemento input nel tuo template:
<input type="text" appNumericTextbox>3. Blocco Procedurale dell'Evento keypress
Abbiamo scritto un metodo rejectNonNumeric che blocca l'effetto dell'evento keypress secondo lo stesso criterio di prima; in questo caso, il blocco non avviene restituendo false in caso di mismatch, ma invece bloccando proceduralmente gli effetti dell'evento.
Feedback Visivo: Segnalazione di Errori
Andiamo a implementare una nuova direttiva che alla pressione di un carattere non numerico modifichi lo stile dell'elemento per segnalare l'errore. Possiamo migliorare l'esperienza utente fornendo un feedback visivo quando l'utente inserisce un input non valido. Ad esempio, possiamo cambiare il colore del bordo dell'input o visualizzare un messaggio di errore.
Template HTML e Data Binding
Come abbiamo anticipato, il contenuto grafico del nostro componente è rappresentato dal suo template HTML. In questo caso, nel momento in cui mettiamo l'attributo value tra parentesi quadre, ammettiamo come valore qualunque dato proveniente dalla component class.
Tendenzialmente il flusso di dati all'interno di un albero di componenti è unidirezionale, dall'alto verso il basso; la comunicazione a ritroso non si fa dunque passando dati, ma scatenando eventi.
Direttive Strutturali (Deprecate in Angular 20)
Attenzione: le seguenti direttive strutturali *ngIf, *ngSwitch e *ngFor sono ora DEPRECATE in Angular 20.
Le direttive strutturali modificano la struttura del DOM. Esempi comuni includono *ngIf (per la visualizzazione condizionale), *ngFor (per l'iterazione su una lista) e *ngSwitch (per la selezione di un blocco di codice basato su un valore).
Solitamente questa direttiva si applica direttamente sul tag coinvolto, ma è comunque possibile usare ng-container qualora la porzione di template condizionale includa più di un nodo del DOM.
L'ultima direttiva strutturale che vedremo, anch'essa ormai sostituita, è ngFor.
Comunicazione tra Componenti e Servizi
Abbiamo, finora, visto come in un template sia possibile passare dati e registrare eventi e, dunque, operare a doppio senso nell'alberatura dei componenti. Con le sintassi viste fino ad ora, non sarebbe possibile implementare un simile componente.
In questo modo il nostro counter.service.ts viene creato direttamente nella cartella del counter component. La nostra classe componente ora non fa altro che richiedere come argomento al costruttore un'istanza di CounterService. Angular prenderà nota di questa dipendenza e farà in modo che sia soddisfatta.
Apparentemente la nostra applicazione è diventata più complessa, senza però cambiare di un millimetro. È importante notare che in casi più concreti, il componente contatore che presenta e aggiorna lo stato dell'applicazione, non sarebbe verosimilmente vicino a tutti gli altri punti dell'app in cui lo stato dev'essere disponibile. Avere un'architettura che consente di disaccoppiare dati e funzioni rispetto alla loro collocazione nell'albero dei componenti offre un livello di flessibilità irraggiungibile senza servizi.
HttpClient e Observable
L'idea dietro questa classe è quella di racchiudere le API native per le comunicazioni HTTP (es. fetch) dietro a un servizio completamente integrato in Angular in modo da potervi aggiungere altre funzionalità attraverso il framework. Un aspetto da considerare nell'uso di HttpClient, è che tutti i suoi metodi implementano risposte asincrone attraverso l'uso del tipo Observable, al posto del nativo Promise.
Qui vediamo come il risultato di this.http.get() sia accessibile passando una callback al metodo subscribe, esattamente come avviene con Promise.then.
L'altra novità di questo esempio è il metodo ngOnInit che implementa l'interfaccia OnInit; questo metodo è un cosiddetto lifecycle hook, letteralmente un gancio al ciclo di vita del componente. Gli hook più utilizzati sono OnInit e OnDestroy, ma ce ne sono altri, specifici per diverse casistiche.
Ovviamente, poiché Observable non è un tipo nativo, non è possibile sfruttare async/await direttamente; una buona notizia è che gli Observable restituiti dai metodi di HttpClient possono essere facilmente convertiti in Promise; un'altra buona notizia è che nel 90% dei casi, questa conversione non sarà necessaria, grazie all'utility AsyncPipe.
Quello che abbiamo fatto essenzialmente, è cambiare il tipo della property data, da string a Observable<string>, cioè il tipo restituito da http.get(...).pipe(map(...)): qui, l'uso di pipe e map corrisponde ancora a Promise.then, non nel caso in cui venga usato per estrarre un valore, bensì per mutare il contenuto della promise restituendo un'altra promise.
Una cosa importante da capire sugli observables è che sono completamente passivi fino a che qualcuno chiama il loro metodo subscribe; dunque come fa la web app a mostrare i dati in questo caso? Chi chiama la subscribe?
Pipes
Come vedremo a brevissimo, le pipes sono funzioni che possono essere utilizzate nei template per trasformare le espressioni usate nei binding. In questo caso non possiamo apprezzarlo, ma la async pipe fa anche una terza cosa: annulla la subscribe quando il ciclo di vita del componente termina.
Nella precedente sezione di questa guida abbiamo visto come la AsyncPipe ci permette di estrarre il valore di un Observable in modo da renderlo visibile nel template senza gestirlo manualmente lato codice. Rispetto al codice preesistente, è evidente che ci siamo semplificati un po' la vita: operare sugli Observable può essere difficile, ma estraendo prima il valore con | async (si legge "pipe async"), diventa tutto più facile.
Benché il metodo Observable.pipe e le pipe di Angular non siano la stessa cosa, la loro omonimia non è un caso. Ebbene sì, Angular implementa le pipe sotto forma di classi ma le espone al template sotto forma di funzioni che si concatenano con l'operatore |. Grazie a questa astrazione, in una pipe è possibile iniettare dei servizi via DI, ma anche conservare uno stato e gestire il ciclo di vita, come fa AsyncPipe.
Le ultime pipe che abbiamo visto sono cosiddette pure, nel senso che il loro output dipende esclusivamente dal loro input. Ma noi abbiamo appena detto che una pipe class può avere uno stato, perciò questo potrebbe influenzare l'output: ecco che subentrano le pipe impure.
Routing e Navigazione
Come altri framework frontend, Angular mira a colmare questo divario realizzando le cosiddette applicazioni a pagina singola (single page application, o SPA); questa definizione vale per tutti quei siti web che implementano una navigazione a frontend e quindi non richiedono mai il ricaricamento della pagina mentre vengono utilizzati.
In ogni applicazione Angular dotata di navigazione esiste un'unica istanza (singleton) della classe Router; il router espone metodi che consentono la navigazione ed eventi che possono essere intercettati per reagire alla navigazione.
Anche il CommonModule importato in precedenza è un NgModule! Il router module è configurato in app.config.ts tramite la funzione provideRouter che accetta come argomento la configurazione delle rotte di navigazione.
Siamo pronti per vedere un semplice esempio. Costruiamo un'app che contiene una generica pagina elenco e una pagina dettaglio; dagli elementi dell'elenco si navigherà verso i dettagli passando alla rotta un parametro identificativo.
La direttiva routerLink è molto importante perché implementa lo stesso comportamento che avremmo utilizzando l'attributo href, salvo il fatto che la navigazione avviene tramite il Router, che è una sua dipendenza interna.
Nel nostro DetailComponent vogliamo mostrare l'identificativo ricevuto attraverso il percorso di navigazione; per farlo dobbiamo introdurre una nuova classe injectable: ActivatedRoute.
tags: #kendo #numeric #textbox #tutorial #italiano
