This is the angular service I wrote window.service.ts
.
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/subject';
import { Observable } from 'rxjs/observable';
declare let $: any;
@Injectable()
export class WindowService {
private _subscriptions: Array<Subscription> = [];
constructor() {
$(window).bind('click', this._onWindowClick.bind(this));
}
ngOnDestroy() {
$(window).unbind('click');
}
// Handle any click event on the document
private _onWindowClick(event): void {
let $target = $(event.target);
// Check every subscription you to see if the $target element
// that was clicked is contained inside the selector they subscribed with
this._subscriptions.forEach((sub: Subscription) => {
// IF you do find that selector continue - the click was inside this subscribers
// element ELSE fire off the subject to let the subscriber know a click away from them occurred
if ($target.closest(sub.selector).length == 0)
sub.subject.next(event);
});
}
// Sign up for clickAway event - called by components
public clickAway(selector: string): Observable<Event> {
let subject = new Subject();
this._subscriptions.push(new Subscription(selector, subject));
return subject.asObservable();
}
}
class Subscription {
constructor(
public selector: string,
public subject: Subject<Event>
) { }
}
The problem I was trying to solve with it was that I have custom components that open and close. Because of the strict component scope that Angular enforces I had no way to know if someone had clicked away from an open component.
Think of a native HTML select element. You click it to open it, then you click away from it and expect it to close. I needed that functionality.
So I created this service that takes a selector that should represent the most parent element of the component. When a click on the window occurs the service checks to see if the click occurred inside the element passed during subscription. If it was not on that element, it executes next()
so the component can know that a click occurred away from itself and likely close itself.
Example use in a component:
ngOnInit() {
this._windowService.clickAway('#customSelect')
.subscribe(() => {
console.log('clicked away');
this.state = ViewState.Closed;
});
}
I'm looking for a review on the code in specific to good Angular and javascript practices. I know using $
is considered a bad practice but I already have it in the project and it makes the check to see if the event occurred an easily understandable inside another element a one liner. Also, best practices on how Angular Services, Component, and Directives should interact and communicate is confusing to me so if there is changes that could make this better, looking for those.
Also of course if anyone sees any red flags, please point out.
To answer your first question: yes it works.