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 dokumentacji BrowserModule (tutaj) widzimy, że CommonModule 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 importujemy CommonModule, który eksportuje tylko wspólne dyrektywy i potoki.
  • Używamy komendy export. Każdy element zdefiniowany w tablicy declarations 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 szablonie AppComponent.

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.