facebook

Blog

Resta aggiornato

Vediamo come importare i moduli in maniera efficiente
Usiamo il Lazy Loading pattern in Angular
martedì 04 Maggio 2021
angular lazy loading

Uno dei concetti fondamentali di Angular sono i Moduli chiamati NgModules.Possiamo definire un NgModules come un contenitore di blocchi di codice che appartengono ad uno stesso flusso, ad una stessa funzionalità oppure dedicati ad un certo dominio dell’applicazione, all’interno dei quali possiamo trovare componenti, servizi e altri file di codice il cui scopo è definito dal modulo stesso. 

Ogni applicazione Angular è composta da almeno un modulo, il modulo root, che per convenzione è chiamato AppModule ed è definito nel file app.module.ts. Di seguito un esempio di come è strutturato:

import { NgModule } from '@angular/core'; 
import { BrowserModule } from '@angular/platform-browser'; 
@NgModule({ 
        imports:     [ BrowserModule ], 
        providers:    [ Logger ], 
        declarations: [ AppComponent ], 
        exports:      [], 
        bootstrap:    [ AppComponent ] 
}) 
export class AppModule { } 

Nelle applicazioni più complesse, oltre all’AppModule, possono essere definiti molti altri moduli, aggiungendoli nell’array imports. Di default, i moduli dichiarati nell’array imports vengono caricati quando l’applicazione parte, anche se non vengono immediatamente utilizzati.

Questo, ovviamente, ha un costo in termini di tempo, soprattutto se si tratta di un’applicazione che possiede molte rotte che appartengono a moduli diversi tra loro.

In questi casi, è bene applicare il Lazy Loading design pattern, che ci consente di caricare i moduli solo nel momento in cui effettivamente ne abbiamo bisogno, ottenendo così un bundle iniziale più piccolo e una diminuzione del tempo necessario al caricamento dell’applicazione.

L’utilizzo di questo pattern nella nostra applicazione richiede pochi passaggi. Innanzitutto, occorre che la nostra applicazione abbia una gestione del routing, mediante quindi l’importazione nell’AppModule del NgModule RouterModule e l’utilizzo della seguente istruzione all’interno dell’array imports

imports: [ 
                RouterModule.forRoot([  
                {path: 'first', component: FirstComponent},  
                {path: 'second', component: SecondComponent},  
                ]), 
... 
] 

In questo modo, possiamo vedere una specifica view nella nostra applicazione in base al path di navigazione presente nella URL.

Ricordiamoci anche di aggiungere il RouterModule alla lista degli exports, per poterlo importare dai diversi moduli usati nell’applicazione.

exports: [ 
                RouterModule 
] 

Il secondo passaggio è quello di sostituire il component che vogliamo mostrare ad un determinato path con l’istruzione:

loadChildren: () => import('./first/first.module').then(m => m.FirstModule) 

ottenendo

imports: [ 
                RouterModule.forRoot([  
                        {path: 'first',  loadChildren: () => import('./first/first.module').then(m => m.FirstModule) },  
                        {path: 'second', component: SecondComponent},  
                ]), 
... 
] 

E aggiungendo il routing anche in FirstModule

const routes: Routes = [ { path: '', component: FirstComponent} ]; 

Ma, stavolta, mediante l’istruzione forChild (in seguito vedremo la differenza tra forChild e forRoot)

imports: [ 
                RouterModule.forChild(routes) 
] 

Infine, assicuriamoci che FirstModule non sia presente nell’array imports dell’AppModule.

Per verificare di aver configurato il tutto correttamente, possiamo far partire l’applicazione e aprire il developer tool del browser, posizionandoci nella sezione Network.

A questo punto, quando proveremo a navigare verso FirstComponent, apparirà una chiamata simile a quella in figura:

Questa chiamata verrà effettuata una sola volta per ogni modulo caricato mediante il Lazy Loading, ovvero la prima volta che accederemo a quel path, nel nostro caso localhost:4200/first.

Per concludere l’argomento, va sicuramente chiarita la differenza tra forRoot e forChild, vediamo perché abbiamo utilizzato forRoot nell’AppModule e forChild nei feature Module.

Il modulo RouterModule possiede un suo service per la gestione delle rotte, tale service è un singleton e va quindi registrato una sola volta all’interno dell’applicazione. 

Per dare la possibilità di registrare il service una sola volta, il team di Angular ha esposto i due metodi statici forRoot e forChild, che ritornano una interfaccia di tipo ModuleWithProviders. In Angular, infatti, è possibile importare un modulo anche mediante l’interfaccia  ModuleWithProviders, che ha la seguente struttura: 

interface ModuleWithProviders<T> {  
        ngModule: Type<T>  
        providers?: Provider[]  
} 

In questo modo, oltre al modulo viene importata anche la lista dei provider associati ad esso, in modo tale che vengano registrati nel motore di dependency injection.

forRoot restituisce un oggetto di tipo ModuleWithProviders contenente come ngModule il RouterModule e nella lista di providers ritorna il RouterService. forChild, invece, restituisce lo stesso oggetto, ma con la lista di providers vuoti. In questo modo, ci si assicura che RouterService sia registrato una sola volta. 

Infatti, provando a registrare RouterModule mediante il metodo forRoot, anche in uno dei feature module si ottiene il seguente errore in console quando si accede al path del modulo in questione.

Spero che l’argomento sia stato di vostro interesse e, se volete approfondire l’uso di questo framework, vi invito a leggere gli articoli su Angular del nostro blog .

Alla prossima!