2

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!
azerafati
  • 18,215
  • 7
  • 67
  • 72

3 Answers3

2

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

Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • The component-wrapper solution is more abstract and lets me easily switch to another dropdown library later if needed. But this is just perfect. – azerafati Apr 08 '20 at 09:29
1

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

Muhammed Albarmavi
  • 23,240
  • 8
  • 66
  • 91
  • Yes, that's the obvious solution in Angular realm. I am actually going for this now, but my dropdowns' templates are totally different, meaning @Input should receive an html template instead of an array or somehow insert my templates into the component. – azerafati Apr 08 '20 at 08:25
  • 1
    you can use ng-content for this check this demo https://stackblitz.com/edit/angular-wd5v45 – Muhammed Albarmavi Apr 08 '20 at 08:33
  • you can use both way by using ng-content the project the template to the dropdown component – Muhammed Albarmavi Apr 08 '20 at 08:34
1

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

Muhammed Albarmavi
  • 23,240
  • 8
  • 66
  • 91