RouterModule.forRoot (ROUTES) vs RouterModule.forChild (ROUTES)


111

Quali sono le differenze tra questi due e quali sono i casi d'uso per ciascuno?

I documenti non sono esattamente utili:

forRoot crea un modulo che contiene tutte le direttive, i percorsi indicati e il servizio router stesso.

forChild crea un modulo che contiene tutte le direttive e le route fornite, ma non include il servizio router.

La mia vaga ipotesi è che uno sia per il modulo "principale" e l'altro per tutti i moduli importati (poiché avrebbero già il servizio disponibile dal modulo principale), ma non riesco davvero a pensare a un caso d'uso.


1
Potresti essere più specifico su ciò che non capisci? La citazione che hai incluso ti dice letteralmente qual è la differenza.
jonrsharpe

2
Non capisco qual è lo scopo di usare .forChild (). Quando vorrei le direttive e le rotte senza il servizio? Nel frattempo, rispondi alla domanda che hai cancellato dal post ...
VSO

18
Dovrebbe RouterServiceessercene solo uno per una singola applicazione Angular2. forRootinizializzerà quel servizio e lo registrerà su DI insieme ad alcune configurazioni di route, mentre forChildregistrerà solo configurazioni di route aggiuntive e dirà ad Angular2 di riutilizzare quella RouterServiceche forRootha creato.
Harry Ninh

@HarryNinh: Grazie, è quello che stavo cercando. Quando vorresti registrare percorsi aggiuntivi al di fuori della registrazione iniziale? Sembra un po 'sciocco. Immagino che non ci sia modo di creare percorsi dinamicamente.
VSO

1
vedere questo dall'autore del router angolare victor.
nikhil mehta

Risposte:


123

Consiglio vivamente di leggere questo articolo:

Modulo con i fornitori

Quando importi un modulo, di solito usi un riferimento alla classe del modulo:

@NgModule({
    providers: [AService]
})
export class A {}

-----------------------------------

@NgModule({
    imports: [A]
})
export class B

In questo modo tutti i provider registrati sul modulo Averranno aggiunti all'iniettore di root e disponibili per l'intera applicazione.

Ma c'è un altro modo per registrare un modulo con provider come questo:

@NgModule({
    providers: [AService]
})
class A {}

export const moduleWithProviders = {
    ngModule: A,
    providers: [AService]
};

----------------------

@NgModule({
    imports: [moduleWithProviders]
})
export class B

Questo ha le stesse implicazioni del precedente.

Probabilmente sai che i moduli caricati in modo pigro hanno il proprio iniettore. Quindi supponi di voler registrarti AServiceper essere disponibile per l'intera applicazione, ma alcuni BServicedevono essere disponibili solo per i moduli caricati in modo pigro. Puoi effettuare il refactoring del tuo modulo in questo modo:

@NgModule({
    providers: [AService]
})
class A {}

export const moduleWithProvidersForRoot = {
    ngModule: A,
    providers: [AService]
};

export const moduleWithProvidersForChild = {
    ngModule: A,
    providers: [BService]
};

------------------------------------------

@NgModule({
    imports: [moduleWithProvidersForRoot]
})
export class B

// lazy loaded module    
@NgModule({
    imports: [moduleWithProvidersForChild]
})
export class C

Ora BServicesarà disponibile solo per i moduli figlio con caricamento lento e AServicesarà disponibile per l'intera applicazione.

Puoi riscrivere quanto sopra come un modulo esportato in questo modo:

@NgModule({
    providers: [AService]
})
class A {
    forRoot() {
        return {
            ngModule: A,
            providers: [AService]
        }
    }

    forChild() {
        return {
            ngModule: A,
            providers: [BService]
        }
    }
}

--------------------------------------

@NgModule({
    imports: [A.forRoot()]
})
export class B

// lazy loaded module
@NgModule({
    imports: [A.forChild()]
})
export class C

In che modo è rilevante per RouterModule?

Supponiamo che siano entrambi accessibili utilizzando lo stesso token:

export const moduleWithProvidersForRoot = {
    ngModule: A,
    providers: [{provide: token, useClass: AService}]
};

export const moduleWithProvidersForChild = {
    ngModule: A,
    providers: [{provide: token, useClass: BService}]
};

