@Directive({
selector: '[clickOutside]',
})
export class ClickOutsideDirective implements OnInit {
private readonly ngZone = inject(NgZone);
private readonly elementRef = inject(ElementRef);
private document = inject(DOCUMENT);
private destroyRef = inject(DestroyRef);
private readonly documentClick$ = this.documentClick();
@Output() clickOutside = new EventEmitter<EventTarget>();
ngOnInit(): void {
this.documentClick$
.pipe(
takeUntilDestroyed(this.destroyRef),
filter((event: Event) => {
return !this.elementRef.nativeElement.contains(event.target);
}),
)
.subscribe((event: Event) => {
this.ngZone.run(() => {
if (!event?.target) {
return;
}
this.clickOutside.emit(event.target);
});
});
}
private documentClick(): Subject<Event> {
const click$ = new Subject<Event>();
this.ngZone.runOutsideAngular(() => {
timer(50)
.pipe(switchMap(() => fromEvent(this.document, 'click')))
.subscribe(click$);
});
return click$;
}
}
#Testing the directive with timer RxJS, forceAsync and timer not working.
2 messages · Page 1 of 1 (latest)
@Component({
standalone: true,
template: `
<div clickOutside>
<div id="inner-div"></div>
</div>
<div id="sibling-div"></div>
`,
imports: [ClickOutsideModule],
})
class TestComponent {}
describe('ClickOutsideDirective', () => {
let fixture: ComponentFixture<TestComponent>;
let directive: ClickOutsideDirective;
let debugElement: DebugElement;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [TestComponent, ClickOutsideModule],
});
fixture = TestBed.createComponent(TestComponent);
debugElement = fixture.debugElement.query(By.directive(ClickOutsideDirective));
directive = debugElement.injector.get(ClickOutsideDirective);
fixture.detectChanges();
}));
it('should create the directive', () => {
expect(directive).toBeDefined();
});
it('should emit clickOutside even when click occurs outside of the element', fakeAsync(() => {
spyOn(directive.clickOutside, 'emit');
fixture.detectChanges();
tick(500)
const siblingDiv = fixture.debugElement.query(By.css('#sibling-div'));
siblingDiv.nativeElement.click();
expect(directive.clickOutside.emit).toHaveBeenCalledWith(siblingDiv.nativeElement);
}));
});