In questo articolo parleremo di una delle domande più gettonate che mi vengono rivolte in fase di consulenza: come creare dei moduli configurabili dall’esterno, in Angular, ovviamente!

Angular ci fornisce molti strumenti (probabilmente è il motivo per cui lo avete scelto!), ma sembra che molti sviluppatori non li sfruttino al cento percento. Fra gli strumenti di cui parlo ci sono la Dependency Injection e gli Observable, ma in questo articolo ci concentreremo solo sul primo.

La DI non è solamente un “add-on” del framework, è un meccanismo integrato fondamentale per le feature più utili di Angular e non c’è nulla che ti possa fermare dall’esplorare ogni singola possibilità che ci offre!

Se dovessimo dare alla DI una descrizione, sarebbe un pattern che ci permette di iniettare dei servizi (principalmente classi) all’interno dei nostri componenti, senza istanziarli manualmente, semplicemente inserendoli fra i parametri di un costruttore. Chi ci fornisce i servizi giusti? Angular, ovvio! E il meccanismo è dettato dal modo in cui voi componete i vostri moduli, ho scritto un altro articolo a riguardo.

Interessante! Ma fermati un secondo e pensa: cos’è una dipendenza?

Una dipendenza è qualcosa che serve a qualcos’altro per funzionare correttamente. Solitamente la dipendenza è un servizio, ma potrebbe essere qualcos’altro. Infatti, un componente può dipendere da una classe, un valore o un oggetto. Vediamo quali strumenti abbiamo e quanto sono utili nelle nostre app!

useClass

Ricordate che quando usate il nome di una classe per indicare un provider, Angular lo prende e lo interpreta in questa maniera:

// Questo...
providers: [ MyClass ]

// ...Diventa questo
providers: [
  { provide: MyClass, useClass: MyClass }
]

Ciò vuol dire che possiamo prendere una classe, anche una vuota, e scambiarla con un’altra:

class MyClass {}
class MyOtherClass {}

providers: [
  { provide: MyClass, useClass: MyOtherClass }
]

Questa pratica è utile per mockare dei servizi durante lo sviluppo, e può essere usata per introdurre un nuovo servizio mantenendo funzionante anche la vecchia classe, per motivi di compatibilità.

useValue

useValue è utile quando non volete fare il provide di una classe, ma di un valore (una primitiva, o perché no, un oggetto!). Ricordate che una dipendenza è qualcosa che serve a qualcos’altro, e a un componente potrebbe servire un oggetto! Questa è una pratica interessantissima e ne discuteremo più avanti, per ora notate questo: la classe base che dovremmo specificare verrebbe sovrascritta dall’oggetto, e quindi, sarebbe inutile.

Guardate qui: non sarebbe meglio fare il provide di una stringa, al posto di utilizzare una classe inutile?

let myObject = { greeting: 'Hello World!' };

providers: [
  { provide: 'MyService', useValue: myObject }
];

// E nel componente...
@Component({ ... })
export class AwesomeComponent {

  constructor(@Inject('MyService') myService) {
    // logging "Hello World!"
    console.log(myService.greeting);
  }
}

Ma ancora, questo approccio potrebbe portare a delle collisioni fra nomi di stringhe, e come sempre, c’è un alternativa migliore che scopriremo fra poco!

useFactory

useFactory fa quello che dice il nome: usa una factory (una semplice funzione) e Angular farà il provide del valore di ritorno di quella funzione. E’ molto utile quando dovete fare dei controlli o delle operazioni prima di sapere cosa fornire al motore di DI.

let firstObject = { greeting: 'Hello World' };
let secondObject = { greeting: 'Hello Earth' };

providers: [
  {
    provide: 'MyService',
    useFactory: () => {
      // Logica condizionale? Può servire?
      if (...) return firstObject;
      return secondObject;
    }
  }
]

E ancora, stiamo utilizzando una stringa per identificare il provider: è tempo di risolvere la questione.

InjectionToken

Ora abbiamo di fronte il problema di usare delle stringhe come identificativi per la DI oppure delle classi che sarebbero sovrascritte, e quindi inutili. Cosa possiamo fare?

Angular ci fornisce l’InjectionToken (precedentemente OpaqueToken, con alcune differenze) che fa esattamente quello che dice il nome: crea un token (un nome) da usare nel campo “provide”, al posto di utilizzare stringhe o classi. Siccome ogni istanza di InjectionToken è differente, non dovremmo preoccuparci di collisioni fra nomi.

import { InjectionToken } from '@angular/core';
// Questa è semplicemente un'interfaccia per aiutarci
import { MyInterface } from '...';

// Questo sarà il nostro token
const NewToken = new InjectionToken<MyInterface>("NEWTOKEN (descrizione opzionale)");

Ricordate che il valore fra parentesi è una semplice descrizione, non altera la nostra logica. Quindi ora possiamo usare il nostro token sia nel modulo che nel componente, ottimo!

Configurare un modulo dall’esterno

Siamo arrivati alla parte interessante: potete usare la Dependency Injection per iniettare un oggetto di configurazione all’interno dei vostri moduli, che potete usare per configurare i vostri servizi! Come? Sembra complicato? Facciamo un esempio.

Immaginate di avere un servizio che parli con un’API, diciamo… Contentful, per esempio! Si tratta del CMS Headless più famoso sulla piazza, che vi consente di costruire i vostri modelli (città, persona, impiegato, ecc…), una specie di WordPress ma senza template. Siccome i vostri dati saranno accessibili soltanto via API, il team ha rilasciato degli SDK (delle piccole librerie di supporto) per facilitare il processo di chiamata. Scriviamo un servizio che usi questo SDK e mettiamo questo servizio nel suo nuovo modulo, ContentfulModule.

