Angular 2 window service that listens for 'click away'
Have you explore solutions with the HostListener decorator?

https://stackoverflow.com/questions/40107008/hostlistener-documentclick-angular-2-same-component



Subject and Observable imports are capitalized.

import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';


you can use const instead of let for $target and subject variables since they are not reassigned.

Use triple-equals.

if ($target.closest(sub.selector).length === 0) {
  sub.subject.next(event);
}


You can spot the previous points with the ng lint command.

Lifecycle hooks only work in components.

You can drop the JS convention of prefixing private members with _.

Members of the Subscription class can be marked as readonly. Moreover, Angular is a rxjs-everywhere-framework so Subscription is a confusing name since it's a common type of rxjs.

JQuery is a dependency of your service, maybe it can be treated like every other dependencies, with injection. You can also type the jQuery objects if you install the @types/jquery package.

import { Inject, Injectable, InjectionToken, ValueProvider } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';

import * as $ from 'jquery';

export const DOM_HELPER: InjectionToken&lt;JQueryStatic&gt; = new InjectionToken&lt;JQueryStatic&gt;('DomHelper');
// use the provider in your app.module
export const domHelperProvider: ValueProvider = {provide: DOM_HELPER, useValue: $};

class Subscription {
  constructor(public readonly selector: JQuery.Selector,
              public readonly subject: Subject&lt;JQuery.Event&gt;) {
  }
}

@Injectable()
export class WindowService {
  private subscriptions: Array&lt;Subscription&gt; = [];

  constructor(@Inject(DOM_HELPER) private $: JQueryStatic) {
    this.$(window).click((event: JQuery.Event): void =&gt; this.onWindowClick(event));
  }

  public clickAway(selector: JQuery.Selector): Observable&lt;JQuery.Event&gt; {
    const subject = new Subject();
    this.subscriptions.push(new Subscription(selector, subject));
    return subject.asObservable();
  }

  private onWindowClick(event: JQuery.Event): void {
    const $target = this.$(event.target);
    this.subscriptions.forEach((sub: Subscription): void =&gt; {
      if ($target.closest(sub.selector).length === 0) {
        sub.subject.next(event);
      }
    });
  }
}


Your code works but it's buggy. The subscriptions array will never stop growing and may cause performance issue. You can add an "unsubscribing" method in your service, components will call it in their ngOnDestroy method; but I think it's a bad design, consumers of your service will forget to call the method for sure because they will assume that they only have to unsubscribe from the observable you provide.

You have to get rid of this array. I suggest creating an Observable directly from the click events and then filtering it in clickAway:

import { Inject, Injectable, InjectionToken, ValueProvider } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/operator/filter';

import * as $ from 'jquery';

export const DOM_HELPER: InjectionToken&lt;JQueryStatic&gt; = new InjectionToken&lt;JQueryStatic&gt;('DomHelper');
export const domHelperProvider: ValueProvider = {provide: DOM_HELPER, useValue: $};

@Injectable()
export class WindowService {
  private observable: Observable&lt;Event&gt;;

  constructor(@Inject(DOM_HELPER) private $: JQueryStatic) {
    this.observable = Observable.fromEvent(window, 'click');
  }

  public clickAway(selector: JQuery.Selector): Observable&lt;Event&gt; {
    return this.observable
      .filter((event: Event): boolean =&gt; this.$(event.target).closest(selector).length === 0);
  }
}


Finally you don't need jQuery to achieve the functionality and you can use Angular and native types in your API:

import { ElementRef, Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/operator/filter';

@Injectable()
export class WindowService {
  private observable: Observable&lt;Event&gt;;

  constructor() {
    this.observable = Observable.fromEvent(window, 'click');
  }

  public clickAway(el: ElementRef): Observable&lt;Event&gt; {
    return this.observable.filter((event: Event): boolean =&gt; !el.nativeElement.contains(event.target));
  }
}


Example of a component using the service:

import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { WindowService } from '../window.service';
import { Subscription } from 'rxjs/Subscription';

@Component({
  selector: 'app-closable',
  templateUrl: './closable.component.html',
  styleUrls: ['./closable.component.css']
})
export class ClosableComponent implements OnInit, OnDestroy {

  @ViewChild('outside')
  private outside: ElementRef;

  private subscription: Subscription;

  constructor(private window: WindowService) {
  }

  ngOnInit() {
    this.subscription = this.window.clickAway(this.outside).subscribe(() =&gt; console.log('clicked away!'));
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}


and its template:

&lt;div&gt;
  closable works!
  &lt;div #outside&gt;
    &lt;button&gt;click outside of me&lt;/button&gt;
  &lt;/div&gt;
&lt;/div&gt;