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

111

Jakie są różnice między tymi dwoma i jakie są przypadki użycia dla każdego z nich?

W docs nie są dokładnie pomocne:

forRoot tworzy moduł, który zawiera wszystkie dyrektywy, podane trasy i samą usługę routera.

forChild tworzy moduł, który zawiera wszystkie dyrektywy i podane trasy, ale nie zawiera usługi routera.

Moje niejasne przypuszczenie jest takie, że jeden jest przeznaczony dla modułu „głównego”, a drugi dla dowolnych zaimportowanych modułów (ponieważ mieliby już dostępną usługę z modułu głównego), ale tak naprawdę nie mogę wymyślić przypadku użycia.

VSO
źródło
1
Czy mógłbyś bardziej szczegółowo opisać to, czego nie rozumiesz? Cytat, który zawarłeś, dosłownie mówi ci, jaka jest różnica.
jonrsharpe
2
Nie rozumiem, jaki jest sens używania .forChild (). Kiedy chciałbym otrzymać dyrektywy i trasy bez usługi? W międzyczasie odpowiedz na pytanie, które usunąłeś z postu ...
VSO
18
Powinien być tylko jeden RouterServicedla jednej aplikacji Angular2. forRootzainicjuje tę usługę i zarejestrować go do DI razem z jakimś config trasy, podczas gdy forChildzarejestruje tylko dodatkowe configs trasy i powiedzieć Angular2 aby ponownie wykorzystać RouterService, które forRootstworzył.
Harry Ninh
@HarryNinh: Dzięki - właśnie tego szukałem. Kiedy jednak chciałbyś zarejestrować dodatkowe trasy poza pierwszą rejestracją? To trochę głupie. Domyślam się, że nie ma możliwości dynamicznego tworzenia tras.
VSO,
1
zobacz to przez Victora autora routera kątowego.
nikhil mehta

Odpowiedzi:

123

Gorąco zachęcam do przeczytania tego artykułu:

Moduł z dostawcami

Podczas importu modułu zwykle używasz odwołania do klasy modułu:

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

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

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

W ten sposób wszyscy dostawcy zarejestrowani w module Azostaną dodani do wtryskiwacza root i będą dostępni dla całej aplikacji.

Ale jest inny sposób zarejestrowania modułu u dostawców takich jak ten:

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

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

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

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

Ma to takie same konsekwencje, jak poprzednie.

Prawdopodobnie wiesz, że leniwie ładowane moduły mają swój własny iniektor. Więc przypuśćmy, że chcesz zarejestrować się, AServiceaby być dostępnym dla całej aplikacji, ale niektóre, BServiceaby były dostępne tylko dla leniwie ładowanych modułów. Możesz refaktoryzować swój moduł w następujący sposób:

@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

Teraz BServicebędzie dostępna tylko dla leniwych załadowanych modułów podrzędnych i AServicebędzie dostępna dla całej aplikacji.

Możesz przepisać powyższe jako wyeksportowany moduł w następujący sposób:

@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

Jak to się ma do RouterModule?

Załóżmy, że dostęp do nich odbywa się za pomocą tego samego tokena:

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

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

Dzięki oddzielnym konfiguracjom, gdy żądasz tokenod leniwego załadowanego modułu, otrzymasz BServicetak, jak planowano.

RouterModule używa ROUTEStokena, aby uzyskać wszystkie trasy właściwe dla modułu. Ponieważ chce, aby trasy specyficzne dla leniwie ładowanego modułu były dostępne w tym module (analogia do naszego BService), używa innej konfiguracji dla leniwie ładowanych modułów potomnych:

