How to extend NgbDropdown or write a wrapper for it?

Viewed 646

Basically for all the dropdowns I want to run myOpenFunction() every time a NgbDropDown opens and run myCloseFunction() every time a dropdown closes?

I was thinking that I could write a custom directive that extends NgbDropDown and run a function before and after dropdown. something like this:

<div myCustomDropdown> <!-- using my own directive instead of ngbDropdown -->
  <button  ngbDropdownToggle>Toggle dropdown</button>
  <div ngbDropdownMenu>
    <button ngbDropdownItem>Action - 1</button>
    <button ngbDropdownItem>Another Action</button>
  </div>
</div>

What I have tried so far based on this here

@Directive({
  selector: '[myCustomDropdown]'
})
export class DropdownDirective extends NgbDropdown implements OnDestroy {


  constructor(_changeDetector: ChangeDetectorRef, config: NgbDropdownConfig, @Inject(DOCUMENT)  _document: any,
              _ngZone: NgZone, _elementRef: ElementRef<HTMLElement>, _renderer: Renderer2,
              @Optional() ngbNavbar: NgbNavbar) {
    super(_changeDetector, config, _document, _ngZone, _elementRef, _renderer, ngbNavbar);
  }


  ngOnDestroy() {
    super.ngOnDestroy();
  }

}

which throws the error

    core.js:6185 ERROR Error: Uncaught (in promise): NullInjectorError: R3InjectorError(TestModule)[NgbDropdown -> NgbDropdown -> NgbDropdown -> NgbDropdown]: 
  NullInjectorError: No provider for NgbDropdown!
3 Answers

Another aproach is create a directive and use HostBinding

@Directive({
  selector: '[extend-dropdown]'
})
export class ExtendDropdown {
  @Input('extend-dropdown')func:(boolean)=>any;
  @HostListener('openChange',['$event']) openChange(event)
  {
    console.log('inner function',event)
    this.func && this.func(event);
  }
}

You use as

<div ngbDropdown [extend-dropdown]="open" class="d-inline-block">
  ...
</div>

//If you has a function in .ts
open(event)
{
   console.log("from main",event)
}

or

<div ngbDropdown extend-dropdown class="d-inline-block">
  ...
</div>

See stackblitz

you can just create a wrap compoenent for dropdown with all configration then you can useit everywhere as ngbDropdown

dropdown

<div ngbDropdown class="d-inline-block">
    <button class="btn btn-outline-primary" [id]="id" 
           ngbDropdownToggle>Toggle dropdown</button>
    <div ngbDropdownMenu [attr.aria-labelledby]="id">
        <button 
               *ngFor="let item of items" 
               ngbDropdownItem 
               (click)="clickHandler(item.action)" >
           {{item.label}}
         </button>
    </div>
</div>

component

export class DropdownComponent implements OnInit {
  @Input() items =[];
  constructor() { }

  clickHandler(action){
    if (action){
      action()
    }
  }
}

now you have a reusable compoennt base of ngbDropdown can accept qan items list with an callback

app template

<app-dropdown [items]="dropDownItems"></app-dropdown>

demo

inspired by @Eliseo answer , so far ngbDropdown the directive has a method called openChange will tell the open sate base of boolen value we can create a new event like open and close and emit the value base of the open state

ExtendDropdownDirective

@Directive({
  selector: "[extend-dropdown]"
})
export class ExtendDropdownDirective {
  @Output() open: EventEmitter<any> = new EventEmitter();
  @Output() close: EventEmitter<any> = new EventEmitter();

  @HostListener("openChange", ["$event"]) openChangeHandler(state: boolean) {
    if (state) {
      this.open.emit();
    } else {
      this.close.emit();
    }
  }
}

now we can use it like this

div ngbDropdown extend-dropdown class="d-inline-block" (open)="open()" (close)="close()">
        ...
</div>

demo

Related