import { Injectable } from '@angular/core';

declare const contentful: any;

@Injectable()
export class ContentfulService {

  private client;

  constructor() {
    // Inizializziamo il client
    this.client = contentful.createClient({
      space: 'IL_VOSTRO_ID',
      accessToken: 'IL_VOSTRO_TOKEN'
    });
  }

  public getEntries(contentType: string): Promise<any> {
    return this.client.getEntries({ content_type: contentType }).then(response => response.items);
  }
}

[Attenzione, nell’esempio ho già incluso contentful come script in index.html perché ho avuto qualche problema con StackBlitz. Potete ovviamente installarlo con npm e importarlo senza dichiarare orribili costanti vuote!]

Perfetto! Ora, l’SDK deve sapere il nostro ID (il nostro “spazio”) e il nostro token per funzionare. Salviamoli da qualche parte, no? Perché non creiamo un file con un oggetto nel nostro modul…

NO!

Questo è ciò che NON dovreste fare per creare un modulo riutilizzabile. State creando un modulo utile che potrebb essere riutilizzato anche in altre app, e se il vostro modulo crescerà abbastanza potreste anche pensare di pubblicarlo su npm! Non sarebbe carino scrivere questo sulla pagina GitHub:

Scusate, per configurare il modulo dovete modificare i file sorgente, e mi raccomando, attenti quando aggiornate il pacchetto…

Argh. Che bruttissima idea. Il vostro modulo deve essere configurato dall’esterno! E se siete perspicaci, potreste già avere un’idea su come fare.

forRoot

Eh già! Probabilmente già saprete che RouterModule usa un metodo forRoot() per passare la nostra configurazione al router: le rotte! Le rotte sono proprio degli oggetti di configurazione. Copiamone l’approccio.

Ecco cosa c’è da fare:

  • Scriviamo nel modulo un metodo statico forRoot() che prenda l’oggetto di configurazione e ne faccia il provide.
  • Creiamo anche un’interfaccia per il nostro oggetto, così che gli utilizzatori del modulo sappiano come dev’essere fatto.
  • Facciamo il provide di quell’oggetto con un InjectionToken e useValue.
import { ContentfulService } from '...';

// La nostra interfaccia per l'oggetto di configurazione
interface ContentfulConfig {
  spaceId: string;
  accessToken: string;
}

// Il nostro InjectionToken
const ContentfulConfigService = new InjectionToken<ContentfulConfig>('ContentfulConfig');

// Il nostro modulo
@NgModule()
export class ContentfulModule {

  static forRoot(config: ContentfulConfig): ModuleWithProviders {
    return {
      ngModule: ContentfulModule,
      providers: [
        ContentfulService,
        {
          provide: ContentfulConfigService,
          useValue: config
        }
      ]
    }
  }
}

// E nel nostro AppModule...
const contentfulConfig: ContentfulConfig = {
  spaceId: '123456789';
  accessToken: 'test'
}

@NgModule({
  imports: [
    ...
    ContentfulModule.forRoot(contentfulConfig)
  ]
})
export class AppModule {}

Ottimo! Un’ultima cosa, entriamo in ContentfulService e chiediamo quell’oggetto nel costruttore, come facciamo di solito con i servizi, ma con una sintassi differente:

@Injectable()
export class ContentfulService {

  private client;

  constructor(@Inject(ContentfulConfigService) private config: ContentfulConfig) {
    this.client = contentful.createClient({
      space: config.spaceId,
      accessToken: config.accessToken
    });
  }

  ...
}

Voilà! Ora avete un [non proprio] bellissimo modulo riutilizzabile che può essere configurato dall’esterno! Ovviamente l’oggetto di configurazione potrà essere iniettato a piacimento dai servizi di ContentfulModule (o anche di AppModule se è il caso, ma sarà un approfondimento per un altro articolo ;)).

Vi ho preparato un esempio funzionante, dovrete solo rimpiazzare lo Space ID e l’Access Token con i vostri (in AppModule) e richiedere uno dei vostri content type in AppComponent (nel mio caso, ad esempio, era “article”):

Ti è piaciuto l’articolo? Condividilo!

Rilasciare risorse gratuite non è facile: un sacco di tempo vola fra la ricerca dell’idea, la stesura, la preparazione di codice funzionante e la revisione. Se ti piace ciò che facciamo, condividi l’articolo: nel peggiore dei casi ti daranno del secchione!

Ti interesserà anche…

Angular

Creazione di Guardie per i Moduli

I moduli sono una delle feature che più amo di Angular: ci permettono di racchiudere componenti, servizi, pipe e direttive all’interno di un unico contenitore. Ma non si tratta soltanto…
Angular

NgRx: cosa cambia nella versione 8

L’ottava versione di NgRx ha portato molti cambiamenti interessanti che ci consentono di scrivere codice più pulito e più snello. E c’è anche qualche chicca che vale la pena di…

Iscriviti alla nostra newsletter!

Rimani aggiornato per quando rilasceremo nuovi articoli, nuovi corsinuove promozioni e nuove risorse gratuite. Ti promettiamo che useremo la tua email con prudenza, e non la condivideremo con nessuno senza il tuo consenso!

Abbiamo a cuore la tua privacy. Niente spam. Leggi la nostra privacy policy qui.

Menu