static forChild(routes: Routes): ModuleWithProviders {
    return {
        ngModule: RouterModule, 
        providers: [{provide: ROUTES, multi: true, useValue: routes}]
    };
}
Max Koretskyi
źródło
3
czyli innymi słowy, powinniśmy wywołać metodę forChild () w modułach funkcji, ponieważ forRoot () została już wywołana w module głównym i wszystkie niezbędne usługi zostały dodane. Więc ponowne wywołanie funkcji Root () doprowadzi do nieprzewidywalnych stanów?
Wachburn
7
@Wachburn, wywołanie forRootmodułu ładowanego z opóźnieniem spowoduje utworzenie nowych wystąpień wszystkich usług globalnych w wtryskiwaczu modułu ładowanego z opóźnieniem. Tak, doprowadzi to do nieprzewidywalnych rezultatów. Przeczytaj także ten artykuł Unikanie częstych nieporozumień z modułami w Angular
Max Koretskyi
1
@Willwsharp, dlaczego?
Max Koretskyi,
1
Całkowicie to rozumiem. Ale jaka jest różnica między Routermodule.foRoot, VS Routermodule.forChild? forRoot i forChild to tylko statyczne metody, które zwracają obiekt na podstawie przekazanej wartości. Więc w module aplikacji, dlaczego nie mogę używać forChild? dlaczego jego błąd rzucania? Dlaczego nie mogę używać wielu forRoot?
Subhadeep
2
Czasami dobrze jest przejść od razu do rzeczy z odpowiedziami. przeładowanie informacjami w większości przypadków prowadzi do większego zamieszania.
Adépòjù Olúwáségun
33

Myślę, że odpowiedzi są prawidłowe, ale myślę, że czegoś brakuje.
To, czego brakuje, to „dlaczego i co rozwiązuje?”.
Ok zaczynajmy.

Najpierw wspomnijmy o kilku informacjach:

Wszystkie moduły mają dostęp do usług root.
Więc nawet leniwie ładowane moduły mogą korzystać z usługi dostarczonej w app.module.
Co się stanie, jeśli leniwie ładowany moduł zapewni sobie usługę, którą dostarczył już moduł aplikacji? będą 2 wystąpienia.
To nie jest problem, ale czasami jest .
Jak możemy to rozwiązać? po prostu nie importuj modułu z tym dostawcą do modułów ładowanych z opóźnieniem.

Koniec opowieści.

To ^ miało tylko pokazać, że moduły ładowane z opóźnieniem mają swój własny punkt wstrzykiwania (w przeciwieństwie do modułów ładowanych z opóźnieniem).

Ale co się stanie, gdy udostępniony (!) Moduł zadeklaruje providers, a ten moduł zostanie zaimportowany przez lazy i app.module ? Znowu, jak powiedzieliśmy, dwa przypadki.

Jak więc możemy rozwiązać ten problem we współdzielonym module POV? Potrzebujemy sposobu, aby nie używać providers:[]! Czemu? ponieważ zostaną one automatycznie zaimportowane zarówno do zużywającego lazy, jak i do modułu app.module, a nie chcemy tego, ponieważ widzieliśmy, że każdy będzie miał inną instancję.

Cóż, okazuje się, że możemy zadeklarować współdzielony moduł, który nie będzie miał providers:[], ale nadal będzie dostarczał dostawców (przepraszam :))

W jaki sposób? Lubię to :

wprowadź opis obrazu tutaj

Uwaga, brak dostawców.

Ale

  • co się stanie teraz, gdy app.module zaimportuje udostępniony moduł z punktem widzenia usługi? NIC.

  • co się stanie teraz, gdy leniwy moduł zaimportuje moduł współdzielony z POV usługi? NIC.

Wchodzenie do mechanizmu ręcznego poprzez konwencję:

Zauważysz, że dostawcy na zdjęciach mają service1iservice2

To pozwala nam na importowanie service2dla leniwie ładowanych modułów i service1dla nieleniwych modułów. ( kaszel ... router ... kaszel )

BTW, nikt nie powstrzymuje Cię przed wywołaniem forRootw leniwym module. ale będziesz mieć 2 instancje, ponieważ app.moduleteż powinieneś to robić - więc nie rób tego w leniwych modułach.

