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

111

¿Cuáles son las diferencias entre estos dos y cuáles son los casos de uso para cada uno?

Los documentos no son exactamente útiles:

forRoot crea un módulo que contiene todas las directivas, las rutas dadas y el propio servicio del enrutador.

forChild crea un módulo que contiene todas las directivas y las rutas dadas, pero no incluye el servicio de enrutador.

Mi conjetura vaga es que uno es para el módulo 'principal' y el otro es para cualquier módulo importado (ya que ya tendrían el servicio disponible desde el módulo principal), pero realmente no puedo pensar en un caso de uso.

VSO
fuente
1
¿Podría ser más específico sobre lo que no comprende? La cita que ha incluido le dice literalmente cuál es la diferencia.
Jonrsharpe
2
No entiendo cuál es el punto de usar .forChild (). ¿Cuándo querría las directivas y las rutas sin el servicio? Mientras tanto, responda la pregunta que eliminó de la publicación ...
VSO
18
Solo debería haber uno RouterServicepara una sola aplicación Angular2. forRootInicializará ese servicio y lo registrará en DI junto con alguna configuración de ruta, mientras forChildque solo registrará configuraciones de ruta adicionales y le dirá a Angular2 que reutilice el RouterServiceque forRootha creado.
Harry Ninh
@HarryNinh: Gracias, eso es lo que estaba buscando. Sin embargo, ¿cuándo querría registrar rutas adicionales fuera del registro inicial? Parece un poco tonto. Supongo que no hay forma de crear rutas dinámicamente.
VSO
1
vea esto por el autor del enrutador angular victor.
nikhil mehta

Respuestas:

123

Recomiendo encarecidamente leer este artículo:

Módulo con proveedores

Cuando importa un módulo, generalmente usa una referencia a la clase del módulo:

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

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

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

De esta manera, todos los proveedores registrados en el módulo Ase agregarán al inyector raíz y estarán disponibles para toda la aplicación.

Pero hay otra forma de registrar un módulo con proveedores como este:

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

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

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

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

Esto tiene las mismas implicaciones que el anterior.

Probablemente sepa que los módulos de carga diferida tienen su propio inyector. Entonces, suponga que desea registrarse AServicepara estar disponible para toda la aplicación, pero algunos BServicepara estar disponibles solo para módulos cargados de forma diferida. Puede refactorizar su módulo de esta manera:

@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

Ahora BServicesolo estará disponible para los módulos de carga diferida para niños y AServiceestará disponible para toda la aplicación.

Puede reescribir lo anterior como un módulo exportado como este:

@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

¿Qué importancia tiene eso para RouterModule?

Supongamos que se accede a ambos utilizando el mismo token:

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

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

Con configuraciones separadas cuando lo solicita tokendesde un módulo cargado de forma diferida, obtendrá BServicetal como estaba planeado.

RouterModule usa ROUTEStoken para obtener todas las rutas específicas de un módulo. Dado que quiere que las rutas específicas del módulo de carga diferida estén disponibles dentro de este módulo (análogos a nuestro BService), utiliza una configuración diferente para los módulos secundarios de carga diferida:

static forChild(routes: Routes): ModuleWithProviders {
    return {
        ngModule: RouterModule, 
        providers: [{provide: ROUTES, multi: true, useValue: routes}]
    };
}
Max Koretskyi
fuente
3
en otras palabras, deberíamos llamar a forChild () en los módulos de funciones porque ya se llamó a forRoot () en el módulo raíz y se agregaron todos los servicios necesarios. Entonces, ¿llamar a forRoot () nuevamente conducirá a estados impredecibles?
Wachburn
7
@Wachburn, llamar forRoota un módulo de carga diferida creará nuevas instancias de todos los servicios globales en el inyector de módulo de carga diferida. Sí, esto dará lugar a resultados impredecibles. Lea también este artículo Evitar confusiones comunes con módulos en Angular
Max Koretskyi
1
@Willwsharp, ¿por qué?
Max Koretskyi
1
Entiendo completamente esto. Pero, ¿cuál es la diferencia entre Routermodule.foRoot, VS Routermodule.forChild? forRoot & forChild son solo métodos estáticos que devuelven un objeto en función del valor pasado. Entonces, en el módulo de la aplicación, ¿por qué no puedo usar forChild? ¿Por qué es un error de lanzamiento? ¿Por qué no puedo usar varios forRoot?
Subhadeep
2
A veces, es bueno ir directo al grano con las respuestas. La sobrecarga de información la mayoría de las veces genera más confusión.
Adépòjù Olúwáségun
33

Creo que las respuestas son correctas, pero creo que falta algo.
Lo que falta es "¿por qué y qué resuelve?".
Vale, empecemos.

Primero mencionemos algo de información:

Todos los módulos tienen acceso a los servicios raíz.
Por lo tanto, incluso los módulos con carga diferida pueden usar un servicio que se proporcionó en app.module.
¿Qué pasará si un módulo de carga diferida se proporciona a sí mismo un servicio que el módulo de la aplicación ya proporcionó? habrá 2 instancias.
No es un problema, pero a veces lo es .
¿Cómo podemos solucionarlo? simplemente no importe un módulo con ese proveedor a módulos con carga diferida.

Fin de la historia.

Esto ^ fue solo para mostrar que los módulos con carga diferida tienen su propio punto de inyección (a diferencia de los módulos sin carga diferida).

Pero, ¿qué sucede cuando se ha declarado un módulo compartido (!)providers Y ese módulo es importado por lazy y app.module ? Nuevamente, como dijimos, dos instancias.

Entonces, ¿cómo podemos resolver esto en el módulo compartido POV? ¡Necesitamos una forma de no usar providers:[]! ¿Por qué? porque se importarán automáticamente tanto a lazy consumidor como a app.module y no queremos eso, ya que vimos que cada una tendrá una instancia diferente.

Bueno, resulta que podemos declarar un módulo compartido que no tendrá providers:[], pero aún así, proporcionará proveedores (lo siento :))

¿Cómo? Me gusta esto :

ingrese la descripción de la imagen aquí

Aviso, no hay proveedores.

Pero

  • ¿Qué pasará ahora cuando app.module importe el módulo compartido con el punto de vista del servicio? NADA.

  • ¿Qué pasará ahora cuando un módulo perezoso importará el módulo compartido con el punto de vista del servicio? NADA.

Ingresando al mecanismo manual a través de la convención:

Notará que los proveedores en las imágenes tienen service1yservice2

Esto nos permite importar service2para módulos con carga diferida y módulos service1no diferidos. ( tos ... enrutador ... tos )

Por cierto, nadie te detiene para llamar forRootdentro de un módulo perezoso. pero tendrá 2 instancias porque app.moduletambién debería hacerlo, así que no lo haga en módulos perezosos.

Además, si app.modulellama forRoot(y nadie llama forchild), está bien, pero el inyector raíz solo tendrá service1. (disponible para todas las aplicaciones)

Entonces, ¿por qué lo necesitamos? Yo diria :

Permite que un módulo compartido, para poder dividir sus diferentes proveedores para ser utilizado con módulos ansiosos y módulos perezosos - vía forRooty forChildconvenciones. Repito: convención

Eso es.

ESPERE !! ni una sola palabra sobre singleton? entonces, ¿por qué leo singleton en todas partes?

Bueno, está escondido en la oración anterior ^

Permite que un módulo compartido pueda dividir sus diferentes proveedores para usar con módulos ansiosos y módulos perezosos, a través de forRoot y forChild .

La convención (!!!) permite que sea singleton, o para ser más preciso, si no sigue la convención, NO obtendrá un singleton.
Entonces, si solo carga forRooten el app.module , entonces obtiene solo una instancia porque solo debe llamarlo forRooten el app.module.
Por cierto, en este punto puedes olvidarte forChild. el módulo de carga diferida no debería / no llamará forRoot, por lo que está seguro en POV de singleton.

forRoot y forChild no son un paquete irrompible, es solo que no tiene sentido llamar a Root, que obviamente se cargará solo app.module sin dar la capacidad a los módulos perezosos, tener sus propios servicios, sin crear nuevos servicios, que deberían ser -único.

Esta convención le da una buena habilidad llamada forChild- consumir "servicios sólo para módulos cargados de forma diferida".

Aquí hay una demostración Los proveedores raíz producen números positivos, los módulos cargados de forma diferida producen números negativos.

Royi Namir
fuente
1. ¿Qué es POV aquí? 2. El stackblitz ya no funciona.
Nicholas K
@NicholasK este stackblitz siempre estropea las cosas después de un tiempo. Intentaré subir una nueva versión
Royi Namir
26

La documentación establece claramente cuál es el propósito de esta distinción aquí: https://angular.io/docs/ts/latest/guide/ngmodule.html#!#core-for-root

Llame a forRoot solo en el módulo de aplicación raíz, AppModule. Llamarlo en cualquier otro módulo, particularmente en un módulo de carga diferida, es contrario a la intención y es probable que produzca un error de tiempo de ejecución.

Recuerde importar el resultado; no lo agregue a ninguna otra lista de @NgModule.

Cada aplicación tiene exactamente un punto de partida (raíz) donde se debe inicializar el servicio de enrutamiento principal forRoot, mientras que las rutas para funciones "secundarias" particulares deben registrarse adicionalmente con forChild. Es extremadamente útil para submódulos y módulos de carga diferida que no tienen que cargarse al inicio de la aplicación, y como dijo @Harry Ninh, se les dice que reutilicen RouterService en lugar del registro del nuevo servicio, lo que puede causar un error de tiempo de ejecución.

Marcin
fuente
2
Esos documentos parecen haberse
PeterS
1

Si appRoutes contiene una ruta a varias funciones en el sitio (admin crud, user crud, book crud) y queremos separarlas, simplemente podríamos hacer eso:

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

Y para rutas:

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},



]
Hmaied Belkhiria
fuente