Con configurazioni separate, quando richiedi tokenda un modulo caricato pigro otterrai BServiceesattamente come pianificato.

RouterModule utilizza il ROUTEStoken per ottenere tutti i percorsi specifici di un modulo. Poiché desidera che all'interno di questo modulo siano disponibili percorsi specifici per il modulo con caricamento pigro (analoghi al nostro BService), utilizza una configurazione diversa per i moduli figlio caricati in modo pigro:

static forChild(routes: Routes): ModuleWithProviders {
    return {
        ngModule: RouterModule, 
        providers: [{provide: ROUTES, multi: true, useValue: routes}]
    };
}

3
quindi, in altre parole, dovremmo chiamare forChild () nei moduli delle funzionalità perché forRoot () era già stato chiamato nel modulo root e tutti i servizi necessari sono stati aggiunti. Quindi chiamare di nuovo forRoot () porterà a stati imprevedibili?
Wachburn

7
@Wachburn, la chiamata forRootin un modulo caricato lazy creerà nuove istanze di tutti i servizi globali nell'iniettore di modulo caricato lazy. Sì, questo porterà a risultati imprevedibili. Leggi anche questo articolo Evitare confusioni comuni con i moduli in Angular
Max Koretskyi

1
@ Willwsharp, perché?
Max Koretskyi

1
Lo capisco completamente. Ma qual è la differenza tra Routermodule.foRoot e VS Routermodule.forChild? forRoot e forChild sono solo metodi statici che restituiscono l'oggetto in base al valore passato. Quindi nel modulo app, perché non sono in grado di utilizzare forChild ?? perché il suo errore di lancio? Perché non posso usare più forRoot?
Subhadeep

2
A volte, è giusto andare dritto al punto con le risposte. il sovraccarico di informazioni la maggior parte delle volte porta a maggiore confusione.
Adépòjù Olúwáségun

33

Penso che le risposte siano corrette ma penso che manchi qualcosa.
La cosa che manca è "perché e cosa risolve?".
Ok iniziamo.

Per prima cosa menzioniamo alcune informazioni:

Tutti i moduli hanno accesso ai servizi di root.
Quindi anche i moduli caricati pigri possono utilizzare un servizio fornito in app.module.
Cosa succederà se un modulo caricato pigro fornirà a se stesso un servizio che il modulo dell'app ha già fornito? ci saranno 2 casi.
Non è un problema ma a volte lo è .
Come possiamo risolvere ? semplicemente non importare un modulo con quel provider in moduli caricati pigri.

Fine della storia.

Questo ^ era solo per mostrare che i moduli caricati in modo pigro hanno il loro punto di iniezione (al contrario dei moduli non caricati in modo pigro).

Ma cosa succede quando un modulo condiviso (!) Ha dichiarato providerse quel modulo viene importato da lazy e app.module ? Ancora una volta, come abbiamo detto, due casi.

Quindi come possiamo risolvere questo problema nel POV del modulo condiviso? Abbiamo bisogno di un modo da non usare providers:[]! Perché? perché verranno importati automaticamente sia in consuming lazy che in app.module e non lo vogliamo, poiché abbiamo visto che ognuno avrà un'istanza diversa.

Bene, si scopre che possiamo dichiarare un modulo condiviso che non avrà providers:[], ma fornirà comunque provider (scusate :))

Come? Come questo :

inserisci qui la descrizione dell'immagine

Avviso, nessun provider.

Ma

  • cosa succederà ora quando app.module importerà il modulo condiviso con il POV del servizio? NIENTE.

  • cosa succederà ora quando un modulo pigro importerà il modulo condiviso con POV di servizio? NIENTE.

Accesso al meccanismo manuale tramite convenzione:

Noterai che i fornitori nelle immagini hanno service1eservice2

Questo ci permette di importare service2moduli caricati in modo pigro e moduli service1non pigri. ( tosse ... router .... tosse )

A proposito, nessuno ti impedisce di chiamare forRootall'interno di un modulo pigro. ma avrai 2 istanze perché app.moduledovresti farlo anche tu, quindi non farlo in moduli pigri.

Inoltre, se app.modulechiama forRoot(e nessuno chiama forchild), va bene, ma root injector avrà solo service1. (disponibile per tutte le app)

