
Привет, я фронтенд-разработчик в Skyeng. При переходе с шестой версии Storybook на седьмую встретилась ошибка «Providers from the BrowserModule have already been loaded. If you need access to common directives such as NgIf and NgFor, import the CommonModule instead».
В этой небольшой статье объясняю, почему возникает баг и как его исправить.
Почему возникает ошибка
BrowserModule экспортирует инфраструктуру для всего Angular-приложения, и его стоит импортировать только один раз. Если повторно импортировать такой модуль, например, в lazy-loaded feature-модуль, то получим ошибку о необходимости использования CommonModule вместе целого BrowserModule.
По такому же принципу должны импортироваться и другие application-wide провайдеры в приложении.
Пример с ошибкой:
// feature.module.ts
@NgModule({
imports: [BrowserModule], // здесь импортировать модуль не нужно
…
})
export class FeatureModule {
…
// app.module.ts
@NgModule({
imports: [BrowserModule], // AppModule уже импортирует BrowserModule
…
})
export class AppModule {
…
Так как FeatureModule содержит повторный импорт BrowserModule, то получим ошибку: «Providers from the BrowserModule have already been loaded. If you need access to common directives such as NgIf and NgFor, import the CommonModule instead».
Что касается Storybook, то при аналогичном импорте модуля в метаданных так же возникнет ошибка:
// storybook-helper.module.ts
@NgModule({
imports: [BrowserModule], // первая ошибка: StorybookHelperModule не должен импортировать BrowserModule
…
})
export class StorybookHelperModule {
…
// test.stories.ts
export default {
title: 'TestComponent',
component: TestComponent,
decorators: [
moduleMetadata({
imports: [StorybookHelperModule], // вторая ошибка: импортируем BrowserModule, находящийся в StorybookHelperModule
}),
],
Даже если компонент в Storybook зависит от BrowserAnimationsModule / BrowserModule, такой модуль не стоит импортировать с помощью других модулей или напрямую при настройке Storybook. |
В моём случае была ошибка именно в наличии импорта BrowserModule во вспомогательном модуле для сторибука. Но даже если BrowserModule импортирован напрямую в метаданных стори, то в консоли тоже будет сообщение о некорректном импорте BrowserModule, но в виде предупреждения.
Как исправить ошибку
Начиная с 7-й версии, Storybook использует новый bootstrapApplication API.
Все application-wide провайдеры, включая BrowserModule и BrowserAnimationsModule, теперь настраиваются с помощью applicationConfig декоратора:
// test.stories.ts
export default {
title: 'TestComponent',
component: TestComponent,
decorators: [
applicationConfig({ // новый декоратор
providers: [importProvidersFrom(BrowserAnimationsModule)],
}),
],
То же самое касается конфигурируемых модулей (знакомый всем паттерн forRoot). Их тоже теперь указываем через applicationConfig декоратор.
Было:
export default {
title: 'TestComponent',
component: TestComponent,
decorators: [
moduleMetadata({
imports: [SomeModule.forRoot()],
}),
],
Стало:
export default {
title: 'TestComponent',
component: TestComponent,
decorators: [
applicationConfig({ // теперь используем новый декоратор
providers: [importProvidersFrom(SomeModule.forRoot())],
}),
]
Вывод
Стоит помнить про то, что содержит в себе модуль, для чего он нужен и как его правильно импортировать. Теперь с 7-й версии Storybook нужно использовать applicationConfig декоратор, если требуется использовать application-wide провайдеры или конфигурируемые модули.
Если остались вопросы — пишите, разберемся вместе!