Introduzione ad Angular CLI

Angular CLI logo

Lo startup di un progetto può essere sempre un problema se non si conoscono le best practices o non si sa esattamente come strutturare l’applicazione. Fortunatamente è già da diversi anni che, per i progetti frontend web, tool come Yeoman ci vengono in soccorso non solo per lo scaffolding dello startup di un progetto, ma anche per supporto durante la sua evoluzione. Sono nati quindi diversi strumenti da riga di comando (CLI, Command Line Interface appunto) che velocizzano lo sviluppo e la standardizzazione della struttura dei progetti, spesso legati al framework principale dell’applicazione (Angular, React, Backbone…) o agli strumenti di build (come Grunt o Gulp).

Più un framework è complesso, più uno strumento come la CLI è non solo utile, ma necessario. Anche per Angular quindi non poteva mancare la sua CLI, che ovviamente si chiama Angular CLI!

Prime impressioni

Visitando la home page, già si capisce subito come installare Angular CLI, creare un progetto e avviare il server di sviluppo!
angular cli logo

Eh già, con Angular CLI sembra che non abbiamo più bisogno di un task executor come Gulp o Grunt, oppure un module bundler come Webpack… è tutto autogestito! Ma andiamo per gradi, e vediamo effettivamente quanto ne possiamo fare a meno, quali sono i limiti e forze di questo strumento.

Startup veloce

Cominciamo quindi dall’inizio, ovvero l’installazione, meglio a livello globale

npm install -g @angular/cli

Al momento della scrittura del post, verrà installata la versione 1.2.4 e Angular 4 (precisamente la 4.3.1).

Affinché funzioni, è necessario avere almeno installato Node 6.9.0 e NPM 3. Se avete una versione precedente, che magari volete mantenere per altri progetti, la soluzione migliore è affidarsi ad un Node Version Manager come nvm.

A questo punto è un attimo creare un nuovo progetto

ng new my-new-project

la cartella my-new-project verrà creata automaticamente: entriamo nella cartella e avviamo il server di sviluppo

cd my-new-project
ng serve

che ci mostrerà i seguenti log, lasciando la console appesa.

