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

111

Quais são as diferenças entre os dois e quais são os casos de uso de cada um?

Os documentos não são exatamente úteis:

forRoot cria um módulo que contém todas as diretivas, as rotas fornecidas e o próprio serviço do roteador.

forChild cria um módulo que contém todas as diretivas e as rotas fornecidas, mas não inclui o serviço de roteador.

Meu palpite vago é que um é para o módulo 'principal' e o outro é para quaisquer módulos importados (uma vez que eles já teriam o serviço disponível no módulo principal), mas não consigo realmente pensar em um caso de uso.

VSO
fonte
1
Você poderia ser mais específico sobre o que você não entende? A citação que você incluiu literalmente diz qual é a diferença.
Jonrsharpe
2
Eu não entendo qual é o objetivo de usar .forChild (). Quando eu iria querer as diretivas e as rotas sem o serviço? Enquanto isso, responda à pergunta que você excluiu da postagem ...
VSO
18
Deve haver apenas um RouterServicepara um único aplicativo Angular2. forRootirá inicializar esse serviço e registrá-lo no DI junto com alguma configuração de rota, enquanto forChildapenas registrará configurações de rota adicionais e dirá ao Angular2 para reutilizar o RouterServiceque forRootcriou.
Harry Ninh
@HarryNinh: Obrigado - era isso que eu estava procurando. Quando você deseja registrar rotas adicionais fora do registro inicial? Parece meio bobo. Estou supondo que não há maneira de criar rotas dinamicamente.
VSO
1
veja isso pelo autor do roteador angular victor.
nikhil mehta

Respostas:

123

Eu sugiro fortemente a leitura deste artigo:

Módulo com provedores

Ao importar um módulo, você geralmente usa uma referência à classe do módulo:

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

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

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

Desta forma, todos os provedores registrados no módulo Aserão adicionados ao injetor raiz e disponíveis para toda a aplicação.

Mas há outra maneira de registrar um módulo com provedores como este:

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

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

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

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

Isso tem as mesmas implicações que o anterior.

Você provavelmente sabe que os módulos carregados lentamente têm seu próprio injetor. Portanto, suponha que você queira se registrar AServicepara ficar disponível para todo o aplicativo, mas alguns BServicepara ficar disponível apenas para módulos carregados lentamente. Você pode refatorar seu módulo assim:

@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

Agora BServiceestará disponível apenas para módulos filhos carregados lentamente e AServicepara todo o aplicativo.

Você pode reescrever o acima como um 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

Como isso é relevante para o RouterModule?

Suponha que ambos sejam acessados ​​usando o mesmo token:

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

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

Com configurações separadas, quando você solicitar tokende um módulo de carregamento lento, você obterá BServiceexatamente como planejado.

RouterModule usa ROUTEStoken para obter todas as rotas específicas para um módulo. Uma vez que ele deseja que rotas específicas para o módulo carregado lentamente estejam disponíveis dentro deste módulo (análogos ao nosso BService), ele usa uma configuração diferente para os módulos filhos carregados lentamente:

static forChild(routes: Routes): ModuleWithProviders {
    return {
        ngModule: RouterModule, 
        providers: [{provide: ROUTES, multi: true, useValue: routes}]
    };
}
Max Koretskyi
fonte
3
então, em outras palavras, devemos chamar forChild () nos módulos de recursos porque forRoot () já foi chamado no módulo raiz e todos os serviços necessários foram adicionados. Portanto, chamar forRoot () novamente levará a estados imprevisíveis?
Wachburn
7
@Wachburn, chamar forRootum módulo de carregamento lento criará novas instâncias de todos os serviços globais no injetor de módulo de carregamento lento. Sim, isso levará a resultados imprevisíveis. Leia também este artigo Evitando confusões comuns com módulos no Angular
Max Koretskyi
1
@Willwsharp, por quê?
Max Koretskyi
1
Eu entendo isso completamente. Mas qual é a diferença entre Routermodule.foRoot, VS Routermodule.forChild? forRoot e forChild são apenas métodos estáticos que fornecem um objeto em troca com base no valor passado. Então, no módulo de app, por que não consigo usar o forChild ?? por que seu erro de lançamento? Por que não consigo usar vários forRoot?
Subhadeep
2
Às vezes, é bom ir direto ao ponto com as respostas. a sobrecarga de informações na maioria das vezes leva a mais confusão.
Adépòjù Olúwáségun
33

Acho que as respostas estão certas, mas acho que falta algo.
O que falta é "por que e o que isso resolve?".
OK vamos começar.

Primeiro, vamos mencionar algumas informações:

Todos os módulos têm acesso aos serviços raiz.
Assim, mesmo os módulos carregados lentamente podem usar um serviço que foi fornecido em app.module.
O que acontecerá se um módulo de carregamento lento fornecer a si mesmo um serviço que o módulo de aplicativo já forneceu? haverá 2 instâncias.
Não é um problema, mas às vezes é .
Como podemos resolver isso ? simplesmente não importe um módulo com esse provedor para módulos carregados lentamente.

Fim da história.

Este ^ era apenas para mostrar que os módulos carregados lentamente têm seu próprio ponto de injeção (em oposição aos módulos carregados não lentamente).

Mas o que acontece quando um módulo compartilhado (!) É declarado providerse esse módulo é importado por lazy e app.module ? Novamente, como dissemos, duas instâncias.

Então, como podemos resolver isso no módulo POV compartilhado? Precisamos de uma maneira de não usar providers:[]! Por quê? porque eles serão importados automaticamente para consumir lazy e app.module e não queremos isso, pois vimos que cada um terá uma instância diferente.

Bem, acontece que podemos declarar um módulo compartilhado que não terá providers:[], mas ainda fornecerá provedores (desculpe :))

Quão? Como isso :

insira a descrição da imagem aqui

Observe, nenhum provedor.

Mas

  • o que acontecerá agora quando app.module importará o módulo compartilhado com POV de serviço? NADA.

  • o que acontecerá agora quando um módulo preguiçoso importará o módulo compartilhado com POV de serviço? NADA.

Entrando no mecanismo manual via convenção:

Você notará que os provedores nas fotos têm service1eservice2

Isso nos permite importar service2módulos carregados lentamente e módulos service1não lazy. ( tosse ... roteador .... tosse )

BTW, ninguém está impedindo você de ligar forRootem um módulo preguiçoso. mas você terá 2 instâncias porque app.moduletambém deve fazê-lo - portanto , não o faça em módulos preguiçosos.

Além disso - se app.moduleligar forRoot(e ninguém ligar forchild) - tudo bem, mas o injetor root só terá service1. (disponível para todos os aplicativos)

Então, por que precisamos disso? Eu diria :

Ele permite que um módulo compartilhado seja capaz de dividir seus diferentes provedores para serem usados ​​com módulos ávidos e módulos lazy - via forRoote forChildconvenção. Repito: convenção

É isso aí.

ESPERAR !! nem uma única palavra sobre singleton ?? então por que eu leio singleton em todos os lugares?

Bem - está escondido na frase acima ^

Ele permite que um módulo compartilhado seja capaz de dividir seus diferentes provedores para serem usados ​​com módulos ávidos e módulos lazy - via forRoot e forChild .

A convenção (!!!) permite que seja um singleton - ou para ser mais preciso - se você não seguir a convenção - você NÃO receberá um singleton.
Portanto, se você carregar apenas forRootno app.module , obterá apenas uma instância, pois só deve chamá- forRootla no app.module.
BTW - neste ponto você pode esquecer forChild. o módulo de carregamento lento não deve / não chama forRoot- então você está seguro no POV do singleton.

forRoot e forChild não são um pacote inquebrável - é só que não adianta chamar o Root que obviamente será carregado apenas app.module sem dar a capacidade de módulos preguiçosos, ter seus próprios serviços, sem criar novos serviços que deveriam ser -singleton.

Esta convenção oferece uma boa capacidade chamada forChild- para consumir "serviços apenas para módulos carregados lentamente".

Aqui está uma demonstração que os provedores de raiz geram números positivos, os módulos carregados lentamente geram números negativos.

Royi Namir
fonte
1. O que é POV aqui? 2. O stackblitz não funciona mais.
Nicholas K
@NicholasK este stackblitz sempre bagunça as coisas depois de um tempo. Vou tentar fazer upload da nova versão
Royi Namir
26

A documentação afirma claramente qual é o propósito desta distinção aqui: https://angular.io/docs/ts/latest/guide/ngmodule.html#!#core-for-root

Chame forRoot apenas no módulo de aplicativo raiz, AppModule. Chamá-lo em qualquer outro módulo, especialmente em um módulo com carregamento lento, é contrário à intenção e provavelmente produzirá um erro de tempo de execução.

Lembre-se de importar o resultado; não o adicione a nenhuma outra lista @NgModule.

Cada aplicativo tem exatamente um ponto de partida (raiz) onde o serviço de roteamento principal deve ser inicializado forRoot, enquanto as rotas para recursos "filhos" específicos devem ser registradas adicionalmente forChild. É extremamente útil para submódulos e módulos carregados lentamente que não precisam ser carregados no início do aplicativo e, como @Harry Ninh disse, eles são instruídos a reutilizar RouterService em vez de registrar o novo serviço, o que pode causar um erro de execução.

Marcin
fonte
2
Esses documentos parecem ter sido movidos, v2.angular.io/docs/ts/latest/guide/…
PeterS
1

Se appRoutes contiver o caminho para várias funções no site (admin crud, user crud, book crud) e quisermos separá-los, poderíamos simplesmente fazer isso:

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

E para rotas:

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
fonte