diff --git a/README.md b/README.md index f38e430..53740db 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ ng2-redux lets you easily connect your Angular 2 components with Redux. - [Installation](#installation) - [Quick Start](#quick-start) - [Usage](#usage) +- [A Note about Internet Explorer](#a-note-about-internet-explorer) - [Cookbooks](#cookbooks) - [Using Angular 2 Services in your Action Creators](#using-angular-2-services-in-your-action-creators) - [Using Angular 2 Services in your Middleware](#using-angular-2-services-in-your-middleware) @@ -195,6 +196,14 @@ export class App { } ``` +## A Note about Internet Explorer + +This library relies on the presence of `Object.assign` and `Array.forEach`. +Internet Explorer requires polyfills for these; however these same functions +are also needed for Angular 2 itself to work. Just make sure you include +[core-js](https://npmjs.com/package/core-js) or [es6-shim](https://npmjs.com/packages/es6-shim) +if you need to support IE. + ## Cookbooks ### Using Angular 2 Services in your Action Creators diff --git a/package.json b/package.json index f54fb77..99fd09f 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ }, "homepage": "https://github.com/angular-redux/ng2-redux#readme", "devDependencies": { + "@angular/core": "2.0.0-rc.1", "chai": "^3.5.0", "expect": "^1.8.0", "mocha": "^2.4.5", @@ -50,15 +51,15 @@ "ts-loader": "^0.8.1", "ts-node": "^0.5.5", "typescript": "^1.8.10", - "@angular/core": "2.0.0-rc.1", "es6-shim": "^0.35.0", "redux": "^3.4.0", "reflect-metadata": "0.1.3", "rxjs": "5.0.0-beta.6", + "typings": "^0.7.4", "zone.js": "0.6.12" }, - "dependencies": { - "lodash": "^3.10.1", - "typings": "^0.7.4" + "peerDependencies": { + "rxjs": "5.0.0-beta.6", + "@angular/core": "2.0.0-rc.1" } } diff --git a/src/___tests___/components/ng-redux.spec.ts b/src/___tests___/components/ng-redux.spec.ts index a28ae29..47f6e16 100644 --- a/src/___tests___/components/ng-redux.spec.ts +++ b/src/___tests___/components/ng-redux.spec.ts @@ -1,10 +1,11 @@ import 'reflect-metadata'; -import {expect, use} from 'chai'; +import 'es6-shim'; +import { expect, use } from 'chai'; import { createStore } from 'redux'; -import {NgRedux} from '../../components/ng-redux'; +import { NgRedux } from '../../components/ng-redux'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; -import * as _ from 'lodash'; + use(sinonChai); function returnPojo() { @@ -88,8 +89,8 @@ describe('Connector', () => { it('Should extend target (object) with actionCreators', () => { connector.connect(returnPojo, { ac1: returnPojo, ac2: () => { } })(targetObj); - expect(_.isFunction(targetObj.ac1)).to.equal(true); - expect(_.isFunction(targetObj.ac2)).to.equal(true); + expect(targetObj.ac1).to.be.a('Function'); + expect(targetObj.ac2).to.be.a('Function'); }); it('Should return an unsubscribing function', () => { diff --git a/src/___tests___/tests.entry.ts b/src/___tests___/tests.entry.ts deleted file mode 100644 index 8b13789..0000000 --- a/src/___tests___/tests.entry.ts +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/___tests___/utils/omit.spec.ts b/src/___tests___/utils/omit.spec.ts new file mode 100644 index 0000000..6d509e7 --- /dev/null +++ b/src/___tests___/utils/omit.spec.ts @@ -0,0 +1,22 @@ +import 'reflect-metadata'; +import { expect, use } from 'chai'; + +import { omit } from '../../utils/omit'; + +describe('omit', () => { + it('should omit the specified properties', () => { + const input = { a: 1, b: 2, c:3, d: 4 }; + const output = omit(input, [ 'b', 'd' ]); + + expect(output.hasOwnProperty('b')).to.be.false; + expect(output.hasOwnProperty('d')).to.be.false; + expect(output).to.eql({ a: 1, c: 3 }); + }); + + it('should not mutate its input', () => { + const input = { a: 1, b: 2, c:3, d: 4 }; + const output = omit(input, [ 'b', 'd' ]); + + expect(input).to.eql({ a: 1, b: 2, c:3, d: 4 }); + }); +}); diff --git a/src/___tests___/utils/type-checks.spec.ts b/src/___tests___/utils/type-checks.spec.ts new file mode 100644 index 0000000..fc73637 --- /dev/null +++ b/src/___tests___/utils/type-checks.spec.ts @@ -0,0 +1,137 @@ +import { expect, use } from 'chai'; + +import { isObject, isFunction, isPlainObject} from '../../utils/type-checks'; + +describe('typeChecks', () => { + describe('isObject', () => { + it('should return true for an object literal', () => { + expect(isObject({})).to.be.true; + }); + + it('should return true for a new Object', () => { + expect(isObject(new Object())).to.be.true; + }); + + it('should return true for an instance of a class', () => { + class Foo {}; + expect(isObject(new Foo())).to.be.true; + }); + + it('should return false for null', () => { + expect(isObject(null)).to.be.false; + }); + + it('should return false for undefined', () => { + expect(isObject(undefined)).to.be.false; + }); + + it('should return false for a function', () => { + expect(isObject(function foo() {})).to.be.false; + }); + + it('should return false for an arrow function', () => { + expect(isObject(() => undefined)).to.be.false; + }); + + it('should return false for a constructor function', () => { + class Foo {}; + expect(isObject(Foo)).to.be.false; + }); + + it('should return false for a string', () => { + expect(isObject('foo')).to.be.false; + }); + + it('should return false for a number', () => { + expect(isObject(1)).to.be.false; + }); + }); + + describe('isFunction', () => { + it('should return true for a function', () => { + expect(isFunction(function () {})).to.be.true; + }); + + it('should return true for an arrow function', () => { + expect(isFunction(() => undefined)).to.be.true; + }); + + it('should return true for a constructor function', () => { + class Foo {}; + expect(isFunction(Foo)).to.be.true; + }); + + it('should return false for null', () => { + expect(isFunction(null)).to.be.false; + }); + + it('should return false for undefined', () => { + expect(isFunction(undefined)).to.be.false; + }); + + it('should return false for an object literal', () => { + expect(isFunction({})).to.be.false; + }); + + it('should return false for an instance of a class', () => { + class Foo {}; + expect(isFunction(new Foo())).to.be.false; + }); + + it('should return false for a string', () => { + expect(isFunction('foo')).to.be.false; + }); + + it('should return false for a number', () => { + expect(isFunction(1)).to.be.false; + }); + + it('should return false for a list', () => { + expect(isFunction([])).to.be.false; + }); + }); + + describe('isPlainObject', () => { + it('should return true for an object literal', () => { + expect(isPlainObject({})).to.be.true; + }); + + it('should return true for a new Object', () => { + expect(isPlainObject(new Object())).to.be.true; + }); + + it('should return false for an instance of a class', () => { + class Foo {}; + expect(isPlainObject(new Foo())).to.be.false; + }); + + it('should return false for null', () => { + expect(isPlainObject(null)).to.be.false; + }); + + it('should return false for undefined', () => { + expect(isPlainObject(undefined)).to.be.false; + }); + + it('should return false for a function', () => { + expect(isPlainObject(function foo() {})).to.be.false; + }); + + it('should return false for an arrow function', () => { + expect(isPlainObject(() => undefined)).to.be.false; + }); + + it('should return false for a constructor function', () => { + class Foo {}; + expect(isPlainObject(Foo)).to.be.false; + }); + + it('should return false for a string', () => { + expect(isPlainObject('foo')).to.be.false; + }); + + it('should return false for a number', () => { + expect(isObject(1)).to.be.false; + }); + }); +}); diff --git a/src/components/ng-redux.ts b/src/components/ng-redux.ts index f62641c..28a8012 100644 --- a/src/components/ng-redux.ts +++ b/src/components/ng-redux.ts @@ -1,11 +1,15 @@ import shallowEqual from '../utils/shallowEqual'; import wrapActionCreators from '../utils/wrapActionCreators'; import * as Redux from 'redux'; -import * as _ from 'lodash'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { Observable } from 'rxjs/Observable'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/distinctUntilChanged'; import { Store, Action, ActionCreator, Reducer } from 'redux'; import { Injectable } from '@angular/core'; import { invariant } from '../utils/invariant'; +import { isObject, isFunction, isPlainObject} from '../utils/type-checks'; +import { omit } from '../utils/omit'; const VALID_SELECTORS = ['string', 'number', 'symbol', 'function']; const ERROR_MESSAGE = `Expected selector to be one of: @@ -31,13 +35,13 @@ export class NgRedux { this._store.subscribe(() => this._store$.next(this._store.getState())); this._defaultMapStateToTarget = () => ({}); this._defaultMapDispatchToTarget = dispatch => ({ dispatch }); - const cleanedStore = _.omit(store, ['dispatch', 'getState', 'subscribe', 'replaceReducer']) + const cleanedStore = omit(store, ['dispatch', 'getState', 'subscribe', 'replaceReducer']) Object.assign(this, cleanedStore); } /** * Create an observable from a Redux store. - * + * * @param {Store} store Redux store to create an observable from * @returns {BehaviorSubject} */ @@ -96,7 +100,7 @@ export class NgRedux { || this._defaultMapStateToTarget; invariant( - _.isFunction(finalMapStateToTarget), + isFunction(finalMapStateToTarget), 'mapStateToTarget must be a Function. Instead received %s.', finalMapStateToTarget); @@ -108,7 +112,7 @@ export class NgRedux { return (target) => { invariant( - _.isFunction(target) || _.isObject(target), + isFunction(target) || isObject(target), 'The target parameter passed to connect must be a Function or' + 'a plain object.' ); @@ -174,10 +178,10 @@ export class NgRedux { }; private updateTarget(target, StateSlice, dispatch) { - if (_.isFunction(target)) { + if (isFunction(target)) { target(StateSlice, dispatch); } else { - _.assign(target, StateSlice, dispatch); + Object.assign(target, StateSlice, dispatch); } } @@ -185,7 +189,7 @@ export class NgRedux { const slice = mapStateToScope(state); invariant( - _.isPlainObject(slice), + isPlainObject(slice), '`mapStateToScope` must return an object. Instead received %s.', slice ); @@ -194,16 +198,16 @@ export class NgRedux { } private getBoundActions = (actions) => { - const finalMapDispatchToTarget = _.isPlainObject(actions) ? + const finalMapDispatchToTarget = isPlainObject(actions) ? wrapActionCreators(actions) : actions || this._defaultMapDispatchToTarget; invariant( - _.isPlainObject(finalMapDispatchToTarget) - || _.isFunction(finalMapDispatchToTarget), + isPlainObject(finalMapDispatchToTarget) + || isFunction(finalMapDispatchToTarget), 'mapDispatchToTarget must be a plain Object or a Function. ' + 'Instead received % s.', finalMapDispatchToTarget); return finalMapDispatchToTarget(this._store.dispatch); }; -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 5f72e40..9b1e050 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,2 @@ -export {provider} from './components/provider'; -export { NgRedux } from './components/ng-redux'; +export { provider } from './components/provider'; +export { NgRedux } from './components/ng-redux'; diff --git a/src/utils/omit.ts b/src/utils/omit.ts new file mode 100644 index 0000000..4c6deae --- /dev/null +++ b/src/utils/omit.ts @@ -0,0 +1,13 @@ +/** + * Creates a copy of object, but with properties matching 'props' + * omitted. + * + * @param {Object} object: the object to be copied and filtered. + * @param {string[]} props: a list of property names to be excluded + * from the filtered copy. + */ +export function omit(object: Object, props: string[]): Object { + const clone = Object.assign({}, object); + props.forEach(prop => delete clone[prop]); + return clone; +}; diff --git a/src/utils/type-checks.ts b/src/utils/type-checks.ts new file mode 100644 index 0000000..023e8a1 --- /dev/null +++ b/src/utils/type-checks.ts @@ -0,0 +1,16 @@ +export function isFunction(thing: any): boolean { + return !!(thing && + thing.constructor && + thing.call && + thing.apply); +} + +export function isObject(thing: any): boolean { + return !!(thing && + typeof thing === 'object' && + !isFunction(thing)); +} + +export function isPlainObject(thing: any): boolean { + return isObject(thing) && thing.constructor === Object; +} diff --git a/typings.json b/typings.json index 544667c..5400147 100644 --- a/typings.json +++ b/typings.json @@ -4,7 +4,6 @@ "ambientDependencies": { "chai": "registry:dt/chai#3.4.0+20160317120654", "es6-shim": "registry:dt/es6-shim#0.31.2+20160317120654", - "lodash": "github:DefinitelyTyped/DefinitelyTyped/lodash/lodash.d.ts#70bf7e2bfeb0d5b1b651ef3219bcc65c8eec117e", "mocha": "registry:dt/mocha#2.2.5+20160317120654", "node": "github:DefinitelyTyped/DefinitelyTyped/node/node.d.ts#20e1eb9616922d382d918cc5a21870a9dbe255f5", "sinon": "registry:dt/sinon#1.16.0+20160317120654",