Custom directive not recognized

Asked by At

I've created a custom directive called holdable.

import { Directive, EventEmitter, Output, HostListener,  } from '@angular/core';

import { Observable, Subject, interval } from 'rxjs';
import { takeUntil, tap, filter } from 'rxjs/operators';

@Directive({
  selector: '[appHoldable]'
})
export class HoldableDirective {

  @Output() holdTime: EventEmitter<number> = new EventEmitter();

  state: Subject<string> = new Subject();

  cancel: Observable<string>;

  constructor() { 
    this.cancel = this.state.pipe(
      filter(v => v === 'cancel'),
      tap(v => {
        console.log('Stopped holding.');
        this.holdTime.emit(0);
      }
      )
    );
  }

  @HostListener('mouseup', ['$event'])
  @HostListener('mouseleave', ['$event'])
  onExit(){
    this.state.next('cancel');
  }

  @HostListener('mousedown', ['$event'])
  onHold(){
    console.log('Started holding.');
    this.state.next('start');
    const n = 100;
    interval(n).pipe(
      takeUntil(this.cancel),
      tap(v => {
        this.holdTime.emit(v * n);
      })
    )
    .subscribe()
  }
}

It it registered in the AppModule, because I want to use it in the Overall application. For example in my module called CashRegisterModule:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { CashRegisterRoutingModule } from './cash-register-routing.module';

import { CashRegisterComponent } from './cash-register.component';
import { UIModule } from '../UI/ui.module';
import { ScannerComponent } from './steps/scanner/scanner.component';
import { ArticlePreviewComponent } from './steps/article-preview/article-preview.component';
import { OverviewComponent } from './steps/overview/overview.component';
import { PaymentComponent } from './steps/payment/payment.component';

@NgModule({
  declarations: [
    CashRegisterComponent, 
    ScannerComponent, 
    ArticlePreviewComponent, OverviewComponent, PaymentComponent
  ],
  imports: [
    CommonModule,
    CashRegisterRoutingModule,
    UIModule
  ]
})
export class CashRegisterModule { }

And this module is also registered in the app.module.ts file Looking like this

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { HttpClientModule } from '@angular/common/http';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { DashboardModule } from './views/dashboard/dashboard.module';
import { UIModule } from './UI/ui.module';
import { ArticleModule } from './views/article/article.module';
import { ArticleService } from './services/article.service';
import { UiService } from './services/ui.service';
import { CashRegisterModule } from './cash-register/cash-register.module';
import { DataService } from './services/data.service';
import { HoldableDirective } from './directives/holdable.directive';

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
    HoldableDirective,
  ],
  imports: [
    CommonModule,
    BrowserModule,
    HttpClientModule,
    RouterModule,
    ReactiveFormsModule,
    DashboardModule,
    UIModule,
    ArticleModule,
    CashRegisterModule,
    AppRoutingModule
  ],
  providers: [
    ArticleService,
    UiService,
    DataService
  ],
  bootstrap: [
    AppComponent
  ],

})
export class AppModule { }

I use the directive in the button inside the PaymentComponent of the CashRegisterComponent

<button *ngFor="let moneyType of state.moneyToRender"
      appHoldable (holdTime)="calculateGivenMoney(moneyType, $event)"
      >{{moneyType.toFixed(2)}} €
    </button>

calculateGivenMoney(…) will be initialized like so

calculateGivenMoney(amount: number, /*operation: string,*/ holdTime: number): number {
    console.log('ich werde geklickt.' + holdTime)
    if(holdTime > 1000){
      this.state.givenMoney = this.state.givenMoney - amount;
    } else {
      this.state.givenMoney = this.state.givenMoney + amount;
    } return this.state.givenMoney;
}

But neither of the print Statements are working…

Does anyone know what isn't working inside my Code?

Thank you very much for your help and have a nice day!

2 Answers

1
ashish.gd On Best Solutions

There are three steps to get this working. And it applies to almost all Angular elements like components, directives, pipes etc.

  1. Declare the directive in a particular module Eg: SharedModule
  2. Export the directive from that module i.e SharedModule
  3. Import the module in the parent module of the component where you want to use it. Eg: SomeOtherModule

Example code:

@NgModule({
    declarations: [HoldableDirective],
    exports: [HoldableDirective]
})
export class SharedModule {}


@NgModule({
    declarations: [
        SomeChildComponent
    ],
    imports: [
        ... other modules,
        SharedModule
    ]
})
export class SomeOtherModule

This way the directive with its respective selector will be available to all members of SomeOtherModule.

Note: It is important to export module members if you want them to be accessible from other modules. Think of it like a public API for the module.

1
OneLunch Man On

It looks like you are thinking of how providers work in modules, kind of a "top down" approach where any module down stream of the module with the provider can use it.

To use components or directives within an individual module, they need to be imported or declared in that individual module. My preference is to create a module for that directive or smaller groups of functionality, export the reusable pieces, and import them where they are needed (see the Angular Material library for an example).

Using that with your example:

@NgModule({
  declarations: [
    HoldableDirective,
  ],
  exports: [
    HoldableDirective,
  ],
  imports: [
    CommonModule,
    ...
  ]
})
export class HoldableModule { }


@NgModule({
  declarations: [
    CashRegisterComponent, 
    ScannerComponent, 
    ArticlePreviewComponent, OverviewComponent, PaymentComponent,

  ],
  imports: [
    CommonModule,
    CashRegisterRoutingModule,
    UIModule,
    HoldableModule, // <-- useing that common module here

  ]
})
export class CashRegisterModule { }