Allora perché ne abbiamo bisogno? Direi :

Consente a un modulo condiviso, di essere in grado di dividere i suoi diversi provider da utilizzare con moduli desiderosi e moduli pigri - tramite forRoote forChildconvenzione. Ripeto: convenzione

Questo è tutto.

ASPETTARE !! non una sola parola sul singleton ?? quindi perché leggo singleton ovunque?

Ebbene, è nascosto nella frase sopra ^

Consente a un modulo condiviso di essere in grado di dividere i suoi diversi provider da utilizzare con moduli desiderosi e moduli pigri - tramite forRoot e forChild .

La convenzione (!!!) permette che sia singleton - o per essere più precisi - se non seguirai la convenzione - NON otterrai un singleton.
Quindi, se carichi solo forRootin app.module , ottieni solo un'istanza perché dovresti chiamarla forRootsolo in app.module.
BTW - a questo punto puoi dimenticarti forChild. il modulo caricato pigro non dovrebbe / non chiamerà forRoot, quindi sei al sicuro nel POV di singleton.

forRoot e forChild non sono un pacchetto indistruttibile - è solo che non ha senso chiamare Root che ovviamente verrà caricato solo in app.module senza dare la possibilità di moduli pigri, avere i propri servizi, senza creare nuovi servizi-che-dovrebbero-essere -singleton.

Questa convenzione ti dà una bella capacità chiamata forChild- consumare "servizi solo per moduli caricati in modo pigro".

Ecco una demo I fornitori di root producono numeri positivi, i moduli caricati pigri producono numeri negativi.


1. Cos'è il POV qui? 2. Lo stackblitz non funziona più.
Nicholas K

@NicholasK questo stackblitz è sempre incasinato le cose dopo un po '. cercherò di caricare una nuova versione
Royi Namir

26

La documentazione afferma chiaramente qual è lo scopo di questa distinzione qui: https://angular.io/docs/ts/latest/guide/ngmodule.html#!#core-for-root

Chiama forRoot solo nel modulo dell'applicazione root, AppModule. Chiamarlo in qualsiasi altro modulo, in particolare in un modulo caricato pigro, è contrario all'intento ed è probabile che produca un errore di runtime.

Ricordati di importare il risultato; non aggiungerlo a nessun altro elenco @NgModule.

Ogni applicazione ha esattamente un punto di partenza (root) con cui dovrebbe essere inizializzato il servizio di routing principale forRoot, mentre le route per particolari caratteristiche "secondarie" dovrebbero essere registrate in aggiunta con forChild. È estremamente utile per sottomoduli e moduli caricati in modo pigro che non devono essere caricati all'avvio dell'applicazione e, come ha detto @Harry Ninh, gli viene detto di riutilizzare RouterService invece della registrazione del nuovo servizio, il che potrebbe causare un errore di runtime.


2
Quei documenti sembrano essere stati spostati, v2.angular.io/docs/ts/latest/guide/…
PeterS

1

Se appRoutes contiene il percorso di varie funzioni nel sito (admin crud, user crud, book crud) e vogliamo separarli, potremmo semplicemente farlo:

 imports: [
    BrowserModule, HttpModule,
    AppRoutingModule,
    RouterModule.forRoot(categoriesRoutes),
    RouterModule.forRoot(auteursRoutes),
  ],

E per i percorsi:

const auteursRoutes:Routes=[
  {path:'auteurs/ajouter',component:CreerAuteurComponent},
]
const categoriesRoutes: Routes = [


  {path:'categories/consulter',component:ConsultercategoriesComponent},
  {path:'categories/getsouscategoriesbyid/:id',component:GetsouscategoriesbyIDComponent},
  {path:'categories/ajout',component:CreerCategorieComponent},
 {path:'categories/:id',component:ModifiercategorieComponent},
 {path:'souscategories/ajout/:id',component:AjoutersouscategorieComponent},
 {path:'souscategories/lecture/:id1',component:SouscategoriesComponent},
 {path:'souscategories/modifier/:id1',component:ModifiersupprimersouscategorieComponent},
  {path:'uploadfile',component:UploadfileComponent},
  {path:'categories',component:ConsultercategoriesComponent},



]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.