AngularJS mini How-To (prima parte)

Cosè AngularJS?

Angular è un framework Javascript, essenziale per le oramai sempre più diffuse single-page application (e anche per i più comuni siti web). AngularJS s’ispira al pattern MVC (Model View Controller), un paradigma architetturale utilizzato da molti linguaggi di programmazione che rende la struttura del codice più semplice e flessibile. Definiamone brevemente il significato:

  • Model: rappresenta la struttura dati che sta dietro un pezzo dell’applicazione. Solitamente i Model vendono definiti tramite oggetti JSON. Per esempio il modello di una lista di utenti potrebbe essere definito in questo modo:
    {
       "users":[
          {
             "name":"Mario Rossi",
             "id":"123"
          },
          {
             "name":"Luca Verdi",
             "id":"456"
          }
       ]
    }
    
  • View: si tratta delle componenti grafiche che vengono definite all’interno dei file HTML. Una volta associati agli elementi grafici i relativi campi del Model, Angular aggiornerà in automatico i dati mantenendo la consistenza tra le informazioni presenti nei Model e quelle visualizzate nelle View;
  • Controller: hanno lo scopo di controllare le interazioni dell’utente e gestire l’aggiornamento dei dati. In sintesi dovranno contenere l’intelligenza della nostra web-app.

Il primo passo con AngularJS

