import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import {EventEmitter, Handler, Subscription} from './emitter'; import {ArrayEmitter as IArrayEmitter} from './types'; const ArrayProto = Array.prototype; export class ArrayEmitter extends Array implements IArrayEmitter { public $emitter: EventEmitter; public $subject: BehaviorSubject>; public on: {(name: string, handler: Handler): void}; public off: {(name: string, handler: Handler): void}; public listen: {(name: string, handler: Handler): Subscription}; public once: {(name: string, handler: Handler): void}; public addListener: {(name: string, handler: Handler): void}; public removeListener: {(name: string, handler: Handler): void}; constructor(...args: any[]) { super(...args); let arr = this; // Non enumerable so that we can send the array over postMessage Object.defineProperty(arr, '$emitter', { enumerable: false, value: new EventEmitter(), }); Object.defineProperty(arr, '$subject', { enumerable: false, value: new BehaviorSubject(this), }); this.$emitter.on('update', () => { this.$subject.next(this); }); // Generate our Array Emitter methods let ArrayEmitterMethods: any = {}; ['on', 'off', 'listen', 'once', 'addListener', 'removeListener'].forEach(key => { ArrayEmitterMethods[key] = { enumerable: false, value: (...methodArgs: any[]) => { return this.$emitter[key](...methodArgs); }, }; }); // These methods create new arrays ['reverse', 'sort', 'splice'].forEach(key => { ArrayEmitterMethods[key] = { enumerable: false, value: (...methodArgs: any[]) => { let emitter = this.$emitter; emitter.on('update', () => { ArrayProto[key].apply(this, methodArgs); }); ArrayProto[key].apply(this, methodArgs); return this; }, }; }); ['filter', 'map', 'slice'].forEach(key => { ArrayEmitterMethods[key] = { enumerable: false, value: (...methodArgs: any[]) => { let newArr: any = new ArrayEmitter(); let emitter = this.$emitter; emitter.on('update', () => { updateNewArr(ArrayProto[key].apply(this, methodArgs)); newArr.$emitter.emit('update', newArr); }); updateNewArr(ArrayProto[key].apply(this, methodArgs)); function updateNewArr(updatedArr: any[]) { newArr.length = 0; for (let i = 0; i < updatedArr.length; i++) { // tslint:disable-line prefer-for-of newArr.push(updatedArr[i]); } } return newArr; }, }; }); ArrayEmitterMethods.concat = { enumerable: false, value: (...methodArgs: any[]) => { let newArr = new ArrayEmitter(); let allArrs = [this, ...methodArgs]; let qries = allArrs.filter((obj: any) => (obj.$emitter && Array.isArray(obj))); qries.forEach((qry: ArrayEmitter) => { let emitter = qry.$emitter; emitter.on('update', () => { updateConcat(); newArr.$emitter.emit('update', newArr); }); }); updateConcat(); return newArr; function updateConcat() { let concatted = ArrayProto.concat.call(this, ...methodArgs); newArr.length = 0; newArr.push(...concatted); } }, }; Object.defineProperties(arr, ArrayEmitterMethods); } }