Ponadto - jeśli app.modulewywołuje forRoot(i nikt nie dzwoni forchild) - w porządku, ale iniektor root będzie miał tylko service1. (dostępne dla wszystkich aplikacji)

Dlaczego więc tego potrzebujemy? Powiedziałbym :

Pozwala modułowi współdzielonemu, aby móc podzielić jego różnych dostawców w celu użycia ich z chętnymi modułami i leniwymi modułami - za pośrednictwem forRooti forChildkonwencji. Powtarzam: konwencja

Otóż ​​to.

CZEKAJ !! ani słowa o singletonie ?? więc dlaczego wszędzie czytam singletona?

Cóż - jest to ukryte w zdaniu powyżej ^

Pozwala modułowi współdzielonemu, aby móc podzielić jego różnych dostawców w celu użycia z modułami chętnymi i leniwymi - za pośrednictwem forRoot i forChild .

Konwencja (!!!) pozwala to być pojedyncza - albo dokładniej - jeśli nie będą postępować zgodnie z konwencją - będziesz NIE dostać pojedyncza.
Więc jeśli załadować tylko forRootw app.module , a następnie dostać tylko jedną instancję, bo tylko powinien wezwać forRootgo w app.module.
Swoją drogą - w tym miejscu można o tym zapomnieć forChild. leniwie ładowany moduł nie powinien / nie dzwoni forRoot- więc jesteś bezpieczny w POV singletona.

forRoot i forChild to nie jeden nierozerwalny pakiet - po prostu nie ma sensu wywoływać Roota, który oczywiście będzie ładowany tylko app.module bez możliwości leniwych modułów, ma własne usługi, bez tworzenia nowych usług-które-powinny-być -singel.

Konwencja ta daje fajną możliwość zwaną forChild- konsumować "usługi tylko dla leniwie ładowanych modułów".

Oto wersja demonstracyjna Dostawcy root zwracają liczby dodatnie, moduły ładowane z opóźnieniem dają liczby ujemne.

Royi Namir
źródło
1
1. Co to jest POV? 2. Stackblitz już nie działa.
Nicholas K
@NicholasK ten stackblitz zawsze psuje wszystko po jakimś czasie. spróbuję wgrać nową wersję
Royi Namir
26

Dokumentacja jasno określa, jaki jest cel tego rozróżnienia tutaj: https://angular.io/docs/ts/latest/guide/ngmodule.html#!#core-for-root

Wywołaj forRoot tylko w głównym module aplikacji, AppModule. Wywołanie go w jakimkolwiek innym module, szczególnie w module ładowanym z opóźnieniem, jest sprzeczne z intencją i prawdopodobnie spowoduje błąd w czasie wykonywania.

Pamiętaj, aby zaimportować wynik; nie dodawaj go do żadnej innej listy @NgModule.

Każda aplikacja ma dokładnie jeden punkt początkowy (root), w którym powinna zostać zainicjowana główna usługa routingu forRoot, natomiast trasy dla poszczególnych funkcji „podrzędnych” powinny być dodatkowo rejestrowane za pomocą forChild. Jest to niezwykle przydatne w przypadku modułów podrzędnych i leniwie ładowanych modułów, które nie muszą być ładowane przy starcie aplikacji, a jak powiedział @Harry Ninh, powiedziano im, aby ponownie użyli RouterService zamiast rejestracji nowej usługi, co może spowodować błąd w czasie wykonywania.

Marcin
źródło
2
Te dokumenty wydają się być przeniesione, v2.angular.io/docs/ts/latest/guide/ ...
PeterS Stycznia
1

Jeśli appRoutes zawiera ścieżkę do różnych funkcji w serwisie (admin crud, user crud, book crud) i chcemy je oddzielić, możemy to zrobić po prostu:

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

A dla tras:

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
źródło