From ca6533320d4478318beea7a6e896b3a309a967e9 Mon Sep 17 00:00:00 2001 From: Charley Wu Date: Wed, 24 Nov 2021 11:56:13 +0800 Subject: [PATCH] ng-click-outside is deprecated and not maintained - https://github.com/arkon/ng-sidebar/issues/229 --- package.json | 1 - src/app/app.component.spec.ts | 5 +- src/app/app.module.ts | 4 +- src/app/click-outside.directive.ts | 70 +++++++++++++++++++++ src/app/nav-menu/nav-menu.component.html | 2 +- src/app/nav-menu/nav-menu.component.spec.ts | 4 +- src/app/nav-menu/nav-menu.component.ts | 2 +- yarn.lock | 11 ---- 8 files changed, 78 insertions(+), 21 deletions(-) create mode 100644 src/app/click-outside.directive.ts diff --git a/package.json b/package.json index f927eb5e..5d575c87 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "@ngx-translate/core": "14.0.0", "bootstrap": "5.1.3", "bootstrap-icons": "1.7.1", - "ng-click-outside": "9.0.0", "rxjs": "7.4.0", "tslib": "2.3.1", "whatwg-fetch": "3.6.2", diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index a9a2409d..f945992d 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -5,21 +5,20 @@ import { TranslateModule } from '@ngx-translate/core'; import { render, screen } from '@testing-library/angular'; -import { ClickOutsideModule } from 'ng-click-outside'; import { AppComponent } from './app.component'; +import { ClickOutsideDirective } from './click-outside.directive'; import { NavMenuComponent } from './nav-menu/nav-menu.component'; it('renders without crashing', async () => { document.title = 'Angular Boilerplate'; await render(AppComponent, { imports: [ - ClickOutsideModule, RouterTestingModule, TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: TranslateFakeLoader }, }), ], - declarations: [NavMenuComponent], + declarations: [NavMenuComponent, ClickOutsideDirective], }); expect(await screen.findByText('Angular Boilerplate')).toBeInTheDocument(); }); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 3c9041eb..db85df41 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,9 +4,9 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; import { RouterModule } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; -import { ClickOutsideModule } from 'ng-click-outside'; import { environment } from '../environments/environment'; import { AppComponent } from './app.component'; +import { ClickOutsideDirective } from './click-outside.directive'; import { CounterComponent } from './counter/counter.component'; import { HomeComponent } from './home/home.component'; import { NavMenuComponent } from './nav-menu/nav-menu.component'; @@ -20,7 +20,6 @@ import XhrTodoService from './todo.service.xhr'; BrowserModule, FormsModule, ReactiveFormsModule, - ClickOutsideModule, TranslateModule.forRoot(), RouterModule.forRoot([ { path: '', component: HomeComponent, pathMatch: 'full' }, @@ -37,6 +36,7 @@ import XhrTodoService from './todo.service.xhr'; HomeComponent, TodoListComponent, TodoDetailComponent, + ClickOutsideDirective, ], providers: [ { diff --git a/src/app/click-outside.directive.ts b/src/app/click-outside.directive.ts new file mode 100644 index 00000000..33c7f52c --- /dev/null +++ b/src/app/click-outside.directive.ts @@ -0,0 +1,70 @@ +import { + Directive, + ElementRef, + EventEmitter, + Input, + NgZone, + OnDestroy, + OnInit, + Output +} from '@angular/core'; + +// eslint-disable-next-line @angular-eslint/directive-selector +@Directive({ selector: '[clickOutside]' }) +export class ClickOutsideDirective implements OnInit, OnDestroy { + @Input() exclude = ''; + @Output() clickOutside: EventEmitter = new EventEmitter(); + + private excludeElements: HTMLElement[] = []; + private events: string[] = ['click']; + + constructor(private elementRef: ElementRef, private zone: NgZone) { + this.eventListener = this.eventListener.bind(this); + } + + ngOnInit() { + if (this.exclude) { + try { + const elements = Array.from( + document.querySelectorAll(this.exclude) + ) as HTMLElement[]; + if (elements) { + this.excludeElements = elements; + } + } catch (err) { + console.error('Check the exclude selector syntax!', err); + } + } + this.initEventListener(); + } + + ngOnDestroy() { + this.zone.runOutsideAngular(() => { + this.events.forEach((e) => + document.removeEventListener(e, this.eventListener) + ); + }); + } + + private initEventListener() { + this.zone.runOutsideAngular(() => { + this.events.forEach((e) => + document.addEventListener(e, this.eventListener) + ); + }); + } + + private eventListener(e: Event) { + if (!e || !e.target) return; + if ( + !this.elementRef.nativeElement.contains(e.target) && + !this.excludeElements.some((el) => el.contains(e.target as Node)) + ) { + this.emit(e); + } + } + + private emit(e: Event) { + this.zone.run(() => this.clickOutside.emit(e)); + } +} diff --git a/src/app/nav-menu/nav-menu.component.html b/src/app/nav-menu/nav-menu.component.html index 2695f9a1..e6de505b 100644 --- a/src/app/nav-menu/nav-menu.component.html +++ b/src/app/nav-menu/nav-menu.component.html @@ -55,7 +55,7 @@ class="dropdown-menu" [ngClass]="{ show: isExpanded }" aria-labelledby="i18nDropdown" - (clickOutside)="clickOutside()" + (clickOutside)="onOutsideClick()" [exclude]="'button.dropdown-toggle'" >
  • diff --git a/src/app/nav-menu/nav-menu.component.spec.ts b/src/app/nav-menu/nav-menu.component.spec.ts index 90770b34..1726baac 100644 --- a/src/app/nav-menu/nav-menu.component.spec.ts +++ b/src/app/nav-menu/nav-menu.component.spec.ts @@ -6,7 +6,7 @@ import { TranslateService } from '@ngx-translate/core'; import { fireEvent, render, screen } from '@testing-library/angular'; -import { ClickOutsideModule } from 'ng-click-outside'; +import { ClickOutsideDirective } from '../click-outside.directive'; import { NavMenuComponent } from './nav-menu.component'; beforeEach(async () => { @@ -16,11 +16,11 @@ beforeEach(async () => { }, imports: [ RouterTestingModule, - ClickOutsideModule, TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: TranslateFakeLoader }, }), ], + declarations: [ClickOutsideDirective], providers: [TranslateService], }); }); diff --git a/src/app/nav-menu/nav-menu.component.ts b/src/app/nav-menu/nav-menu.component.ts index 6dbd0aa1..1d9686e8 100644 --- a/src/app/nav-menu/nav-menu.component.ts +++ b/src/app/nav-menu/nav-menu.component.ts @@ -26,7 +26,7 @@ export class NavMenuComponent { this.isExpanded = !this.isExpanded; } - clickOutside(): void { + onOutsideClick(): void { this.isExpanded = false; } diff --git a/yarn.lock b/yarn.lock index 93b66de4..e1d616f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3216,7 +3216,6 @@ __metadata: jest: 27.3.1 jest-preset-angular: 11.0.1 msw: 0.35.0 - ng-click-outside: 9.0.0 rxjs: 7.4.0 ts-node: 10.4.0 tslib: 2.3.1 @@ -8145,16 +8144,6 @@ __metadata: languageName: node linkType: hard -"ng-click-outside@npm:9.0.0": - version: 9.0.0 - resolution: "ng-click-outside@npm:9.0.0" - peerDependencies: - "@angular/common": ">=12.0.0" - "@angular/core": ">=12.0.0" - checksum: 39e9677136e33426cda54421256ea17ca782f8c5259878d2820c1eb504d453b256293a4c496b51d49f71ea66a631d2d4acc2fc393b30f971586379e2fff38f11 - languageName: node - linkType: hard - "nice-napi@npm:^1.0.2": version: 1.0.2 resolution: "nice-napi@npm:1.0.2"