Per creare un progetto AngularJS è necessario scaricare preventivamente la libreria dal home page del progetto (https://angularjs.org/), oppure utilizzare la libreria tramite il CDN di Google inserendone il riferimento nella pagina HTML, come mostrato qui di seguito:

<!DOCTYPE html>
<html>
  <head lang="en">
      <meta charset="UTF-8">
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.js"></script>
  </head>
  <body>
  </body>
</html>

Fatto! Abbiamo configurato il necessario per iniziare ad utilizzare il framework. Introduciamo ora alcuni concetti di base, partendo dalla struttura minima di una pagina Angular.

<!DOCTYPE html>
<html ng-app>
<head lang="en">
    <meta charset="UTF-8">
    <title>My first AngularJS app</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.js"></script>
</head>
<body>
<p>13 + 12 = {{13+12}}</p>
</body>
</html>

Il risultato di questa prime righe sarà una pagina web con la scritta “12 + 13 = 25”. Iniziamo dunque ad analizzare gli elementi che compongono questa il file HTML. Per prima cosa notiamo l’attributo ng-app nel tag <html ng-app>; si tratta di una direttiva di Angular che indica al framework quale elemento del DOM dovrà utilizzare come radice per la nostra applicazione. In questo caso abbiamo utilizzato l’intera pagina ma, più in generale, avremmo potuto inserirlo all’interno del tag <body> o addirittura all’interno di un singolo <div>. Un altro elemento rilevante, che abbiamo in parte già accennato, è il caricamento della libreria. Questo, oltre ad importare gli oggetti definiti in AngularJS, avvia il processo di bootstrap. In sintesi, dopo il caricamento del documento HTML, viene effettuata la compilazione del DOM (ovvero della struttura ad albero che compone al pagina HTML) a partire dall’elemento marcato con l’attributo ng-app, elaborando ogni direttiva, binding ed espressione incontrati. Al termine della fase di bootstrap, AngularJS si mette in attesa del verificarsi di eventi sul browser. Infine, l’ultimo elemento rilevante è rappresentato dalla direttiva {{12+13}}. Si tratta di un comando che AngularJS è in grado di valutare e sostituire con il relativo risultato. La valutazione di tali comandi non avviene solo in fase di bootstrap ma è costante per tutto il tempo in cui la pagina web sarà visibile. Nello specifico viene creato un binding tra l’elemento grafico e una variabile del model; ogni volta che il valore della variabile cambia, il componente grafico viene aggiornato di conseguenza, e viceversa. Possiamo verificare questa affermazione osservando il risultato del prossimo esempio:

<!DOCTYPE html>
<html ng-app>
<head lang="en">
    <meta charset="UTF-8">
    <title>My first AngularJS app</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.js"></script>
</head>
La somma di
<input type="number" ng-model="addendo1" />
e
<input type="number" ng-model="addendo2" />
è
{{addendo1 + addendo2}}
</html>

In questo esempio sono state inserite due caselle di testo in cui l’utente può inserire due numeri che verranno sommati ed il cui risultato verrà stampato in tempo reale subito a lato. L’elemento chiave in questo caso è rappresentato dalla direttiva ng-model; questo si occupa di creare un binding bidirezionale tra ciascun campo di input e due variabili (in questo caso addendo1 e addendo2 definite all’interno del model. Le modifiche sui campi di input vengono sincronizzate con le relative variabili causando così la rivalutazione dell’espressione {{addendo1 + addendo2}} che stamperà il risultato dell’operazione. Abbiamo quindi definito un applicazione composta da una View e da un Model con due variabili, in grado di fare una semplice somma, senza scrivere nemmeno una riga di codice Javascript.

Creare applicazioni MVC

Vediamo ora come poter creare una single-page-application con delle view basate su dei modelli ad hoc che andremo a definire, e come introdurre dei controller che gestiscano le interazioni dell’utente con la nostra applicazione. Al fine di associare questi concetti ad un ambito quanto meno verosimile, andremo a creare un catalogo dei piloti iscritti al campionato mondiale di MotoGP 2014.

View

Innanzitutto definiamo la view che conterrà la lista dei piloti.

<!DOCTYPE html>
<html ng-app>
<head lang="en">
    <meta charset="UTF-8">
    <title>Moto GP riders</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.js"></script>
    <script src="controller.js"></script>

    <link rel="stylesheet" href="style.css"/>
</head>
<body ng-controller="mainController">
<ul class="rider-list">
    <li ng-repeat="rider in riders">
        <img ng-src="img/riders/{{rider.number}}.jpg" alt="{{rider.name}}"/>

        <div class="info">
            <p class="flag-n">
                <img alt="{{rider.nation}}" ng-src="img/nation/{{rider.nation}}.png">
                <span class="num">{{rider.number}}</span>
            </p>

            <p class="name">{{rider.name}}</p>
        </div>
    </li>
</ul>
</body>
</html>

Tralasciando la sezione head che contiene i vari riferimenti alle librerie e CSS, la parte interessante è contenuta all’interno del tag <body>, ovvero una lista di elementi, ognuno dei quali contiene alcune immagini del pilota (bandiera e foto), il nome ed il numero. Possiamo evidenziare alcune nuove direttive che istruiscono il framework su come generare la view:

  • La direttiva ng-repeat indica al sistema di generare tanti elementi <li> quanti sono i piloti contenuti nel modello. La sintassi utilizzata è la seguente:
    ng-repeat=”rider in riders”
    

    dove riders è il modello che contiene la lista di tutti i piloti iscritti al campionato mondiale di MotoGp, mentre rider rappresenta il pilota correntemente preso in considerazione all’interno di un singolo ciclo di iterazione;

  • La direttiva ng-src svolge la stessa funzione del attributo src. Anche se apparentemente il risultato è il medesimo, tale direttiva fa si che l’espressione {{rider.number}} venga valutata prima di importare l’immagine; in caso contrario l’espressione sarebbe stata considerata come nome associato al file JPEG puntando quindi ad url inesistente.
  • La direttiva ng-controller indica qual è il controller associato all’elemento, ovvero chi dovrà gestire le interazioni che si verificheranno all’interno del tag <body>

Controller/Model

Passiamo ora ad analizzare quanto scritto nel file controller.js, il quale, come intuibile dal nome, conterrà il codice necessario alla definizione del controller:

var riders = [
	{
    	name: "Valentino Rossi",
    	number: 46,
    	team: "Movistar Yamaha MotoGP",
    	nation: 'ita',
    	height: 182,
    	weight: 65,
    	city: "Urbino"
	},
	...
]

var mainController = function ($scope) {
	$scope.riders = riders;
}

Il modello per la nostra single page application è formato da un oggetto JSON che contiene la lista dei piloti (per compattezza ne è stato riportato solo il primo), per ognuno dei quali viene fornito il nome,il numero di gara, i team associato ed altre informazioni. Per quanto riguarda il controller, viene definito nella funzione subito sotto; si tratta di una semplice funzione Javascript nella quale viene estesa la variabile $scope con l’attributo riders che punta alla lista dei piloti descritta poco fa. L’aspetto più oscuro è il parametro $scope. Si tratta del contesto entro il quale verranno valutate le espressioni incluse all’interno del tag per cui è definito questo controller. In altre parole potremmo riferirci alla variabile riders solo all’interno del sottoalbero <body> mentre per gli altri elementi come <html> o <head> questa non sarà definita. Tornando alla parte HTML, possiamo apprezzare come la variabile riders si trovi all’interno dello scope assegnato al mainController e, tramite la direttiva ng-repeat, come sia possibile accedere ad ogni iterazione alle proprietà del motociclista corrente. E’ possibile reperire il codice sorgente di questo passo al seguente indirizzo: https://github.com/yupitz/motoGpRiders/tree/step3 Il risultato è mostrato nell’immagine seguente. piloti

Filtri e ordinamenti

Aggiorniamo la nostra single-page-application inserendo la possibilità di filtrare la nostra lista di piloti. Anche in questo caso AngularJS facilita molto questa operazione. Aggiungiamo alla pagina HTML un campo di ricerca tramite il seguente codice:

<input type="text" ng-model="query" placeholder="Search..."/>

Com’è possibile notare, anche in questo caso abbiamo utilizzato la direttiva ng-model per creare un binding tra la casella di testo ed una variabile del model. Da notare che non abbiamo avuto bisogno di esplicitare la dichiarazione della variabile query all’interno dei nostri file Javascript; questa viene generata in modo automatico dal framework e resa disponibile all’interno dello scope corrente. Modifichiamo quindi l’espressione assegnata alla direttiva ng-repeat come mostrato di seguito:

<ng-repeat="rider in riders | filter:query">

Così facendo abbiamo definito un filtro, applicato alla lista di piloti, che valuta la presenza del valore query all’interno dei campi associati al model di ciascun pilota. La valutazione dei filtri avviene in modo dinamico, ovvero il risultato viene rivalutato ogni volta che il valore del modello query cambia. Oltre alla funzione filter, AngularJS mette a disposizione diversi filtri utili per la formattazione e l’ordinamento dei valori contenuti all’interno delle view; ecco i più comuni: angular-table Questi filtri possono essere utilizzati in combinazione gli uni con gli altri separandoli con un carattere pipe (|). Ad esempio è possibile filtrare i piloti e ordinarli in base al loro numero di gara nel seguente modo

<ng-repeat="rider in riders | filter:query | orderBy:'number' ">

E’ possibile reperire il codice sorgente di questo passo al seguente indirizzo: https://github.com/yupitz/motoGpRiders/tree/step4

Non finisce qui

Termina qui la prima parte del tutorial, nella prossima parte vedremo come realizzare i template, gestire le rotte e recuperare i dati dal backend.

Alessandro Modolo

Dopo essermi laureato in informatica nel 2012, specializzandomi nella progettazione e sviluppo software, ho intrapreso il mio cammino lavorativo come programmatore. Ho avuto moto di conoscere da vicino gli ambienti silverlight e android, ma da un'anno a questa parte mi dedico allo sviluppo di applicazioni ibride in campo mobile.

  • Fabio

    Forse sono io che non ho capito bene, ma mi pare che i frammenti di codice siano parziali.
    Nel primissimo frammento, ad esempio, non vedo da nessuna parte il tag il cui significato è spiegato subito dopo

    • Giampaolo Trapasso

      Ciao Fabio, grazie della segnalazione. Ieri sera abbiamo aggiornato i plugin e qualcosa deve essere andato storto. Ora il post è in ordine

  • Pingback: AngularJS mini How-To (seconda parte) :: CoseNonJaviste()

  • Ciao, ho notato che il tuo esempio, l’ultimo, non funziona più con l’ultima versione del ramo 1.x. Per la precisione la 1.5.x
    La console di FF Developer da come errore: ( riporto testualmente la pagina di AngularJS DOCS )

    Error: ng:areq

    Bad Argument

    Argument ‘mainController’ is not a function, got undefined

    Description

    AngularJS often asserts that certain values will be present and truthy using a
    helper function. If the assertion fails, this error is thrown. To fix this problem,
    make sure that the value the assertion expects is defined and truthy.

    Mentre funziona perfettamente con la versione CDN fornita nel tutorial.
    Ora ti darei volentieri una mano, ma sto studiando da poco Angular e non so ancora dove mettere le mani.
    Ciao Marco.

  • Mi sono sempre ripromesso di imparare AngularJS, mi sa che è la volta buona che comincio davvero