Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BrowserAnimationsModule does not work when imported in core module

I divided bigger app into modules (feature modules, core module and shared module). I am using Angular Material and therefore I imported BrowserAnimationsModule. I placed it in Shared Module and everything works fine, but the problem is arising when I want to lazy load some feature modules - then I have error about double import of BrowserModules. I know that BrowserAnimationsModule should be imported by Core Module, but when I am trying to do that, I get following error:

Uncaught Error: Template parse errors:
Can't bind to 'ngForOf' since it isn't a known property of 'mat-option'.
1. If 'mat-option' is an Angular component and it has 'ngForOf' input, then verify that it is part of this module.
2. If 'mat-option' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
3. To allow any property add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component. ("' | translate}}" (change)="change()" [(ngModel)]="actualFilter" name="food">
          <mat-option [ERROR ->]*ngFor="let filterColumn of displayedColumns" [value]="filterColumn">
            {{filterColumn | t"): ng:///ChargesModule/ChargesComponent.html@9:22
Property binding ngForOf not used by any directive on an embedded template. Make sure that the property name is spelled correctly and all directives are listed in the "@NgModule.declarations". ("COLUMNFILTER' | translate}}" (change)="change()" [(ngModel)]="actualFilter" name="food">
          [ERROR ->]<mat-option *ngFor="let filterColumn of displayedColumns" [value]="filterColumn">
            {{filt"): ng:///ChargesModule/ChargesComponent.html@9:10

Below I am presenting the modules: app.module.ts:

    @NgModule({
      declarations: [
        AppComponent
      ],
      imports: [
        BrowserModule,
        HomeModule,
        ChargesModule,
        ModeratorPanelModule,
        CoreModule
      ],
      providers: [],
      bootstrap: [AppComponent]
    })

    export class AppModule { }

core.module.ts:

 @NgModule({
      imports: [
        HttpModule,
        HttpClientModule,
        AppRoutingModule,
        SharedModule,
        BrowserAnimationsModule
      ],
      declarations: [
        SidenavComponent,
        HeaderComponent,
        ErrorPageComponent,
        PageNotFoundComponent,
        LoginComponent,
        UpdatePanelComponent,
        DialogConfirmCancelComponent,
        ConfirmMessageComponent,
      ],
      exports: [
        HttpModule,
        HttpClientModule,
        SharedModule,
        HeaderComponent,
        SidenavComponent,
        AppRoutingModule,
        BrowserAnimationsModule
      ],
      providers: [
        AuthService, AuthGuard, HttpAuthService, DictionariesService,
        DictionaryItemFactory, CanDeactivateGuard, { provide: MatPaginatorIntl, useClass: CustomPaginator }
      ],
      entryComponents: [DialogConfirmCancelComponent, ConfirmMessageComponent]
    })
    export class CoreModule { }

shared.module.ts:

export function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http);
}
@NgModule({
  imports: [
    CommonModule,
    MaterialModule,
    FlexLayoutModule,
    CdkTableModule,
    FormsModule,
    NgbModule.forRoot(),
    TimepickerModule.forRoot(),
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient]
      }
    })
  ],
  exports: [
    NgbModule,
    TimepickerModule,
    TranslateModule,
    CurrenciesComponent,
    MaterialModule,
    FlexLayoutModule,
    CdkTableModule,
    FormsModule,
    DropDownListComponent,
    DictionariesComponent
  ],
  declarations: [
    DictionariesComponent,
    DropDownListComponent
  ],
  providers: []
})
export class SharedModule { }

In MaterialModule, I have imported all material modules together with CommonModule.

like image 883
Pawel Avatar asked Dec 14 '22 20:12

Pawel


1 Answers

If you came across an error like

Can't bind to 'ngForOf' since it isn't a known property

then you should be aware that it either:

  • has to do with undeclared @Input;

  • or you have some problem with @NgModule configuration

Since ngForOf is a built-in directive let's analyze what's the problem with your @NgModule configuration.

First of all we need to determine where this error comes from. It's easy to see from the error message:

{{filterColumn | t"): ng:///ChargesModule/ChargesComponent.html@9:22

It should be clear that we're getting error for ChargesComponent that is part of ChargesModule. I believe ChargesModule declaration looks like:

charges.module.ts

@NgModule({
  imports: [
    SharedModule,
    ...
  ],
  declarations: [
    ChargesComponent,
    ...
  ]
})
export class ChargesModule {}

Now if you haven't read my previous answer Angular 2 Use component from another module i strongly suggest you to do it. Make sure you understand how one component/directive/pipe can be used in other module. If you do then just export CommonModule from SharedModule, if you are still in doubt then read on...

The main rule here is

Since we need to use ngForOf directive within ChargesComponent template, that is part of ChargesModule, then this directive should be part of directives in transitive ChargesModule.

How those directives are collected?

               ChargesModule.directives 
                         + 
      exported directives from imported @NgModules
                        ||
         ChargesModule.transitiveModule.directives

At first glance we would declare ngForOf directive in ChargesModule declarations array but ngForOf is already declared in CommonModule and as directive has to be a part of only one module we can't do that.

Therefore continue looking ngForOf directives among imported @NgModules. Well, we imported SharedModule Let's see which directives it exports:

shared.module.ts

@NgModule({
  ...,
  exports: [
    NgbModule,
    TimepickerModule,
    TranslateModule,
    CurrenciesComponent,
    MaterialModule,
    FlexLayoutModule,
    CdkTableModule,
    FormsModule,
    DropDownListComponent,
    DictionariesComponent
  ]
})
export class SharedModule { }

SharedModule exports all directives that are either

exported from @NgModules

 NgbModule,
 TimepickerModule,
 TranslateModule,
 MaterialModule,
 FlexLayoutModule,
 CdkTableModule,
 FormsModule

This means that if NgbModule has exported directives then they will be added to exported directives of SharedModule.

or just directives included in exports array

 CurrenciesComponent,
 DropDownListComponent,
 DictionariesComponent

As we don't see here CommonModule we can assume we will get the error Can't bind to 'ngForOf'

To solve it we have to add CommonModule to exports array and all should work as expected.

The good question is

However, I do not understand why this problem occured only after moving of BrowserAnimationsModule from Shared to Core module. Can you explain that?

It's hard to understand without knowing how BrowserAnimationsModule looks like. Let's achieve enlightenment by looking at source code:

@NgModule({
  exports: [BrowserModule],
  providers: BROWSER_ANIMATIONS_PROVIDERS,
})
export class BrowserAnimationsModule {
}

and look at BrowserModule:

@NgModule({
  ...
  exports: [CommonModule, ApplicationModule]
})
export class BrowserModule {

It’s immediately obvious that if we export BrowserAnimationsModule module from SharedModule we also export directives from CommonModule since:

SharedModule.transitiveModule.directives
               /\
               ||
   BrowserAnimationsModule exports 
               /\
               ||
     BrowserModule that exports
               /\
               ||   
          CommonModule

So after moving BrowserAnimationModule to CoreModule your SharedModule doesn't export ngForOf directive anymore and that's why you're getting the error.

like image 123
yurzui Avatar answered May 08 '23 04:05

yurzui