Moduły funkcjonalne
W naszym poprzednim przykładzie nasz moduł główny miał komponent, potok i usługę, której jedynym celem jest obsługa kart kredytowych. Spróbujmy wyodrębnić te trzy elementy do ich własnego modułu funkcjonalnego , a następnie zaimportujemy go do naszego modułu głównego. Pierwszym krokiem jest utworzenie dwóch folderów, aby odróżnić elementy należące do modułu głównego od elementów należących do modułu funkcjonalnego.
.
├── app
│ ├── app.component.ts
│ └── app.module.ts
├── credit-card
│ ├── credit-card-mask.pipe.ts
│ ├── credit-card.component.ts
│ ├── credit-card.module.ts
│ └── credit-card.service.ts
├── index.html
└── main.ts
Zwróć uwagę, jak każdy folder ma swój własny plik modułu: app.module.ts i credit-card.module.ts. Skupmy się na tym drugim.
credit-card/credit-card.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CreditCardMaskPipe } from './credit-card-mask.pipe';
import { CreditCardService } from './credit-card.service';
import { CreditCardComponent } from './credit-card.component';
@NgModule({
imports: [CommonModule],
declarations: [
CreditCardMaskPipe,
CreditCardComponent
],
providers: [CreditCardService],
exports: [CreditCardComponent]
})
export class CreditCardModule {}
Nasza funkcja CreditCardModule
jest bardzo podobna do głównegoAppModule
z kilkoma ważnymi różnicami:
- Nie importujemy
BrowserModule
, aleCommonModule
. W dokumentacjiBrowserModule
(tutaj) widzimy, żeCommonModule
jest eksportowane wraz z wieloma innymi usługami, które pomagają w renderowaniu aplikacji Angular. Usługi te łączą nasz moduł główny z konkretną platformą (przeglądarką), ale chcemy, aby nasze moduły funkcjonalne były niezależne od platformy. Dlatego importujemyCommonModule
, który eksportuje tylko wspólne dyrektywy i potoki. - Używamy komendy
export
. Każdy element zdefiniowany w tablicydeclarations
jest domyślnie prywatny . Powinniśmy eksportować tylko to, co inne moduły w naszej aplikacji potrzebują do wykonywania swojej pracy. W naszym przypadku jest to tylkoCreditCardComponent
, ponieważ jest on używany w szablonieAppComponent
.
app/app.component.ts
...
@Component({
...
template: `
...
<app-credit-card></app-credit-card>
`
})
export class AppComponent {}
Przechowujemy
CreditCardMaskPipe
jako prywatny, ponieważ jest on używany tylko wewnątrzCreditCardModule
i żaden inny moduł nie powinien go używać bezpośrednio.
Możemy teraz zaimportować ten moduł funkcji do naszego uproszczonego modułu głównego.
app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { CreditCardModule } from '../credit-card/credit-card.module';
import { AppComponent } from './app.component';
@NgModule({
imports: [
BrowserModule,
CreditCardModule
],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule { }
W tym momencie skończymy i nasza aplikacja zachowuje się zgodnie z oczekiwaniami.
Zobacz przykład [014]
Usługi i moduły opóźnione ładowanie
Podczas gdy komponenty, pipes i dyrektywy są ograniczone do modułów, chyba że są eksportowane jawnie, usługi są globalnie dostępne, chyba że moduł jest ładowany z opóźnieniem [Lazy Load].
Trudno to początkowo zrozumieć, więc spróbujmy zobaczyć, co dzieje się z CreditCardService
w naszym przykładzie. Najpierw zauważ, że usługa nie znajduje się w tablicy exports
, ale w tablicyprovider
. Dzięki tej konfiguracji nasza usługa będzie dostępna wszędzie, nawet w AppComponent
, który pochodzi z innego modułu.
Kiedy moduł jest ładowany z opóźnieniem, Angular stworzy instancję naszej usługi i jej wstrzykiwacz.
Wyobraźmy sobie przez chwilę, że nasz moduł CreditCardModule
jest skonfigurowany tak, aby był ładowany z opóźnieniem. Przy obecnej konfiguracji, gdy aplikacja jest bootstrapowana, a nasz moduł główny jest ładowany do pamięci, instancja CreditCardService
(singleton) zostanie dodana do wstrzykiwacza głównego. Ale kiedy CreditCardModule
jest załadowany z opóźnieniem - wstrzykiwacz zostanie utworzony dla tego modułu z nową instancją CreditCardService
. W tym momencie mamy hierarchiczne wstrzykiwanie z dwoma instancjami tej samej usługi, co zwykle nie jest tym, czego chcemy.
Pomyśl na przykład o usłudze uwierzytelniającej. Chcemy mieć tylko jeden singleton w całej aplikacji, bez względu na to, czy nasze moduły są ładowane podczas ładowania początkowego czy z opóźnieniem. Aby mieć dostęp do usługi modułu dodatkowego, dostarczanej przez wstrzykiwacza głównego, musimy zastosować inne podejście.
credit-card/credit-card.module.ts
import { NgModule, ModuleWithProviders } from '@angular/core';
/* ...other imports... */
@NgModule({
imports: [CommonModule],
declarations: [
CreditCardMaskPipe,
CreditCardComponent
],
exports: [CreditCardComponent]
})
export class CreditCardModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: CreditCardModule,
providers: [CreditCardService]
}
}
}
Inaczej niż poprzednio, nie umieszczamy naszej usługi bezpośrednio w providers
dostawcy NgModule
. Tym razem definiujemy statyczną metodę o nazwie forRoot
, w której definiujemy moduł i usługę, którą chcemy eksportować.
Dzięki tej nowej składni nasz moduł główny jest nieco inny.
app/app.module.ts
/* ...imports... */
@NgModule({
imports: [
BrowserModule,
CreditCardModule.forRoot()
],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule { }
Można dostrzec różnicę? Nie importujemy bezpośrednio CreditCardModule
, zamiast tego importujemy obiekt zwracający metody forRoot
, razem z CreditCardService
. Chociaż ta składnia jest nieco bardziej skomplikowana niż oryginalna, gwarantuje nam to, że tylko jedno wystąpienie CreditCardService
zostanie dodane do modułu głównego. Po załadowaniu modułu CreditCardModule
(nawet w przypadku opóźnionego ładowania) do wstrzykiwacza nie zostanie dodane żadne nowe wystąpienie tej usługi.
Zobacz przykład [015]
Zawsze używaj składni forRoot
podczas eksportowania usług z modułów funkcjonalnych, chyba że masz bardzo specjalną potrzebę, która wymaga wielu instancji na różnych poziomach drzewa wstrzykiwania zależności.