** NG Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200 **
Hash: 64c03dceb94f11eeed81
Time: 8435ms
chunk    {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 160 kB {4} [initial] [rendered]
chunk    {1} main.bundle.js, main.bundle.js.map (main) 3.63 kB {3} [initial] [rendered]
chunk    {2} styles.bundle.js, styles.bundle.js.map (styles) 10.5 kB {4} [initial] [rendered]
chunk    {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.47 MB [initial] [rendered]
chunk    {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered]
webpack: Compiled successfully.

Da qua si deducono subito alcune cose:

  • all’indirizzo http://localhost:4200 è disponibile l’applicazione che vogliamo sviluppare. Al momento mostra la pagina di default generata dalla CLI
    angular cli project default home
  • sono disponibili 5 file js, con relative mappe, generati in memoria dal server: questa separazione, in fase di sviluppo, rende più veloce la rigenerazione, automatica al salvataggio del sorgente, del file main.bundle.js che contiene il nostro codice.
  • infine, scopriamo che tutto il lavoro lo fa WebPack dietro le quinte, di cui Christian ci ha già parlato in un altro post. Di fatto, chi “serve” la nostra applicazione in fase di sviluppo è webpack-dev-server.

Ma vediamo che cosa contiene la cartella del progetto appena creato (per capire meglio quanto segue è consigliato generare un nuovo progetto… tanto sapete come fare! 🙂 )

Al primo livello troviamo:

  • .angular-cli.json: file di configurazione di Angular CLI. Per ogni personalizzazione bisogna passare da qua. Lo vedremo meglio tra poco
  • .editorconfig: file di configurazione che definisce lo stile del codice comune a diversi editor, secondo le direttive del progetto EditorConfig. IntelliJ (e famiglia) lo supportano nativamente;
    esistono poi plugin per Atom, Sublime, Eclipse, XCode…
  • .git: repository Git già inizializzato, con il primo commit fatto da Angular CLI
  • .gitignore: preconfigurato già con diversi elementi da ignorare
  • README.md: contiene un riassunto veloce dei comandi principali di Angular CLI
  • dist: cartella dove risiede il compilato
  • e2e: cartella dei test end-to-end
  • karma.conf.js: file di configurazione di Karma. Un test è già pronto: lanciate ng test per vederlo all’opera! I test sono scritti con Jasmine
  • node_modules: dipendenze del progetto
  • package.json: classico descrittore del progetto
  • protractor.conf.js: file di configurazione di Protractor per i test end-to-end. Anche in questo caso è già tutto pronto: basta eseguire ng e2e
    per vedere Chrome aprirsi e poi richiudersi, dandoci i risultati del test in console! Il webdriver viene automaticamente scaricato per noi.
  • src: sorgenti dell’applicazione
  • tsconfig.json file di configurazione di TypeScript
  • tslint.json: file di configurazione di TSLint,
    ovvero analizzatore statico di codice per TypeScript

La cartella src è quella che ci interessa approfondire. La maggior parte dei file presenti in questa cartella assolve uno specifico compito assegnatogli dal file di configurazione .angular-cli.json.

  • app: cartella contenente il nostro codice. Ogni comando della CLI genera del codice in questa cartella
  • assets: cartella che conterrà i vari asset, come le immagini
  • environments: contiene i file .ts che definiscono quelle proprietà che variano tra gli ambienti (dev e prod per non fare due nomi a caso). L’associazione tra ambiente e file è definita in .angular-cli.json, che di default lavora su dev.
  • favicon.ico: icona mostrata sulla barra degli indirizzi
  • index.html: template HTML di partenza
  • main.ts: file di bootstrap dell’applicazione
  • polyfills.ts: import di tutti i polyfill necessari ad Angular per supportare i browser meno recenti. Se si ha questa necessità, meglio controllare la pagina di compatibilità dei browser
  • styles.css: foglio di stile principale
  • test.ts: entrypoint dei test
  • tsconfig.app.json: file di configurazione di TypeScript
  • tsconfig.spec.json: file di configurazione per i test unitari in TypeScript
  • typings.d.ts: descrittore delle definizioni in TypeScript delle librerie JavaScript

Se date quindi un’occhiata al file .angular-cli.json, troverete riferimenti a gran parte di questi file. Una spiegazione completa di ogni elemento di questo file è disponibile nella documentazione ufficiale.

I comandi base

Ora che abbiamo conosciuto cosa è stato creato automaticamente con un semplice comando, vediamo come Angular CLI ci dà un aiuto nel proseguire lo sviluppo. E’ possibile consultare l’elenco dei comandi disponibili a partire dal Wiki ufficiale.

La CLI di Angular è gerarchica: basta digitare ng --help per rendersene conto. Abbiamo già conosciuto ng new e ng serve: vediamo adesso invece come ng generate (ng g per gli amici) sarà il nostro compagno di tutti i giorni.

Moduli e Componenti

I tasselli principali di una applicazione Angular sono Moduli e Componenti. L’applicazione nasce con un modulo radice (che abbiamo conosciuto nello scorso post) chiamato solitamente AppModule, e su questo viene fatto il bootstrap. Ogni nuovo componente creato, se non differentemente specificato, viene associato a questo modulo:

ng g component my-first

che come output produce:

installing component
  create src/app/my-first/my-first.component.css
  create src/app/my-first/my-first.component.html
  create src/app/my-first/my-first.component.spec.ts
  create src/app/my-first/my-first.component.ts
  update src/app/app.module.ts

Vengono quindi creati automaticamente 4 file nella cartella my-first sotto src/app e aggiunta la sua dichiarazione nel modulo di root app.module.ts.

Creare un nuovo modulo “home-page” invece è altrettanto facile:

ng g module home-page

con output:

installing module
  create src/app/home-page/home-page.module.ts
  WARNING Module is generated but not provided, it must be provided to be used

Il nuovo modulo viene creato nella cartella home-page, al pari dei componenti, e, come dice il warning, non è collegato al modulo di root, quindi non verrà avviato. Possiamo modificare AppModule manualmente, oppure rilanciare il comando dichiarando di chi è “figlio” il modulo che stiamo creando:

ng g module home-page --module=src/app/app.module.ts

che questa volta produrrà in console:

installing module
  create src/app/home-page/home-page.module.ts
  update src/app/app.module.ts

Direttive, Pipes e Services

Altri elementi fondamentali dello sviluppo che possiamo creare da dei “blueprint” (come viene definito lo scaffolding nell’help della CLI) sono le direttive. Per esempio, la direttiva “awesome” si crea così:

ng g directive awesome
installing directive
  create src/app/awesome.directive.spec.ts
  create src/app/awesome.directive.ts
  update src/app/app.module.ts

Viene creata direttamente nella cartella src/app e associata al modulo di root.

Stesso discorso per una nuova pipe “toUpperCase”

ng g pipe toUpperCase

o per un service, anche se in questo caso è necessario specificare il modulo che farà da “provider”

ng g service myService --module=app.module.ts

E’ possibile consultare l’elenco completo dei sotto comandi di generate direttamente dall’help da riga di comando

ng g --help

o dal wiki.

La struttura del progetto

Già dai pochi comandi visti nel paragrafo precedente, si nota che la struttura del codice sorgente dentro la cartella src/app rischia di diventare ben presto caotica perché non c’è distinzione e gerarchia tra componenti e moduli, gli unici ad essere creati in cartelle.

La struttura che vorremmo dare al sorgente è la seguente:

  • un modulo per ogni pagina dell’applicazione, definito in una sotto cartella di src/app
  • vogliamo fare distinzione tra Smart Components e Presentation Components: i primi, essendo specifici della logica di business del modulo che stiamo creando, risiedono sotto la cartella del modulo stesso; i secondi invece, essendo riusabili tra moduli, risiederanno in un modulo che chiameremo “view-components”
  • i servizi riusabili sono in un modulo comune e definiti in sotto cartelle del modulo stesso
  • un modulo di utilità dove definire tutto il resto (direttive, pipes…)
  • tutti i componenti hanno un prefisso specifico che identifica il progetto (per esempio “my”)

L’idea base quindi è quella di avere come figli di src/app cartelle che rappresentano esclusivamente i moduli; i componenti avranno a sua volta delle cartelle che raccolgono i 4 file, mentre negli altri casi si avranno dei file “liberi” nella cartella stessa del modulo.

Immaginiamo quindi di volere questa alberatura su file system:

src/app
  - home-page/
    + home-page.module.ts
    + dashboard/
      * dashboard.component.ts
      * dashboard.component.spec.ts
      * dashboard.component.html
      * dashboard.component.css
  - view-components/
    + view-components.module.ts
    + paged-table/
      * paged-table.component.ts
      * paged-table.component.spec.ts
      * paged-table.component.html
      * paged-table.component.css
  - services/
    + services.module.ts
    + search.service.ts
  - utils/
    + utils.module.ts
    + to-upper-case.pipe.ts
  - app.component.css
  - app.component.html
  - app.component.spec.ts
  - app.component.ts
  - app.module.ts

Occhio che il ng g” agisce in modo contestuale al path corrente, per cui è importante sapere in che cartella siamo per costruire i path. Per comodità, conviene stare nella cartella src/app.

Prima di cominciare, definiamo che prefisso dare ai componenti. Nel file .angular-cli.json (nella radice del progetto), l’attributo "prefix" è impostato su "app": cambiamolo in "my" con l’aiuto della CLI

ng set apps[0].prefix my

Da questo momento in poi, creando il componente dashboard, il suo selettore CSS sarà my-dashboard.

Nel mondo reale quindi, per creare la struttura desiderata faremo così:

# Aggiungiamo il feature module "home-page" al modulo root
ng g module home-page --module=app
# Creiamo il componente "dashboard" all'interno della cartella "home-page", da aggiungere al modulo "home-page"
ng g component home-page/dashboard --module=home-page
# Creiamo il modulo dei Presentation Components, chiamato "view-components"
ng g module view-components --module=app
# Creiamo il componente di view "paged-table"
ng g component view-components/paged-table --module=view-components
# Aggiungiamo il modulo dei servizi al modulo di root, così da avere una sola istanza dei servizi per tutta l'applicazione
ng g module services --module=app
# E il servizio di ricerca nella cartella "services"
ng g service services/search --module=services
# Infine, il modulo di utilità
ng g module utils --module=app
# E una pipe "toUpperCase" creata nella cartella "utils"
ng g pipe utils/toUpperCase --module=utils

Il parametro --module è molto malleabile: possiamo passare il path completo al modulo, il nome del file, o solo la parte iniziale, se non ci sono ambiguità fa il lavoro che ci si aspetta!

Quello che abbiamo creato quindi è qualcosa che somiglia a questo class diagram:

anglular cli sample app diagram

Personalizzazioni

Ovviamente se Angular CLI non fosse uno strumento flessibile, in pochi continuerebbero ad usarlo. La documentazione ufficiale su GitHub ha una sezione molto interessante chiamata “Stories“, dove sono elencate le personalizzazioni più frequenti. Guardiamone alcune insieme.

Angular CLI e il Backend Proxy

Appena preso confidenza con Angular CLI mi sono chiesto: come faccio a puntare al mio backend? Abbiamo visto che dietro la CLI c’è WebPack con il suo web server che supporta i proxy. Basta quindi creare un semplice file JSON, normalmente chiamato proxy.conf.json, del tipo

{
  "/api": {
    "target": "http://localhost:3000",
    "secure": false
  }
}

per far “girare” tutte le chiamate a http://localhost:4200/api sulla porta 3000. All’avvio del server di sviluppo dobbiamo quindi indicare che vogliamo usare il proxy tramite il parametro --proxy-config. Per non stare a riscriverlo tutte le volte che avviamo il server, conviene modificare direttamente lo script “start” del package.json:

"start": "ng serve --proxy-config proxy.conf.json"

Nel caso si abbia necessità di configurazioni più articolate, la documentazione è semplice ma esauriente.

CSS Preprocessors: Angular CLI con SASS, LESS o Stylus

Quando si parla di frontend, è bene trattare dignitosamente anche il CSS! Per questo ad oggi è opportuno usare un CSS preprocessor come LESS, SASS/SCSS o Stylus. Quale che sia la scelta tra questi tre, Angular CLI non si tira indietro.

La tipologia di stile usata nel progetto è definita in fondo al file .angular-cli.json:

"defaults": {
   "styleExt": "css",
   "component": {}
}

Possiamo semplicemente cambiarla manualmente in “scss” (per esempio), oppure usare la CLI:

ng set defaults.styleExt scss

è la seconda volta che incontriamo ng set: si intuisce che il valore che segue (defaults.styleExt in questo caso) è proprio il JSON path verso la proprietà che vogliamo modificare all’interno della configurazione di Angular CLI!

Angular CLI e Bootstrap

Diamo un po’ di stile alla nostra applicazione! Se non usiamo Angular Material, una scelta consigliabile potrebbe essere Bootstrap. Come aggiungerlo quindi, visto che si porta dietro il suo CSS e i suoi font?

Per prima cosa ovviamente va installato:

npm install bootstrap --save # installa la versione 3

poi bisogna dire ad Angular CLI di caricare suo CSS: come fare? Ovviamente nel file di configurazione .angular-cli.json va aggiunto il riferimento, accanto agli stili di default:

{
  "apps": [
...
    "styles": [
      "../node_modules/bootstrap/dist/css/bootstrap.min.css",
      "styles.css"
    ]
  ]
}

Riavviando ng serve, vediamo subito che senza toccare niente il font di default è cambiato! Per essere sicuri che anche i font siano stati caricati, aggiungiamo alla pagina qualche glyphicon, il cui effetto (di dubbio gusto estetico) è questo:

angular cli page with glyphicons

Bene, anche i font funzionano! Se invece vogliamo usare Bootstrap + SASS, la “storia” ufficiale spiega come fare.

Compilazione

Ad un certo punto dovremmo abbandonare l’ambiente di sviluppo e “compilare” tutto il progetto in modo che sia gradito ai browser e deployato su un server. Un semplice

ng build

e ritroviamo il buon vecchio HTML+CSS+JS… per noi nella cartella dist, tra l’altro ripulita giusto prima della build. Se però guardiamo quanto spazio occupa questa cartella ci prenderà un colpo! Per un semplice progetto quasi vuoto come questo siamo già a 5Mb! Inaccettabile, se non sappiamo come parametrizzare il comando e capiamo cosa fa esattamente.

La documentazione è piuttosto esauriente: da qui per esempio apprendiamo che possiamo specificare un environment e un target.

  • environment: definisce le proprietà differenti per ambiente. I vari ambienti sono definiti (ovviamente)
    nel file .angular-cli.json:
    "environmentSource": "environments/environment.ts",
    "environments": {
       "dev": "environments/environment.ts",
       "prod": "environments/environment.prod.ts"
    }
    

    che si trovano nella cartella del sorgente (src/environments). Basta quindi importare il file principale (identificato da environmentSource)

    import { environment }  from '../environments/environment';
    ...
    export class AppComponent {
      title = 'app';
      env = environment.production ? 'prod' : 'dev';
    }
    

    Angular CLI si preoccuperà di risolvere l’import giusto in base all’ambiente.

    angular cli env dev
    ng serve
    angular cli env prod
    ng serve --prod

    I file dei vari ambienti devono definire le stesse proprietà, altrimenti si avranno errori in compilazione, evitandoci sorprese a runtime!

  • target: ambiente di destinazione della build. Possiamo scegliere tra --target=production o --target=development. Il default è development (abbreviato con --dev), mentre il primo possiamo specificarlo anche con --prod. Quest’ultimo attiva una serie di altri flag:
    • --aot: come abbiamo già visto nel post precedente, in produzione è bene attivare la compilazione AOT invece della JIT, utile solo in sviluppo.
    • --env=prod: abilita le configurazioni di produzione
    • --extract-css: estrae il CSS dal DOM (normalmente in sviluppo è nell’header)

    La stessa applicazione compilata con

    ng build --prod
    

    adesso pesa appena 623Kb!!

Aggiornare la CLI

La CLI viene aggiornata piuttosto spesso: per rimanere al passo, l’aggiornamento va eseguito sia a livello globale

npm uninstall -g @angular/cli
npm cache clean
npm install -g @angular/cli@latest

che locale (cioè nella cartella del progetto in cui stiamo lavorando):

rm -rf node_modules dist # use rmdir /S/Q node_modules dist in Windows Command Prompt; use rm -r -fo node_modules,dist in Windows PowerShell
npm install --save-dev @angular/cli@latest
npm install

Attenzione che questa operazione aggiornerà anche la versione di Angular 4 (!!) se avete lasciato la dipendenza di default ("@angular/core": "^4.0.0") nel package.json.

Conclusioni

La struttura dei progetti nello sviluppo frontend è diventata molto elaborata negli ultimi anni: somiglia sempre più ad un progetto “backend” ben strutturato, molto diverso dalla struttura finale che si aspetta il browser. E’ per questo che sono nati i task runner (come i già citati Gulp o Grunt) o strumenti tuttofare come WebPack. Angular CLI si unisce a questa famiglia, in particolare a WebPack, aggiungendo un ulteriore livello, ovvero lo scaffolding durante lo sviluppo di cui è difficile ormai farne a meno! Come sempre non esistono gli strumenti silver bullet, ma per lo sviluppo con Angular è ormai uno strumento imprescindibile.

Andrea Como

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio . - -