5

I know there are countless of questions around this, and I tried every solution but nothing suited my needs. Tried directives, tried it straight in my component but it didn't work a single time.

So my situation is like this; I have a tableview and at the end of every tableview you can open a small dropdown by clicking on an icon. The dropdown that pops up is a small component. So in my parent component it looks like this;

<tbody>
    <tr *ngFor="let rows of subscriptionTable.data; let i = index;">
        <td *ngFor="let cell of subscriptionTable.data[i].data"> 
            <!-- Table actions -->
            <div *ngIf="cell.actions && cell.actions.length > 0">
                <app-dropdown
                    [actions]         = cell.actions
                    (onActionClicked) = "handleSubscriptionAction($event, i)"
                >
                </app-dropdown>
            </div>
        </td>
    </tr>
</tbody>

So I'm rendering multiple child app-dropdown components that look like this;

<div class="fs-action">
  <div class="fs-action__dots" (click)="openActionSheet($event)">
    <span class="fs-action__dot"></span>
    <span class="fs-action__dot"></span>
    <span class="fs-action__dot"></span>
  </div>

  <ul class="fs-action__list">
    <li 
      class="fs-action__item"
      *ngFor="let action of actions; let i = index;" 
      (click)="actionClicked( $event, i )" 
      [ngClass]="{'fs-action__item--danger': action.type === 'destructive' }"
    >
        {{ action.label }}
    </li>
  </ul>
</div>

What I want to do now is, that as soon as I click outside the fs-action element, the dropdown dismisses. I would like to do that inside the app-dropdown component, then everything related to this component is in the same place.

But as soon as I attach a directive or anything else, the hostlistener get's added for every row. Every outside click is then being triggered multiple times. Checking if I was clicking outside the action element then becomes a burden, because it then renders false for all the other hostlisteners and true for the one that's active.

The issue is illustrated inside this stackblitz; https://stackblitz.com/edit/angular-xjdmg4

I commented the section which causes issues inside dropdown.component.ts;

  public onClickOutside( event ) {
    // It executes 3 times, for each of the items that is added
    // Even when clicking inside the dropdown, it calls this function
    console.log("click")
  }
  • Sure, will try to do it in an hour or so! Currently out of office. –  Jan 03 '20 at 17:55
  • You should be able to use a `HostListener` for something like this and attach the appropriate event. – tlm Jan 03 '20 at 17:57
  • Found some time, here is a stackblitz illustrating the issue; https://stackblitz.com/edit/angular-xjdmg4 –  Jan 03 '20 at 19:33

2 Answers2

2

Just use the (click) event binding, pass a function from the component’s instance and you are done.

Solution: Declare Directive ClickOutside like below:

@Directive({
  selector: '[clickOutside]',
})
export class ClickOutsideDirective {
  @Output('onClickOutside') onClickOutside = new EventEmitter<MouseEvent>();

  constructor(private _eref: ElementRef) {}

  @HostListener('document:click', ['$event', '$event.target'])
  onDocumentClicked(event: MouseEvent, targetElement: HTMLElement) {
    if (targetElement && document.body.contains(targetElement) && !this._eref.nativeElement.contains(targetElement)) {
      this.onClickOutside.emit(event);
      }
    }
  }
}
<div clickOutside (onClickOutside)="onClickOutside()">
...
</div>

And there is directive in an npm module called ng-click-outside.

Installation: npm install --save ng-click-outside

Read full documentation in link below:

ng-click-outside directive

Sadaf Niknam
  • 509
  • 1
  • 4
  • 14
  • This doesn't really work as intended. See my stackblitz.com/edit/angular-xjdmg4. Even when clicking inside the div it renders the click as outside. –  Jan 03 '20 at 19:56
1

I used a directive to a similar situation. To install:

npm install --save ng-click-outside

Then import in your module

import { ClickOutsideModule } from 'ng-click-outside';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, ClickOutsideModule],
  bootstrap: [AppComponent]
})
class AppModule {}

In your component

@Component({
  selector: 'app',
  template: `
    <div (clickOutside)="onClickedOutside($event)">Click outside this</div>
  `
})
export class AppComponent {
  onClickedOutside(e: Event) {
  console.log('Clicked outside:', e);
  }
}

It has some options. You can see it in https://www.npmjs.com/package/ng-click-outside

Regards

Charly Sosa
  • 565
  • 3
  • 5
  • Already tried this one. But if I add this inside my child component, on let's say the `fs-action`, it get's added 9 times. Clicking outside something executes 9 times as well. Or do I need to add it to something different? –  Jan 03 '20 at 17:46
  • Try adding [attachOutsideOnClick]="true" – Charly Sosa Jan 03 '20 at 18:19
  • Or check others options, may be if you combine they you get the result that wished – Charly Sosa Jan 03 '20 at 18:21
  • This doesn't really work as intended I think. See my stackblitz.com/edit/angular-xjdmg4. Even when clicking inside the div it renders the click as outside. And it fires 3 times. –  Jan 03 '20 at 19:56
  • Aah hold up. I'm now reading your comment about `attachOutsideOnClick`. I must have missed that, because I think that can make it work! –  Jan 03 '20 at 20:57
  • @CharlySosa : where do i add [attachOutsideOnClick]="true" ? and how to define it in TS file? – shiva Mar 16 '21 at 05:57
  • @shiva in html template add attachOutsideOnClick "
    Click outside this
    "
    – Charly Sosa Mar 17 '21 at 13:54