angularjhipsterjquery-masonryangular-masonry

error NG8001: 'ngx-masonry' is not a known element


I'm trying to import the module ngx-masonry in my JHipster app (angluar app).

I run

npm install ngx-masonry masonry-layout --save

Then imported the module inside app.module.ts

import { NgModule, LOCALE_ID } from '@angular/core';
import { registerLocaleData } from '@angular/common';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import locale from '@angular/common/locales/en';
import { BrowserModule, Title } from '@angular/platform-browser';
import { ServiceWorkerModule } from '@angular/service-worker';
import { FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { TranslateModule, TranslateService, TranslateLoader, MissingTranslationHandler } from '@ngx-translate/core';
import { NgxWebstorageModule, SessionStorageService } from 'ngx-webstorage';
import * as dayjs from 'dayjs';
import { NgbDateAdapter, NgbDatepickerConfig } from '@ng-bootstrap/ng-bootstrap';

import { ApplicationConfigService } from 'app/core/config/application-config.service';
import './config/dayjs';
import { SharedModule } from 'app/shared/shared.module';
import { AppRoutingModule } from './app-routing.module';
import { HomeModule } from './home/home.module';
import { EntityRoutingModule } from './entities/entity-routing.module';
// jhipster-needle-angular-add-module-import JHipster will add new module here
import { NgbDateDayjsAdapter } from './config/datepicker-adapter';
import { fontAwesomeIcons } from './config/font-awesome-icons';
import { httpInterceptorProviders } from 'app/core/interceptor/index';
import { translatePartialLoader, missingTranslationHandler } from './config/translation.config';
import { MainComponent } from './layouts/main/main.component';
import { NavbarComponent } from './layouts/navbar/navbar.component';
import { FooterComponent } from './layouts/footer/footer.component';
import { PageRibbonComponent } from './layouts/profiles/page-ribbon.component';
import { ActiveMenuDirective } from './layouts/navbar/active-menu.directive';
import { ErrorComponent } from './layouts/error/error.component';
import 'jquery/dist/jquery.js';
import '@angular/animations';
import { NgxMasonryModule } from 'ngx-masonry';

@NgModule({
  imports: [
    NgxMasonryModule,
    BrowserModule,
    SharedModule,
    HomeModule,
    // jhipster-needle-angular-add-module JHipster will add new module here
    EntityRoutingModule,
    AppRoutingModule,
    // Set this to true to enable service worker (PWA)
    ServiceWorkerModule.register('ngsw-worker.js', { enabled: false }),
    HttpClientModule,
    NgxWebstorageModule.forRoot({ prefix: 'jhi', separator: '-', caseSensitive: true }),
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: translatePartialLoader,
        deps: [HttpClient],
      },
      missingTranslationHandler: {
        provide: MissingTranslationHandler,
        useFactory: missingTranslationHandler,
      },
    }),
  ],
  providers: [
    Title,
    { provide: LOCALE_ID, useValue: 'en' },
    { provide: NgbDateAdapter, useClass: NgbDateDayjsAdapter },
    httpInterceptorProviders,
  ],
  declarations: [MainComponent, NavbarComponent, ErrorComponent, PageRibbonComponent, ActiveMenuDirective, FooterComponent],
  bootstrap: [MainComponent],
})
export class AppModule {
  constructor(
    applicationConfigService: ApplicationConfigService,
    iconLibrary: FaIconLibrary,
    dpConfig: NgbDatepickerConfig,
    translateService: TranslateService,
    sessionStorageService: SessionStorageService
  ) {
    applicationConfigService.setEndpointPrefix(SERVER_API_URL);
    registerLocaleData(locale);
    iconLibrary.addIcons(...fontAwesomeIcons);
    dpConfig.minDate = { year: dayjs().subtract(100, 'year').year(), month: 1, day: 1 };
    translateService.setDefaultLang('en');
    // if user have changed language and navigates away from the application and back to the application then use previously choosed language
    const langKey = sessionStorageService.retrieve('locale') ?? 'en';
    translateService.use(langKey);
  }
}

Then I used the component inside note.component.html file:

...Other html code...
  <ngx-masonry>
  <div class="grid" ngxMasonryItem >
    <div class="grid-item card-selectable" *ngFor="let note of notes; trackBy: trackId">
      <div [routerLink]="['/note', note.id, 'edit']">
        <h5>{{note.title}}</h5>
      </div>
      </div>
    </div>
  </div>
  </ngx-masonry>
...Other html code...

I also tried importing the module directly inside my note.component.ts:

import { Component, OnInit } from '@angular/core';
import { HttpHeaders, HttpResponse } from '@angular/common/http';
import { ActivatedRoute } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';

import { INote } from '../note.model';

import { ASC, DESC, ITEMS_PER_PAGE } from 'app/config/pagination.constants';
import { NoteService } from '../service/note.service';
import { NoteDeleteDialogComponent } from '../delete/note-delete-dialog.component';
import { ParseLinks } from 'app/core/util/parse-links.service';
import { NgxMasonryModule } from 'ngx-masonry';

@Component({
  selector: 'jhi-note',
  templateUrl: './note.component.html',
})
export class NoteComponent implements OnInit {
  notes: INote[];
  isLoading = false;
  itemsPerPage: number;
  links: { [key: string]: number };
  page: number;
  predicate: string;
  ascending: boolean;
  currentSearch: string;

  constructor(
    protected noteService: NoteService,
    protected modalService: NgbModal,
    protected parseLinks: ParseLinks,
    protected activatedRoute: ActivatedRoute
  ) {
    this.notes = [];
    this.itemsPerPage = ITEMS_PER_PAGE;
    this.page = 0;
    this.links = {
      last: 0,
    };
    this.predicate = 'id';
    this.ascending = true;
    this.currentSearch = this.activatedRoute.snapshot.queryParams['search'] ?? '';
  }

  loadAll(): void {
    this.isLoading = true;
    if (this.currentSearch) {
      this.noteService
        .search({
          query: this.currentSearch,
          page: this.page,
          size: this.itemsPerPage,
          sort: this.sort(),
        })
        .subscribe(
          (res: HttpResponse<INote[]>) => {
            this.isLoading = false;
            this.paginateNotes(res.body, res.headers);
          },
          () => {
            this.isLoading = false;
          }
        );
      return;
    }

    this.noteService
      .query({
        page: this.page,
        size: this.itemsPerPage,
        sort: this.sort(),
      })
      .subscribe(
        (res: HttpResponse<INote[]>) => {
          this.isLoading = false;
          this.paginateNotes(res.body, res.headers);
        },
        () => {
          this.isLoading = false;
        }
      );
  }

  reset(): void {
    this.page = 0;
    this.notes = [];
    this.loadAll();
  }

  loadPage(page: number): void {
    this.page = page;
    this.loadAll();
  }

  search(query: string): void {
    this.notes = [];
    this.links = {
      last: 0,
    };
    this.page = 0;
    if (query && ['hashcode', 'title', 'textContent', 'color'].includes(this.predicate)) {
      this.predicate = 'id';
      this.ascending = true;
    }
    this.currentSearch = query;
    this.loadAll();
  }

  ngOnInit(): void {
    this.loadAll();
  }

  trackId(index: number, item: INote): number {
    return item.id!;
  }

  delete(note: INote): void {
    const modalRef = this.modalService.open(NoteDeleteDialogComponent, { size: 'lg', backdrop: 'static' });
    modalRef.componentInstance.note = note;
    // unsubscribe not needed because closed completes on modal close
    modalRef.closed.subscribe(reason => {
      if (reason === 'deleted') {
        this.reset();
      }
    });
  }

  createTestNotes(): void {
    this.noteService.createTestNotes().subscribe(() => {this.reset()});
  }

  deleteAllNotes(): void {
    this.noteService.deleteAllNotes().subscribe(() => {this.reset()});
  }

  protected sort(): string[] {
    const result = [this.predicate + ',' + (this.ascending ? ASC : DESC)];
    if (this.predicate !== 'id') {
      result.push('id');
    }
    return result;
  }

  protected paginateNotes(data: INote[] | null, headers: HttpHeaders): void {
    this.links = this.parseLinks.parse(headers.get('link') ?? '');
    if (data) {
      for (const d of data) {
        this.notes.push(d);
      }
    }
  }
}

But keep getting this error:

2021-10-15T09:40:50.710+0200 [ERROR] [system.err] Error: src/main/webapp/app/entities/note/list/note.component.html:73:7 - error NG8001: 'ngx-masonry' is not a known element:
2021-10-15T09:40:50.710+0200 [ERROR] [system.err] 1. If 'ngx-masonry' is an Angular component, then verify that it is part of this module.
2021-10-15T09:40:50.710+0200 [ERROR] [system.err] 2. If 'ngx-masonry' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
2021-10-15T09:40:50.711+0200 [ERROR] [system.err] 
2021-10-15T09:40:50.711+0200 [ERROR] [system.err] 73       <ngx-masonry>
2021-10-15T09:40:50.711+0200 [ERROR] [system.err]          ~~~~~~~~~~~~~
2021-10-15T09:40:50.711+0200 [ERROR] [system.err] 
2021-10-15T09:40:50.711+0200 [ERROR] [system.err]   src/main/webapp/app/entities/note/list/note.component.ts:17:16
2021-10-15T09:40:50.711+0200 [ERROR] [system.err]     17   templateUrl: './note.component.html',
2021-10-15T09:40:50.712+0200 [ERROR] [system.err]                       ~~~~~~~~~~~~~~~~~~~~~~~
2021-10-15T09:40:50.712+0200 [ERROR] [system.err]     Error occurs in the template of component NoteComponent.

My package.json includes:

"jquery": "^3.6.0",
"masonry-layout": "^4.2.2",
"ngx-infinite-scroll": "10.0.1",
"ngx-masonry": "^12.0.0",
"@types/jquery": "3.5.7",
"@types/masonry-layout": "4.2.4",

Already tried stopping the server, npm, restarting the IDE.


Solution

  • I just happened to run into a similar problem on my application. I actually fixed it by:

    1. Importing NgxMasonryModule in the module of which I'm using it in. In your case that should be the module in which note.component is declared
    2. Making sure that the parent module (which again is the module you declared note.component in) is important as a module in every parent module above. In my case I didn't import the module I'm using the package in as a module, but as a component.

    Resulting in:

    note.module (which is the module in which note.component is declared)

    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common'; 
    import { NoteComponent } from './content-grid.component';
    import { NgxMasonryModule } from 'ngx-masonry';
    
    @NgModule({
      declarations: [
        NoteComponent
      ],
      imports: [
        CommonModule,
        NgxMasonryModule
      ]
    })
    
    export class NoteModule { 
    }
    

    In case you use NoteModule in another module, make sure that both NoteModule as that other module get imported as modules, and not as components.

    app.module

    ...
    @NgModule({
      declarations: [
        ...
      ],
      imports: [
        NoteModule
      ],
    ...