Skip to content

Commit

Permalink
Add smarter type inference for ofType pipe.
Browse files Browse the repository at this point in the history
It is based on TS2.8 introduced conditional types. Upgrade package.json
to that version.

Move previous version of actions.ts to @ngrx/effects/compat module.

Also remove the deprecated non-pipable version of `ofType` so that we
don't have to keep complicated types in two places.

Fixes some tests post removal of non-pipable version.

Original implementation by @mtaran-google.
  • Loading branch information
rkirov committed Jul 23, 2018
1 parent 97269c0 commit 883cb46
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 27 deletions.
19 changes: 19 additions & 0 deletions modules/effects/compat/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package(default_visibility = ["//visibility:public"])

exports_files(["package.json"])

load("//tools:defaults.bzl", "ng_module")

ng_module(
name = "compat",
srcs = glob([
"src/actions.ts",
]),
module_name = "@ngrx/effects/compat",
visibility = ["//visibility:public"],
deps = [
"//modules/store",
"@rxjs",
"@rxjs//operators",
],
)
3 changes: 3 additions & 0 deletions modules/effects/compat/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "@ngrx/effects/compat"
}
12 changes: 12 additions & 0 deletions modules/effects/compat/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default {
entry: './dist/effects/@ngrx/effects/compat.es5.js',
dest: './dist/effects/bundles/effects-compat.umd.js',
format: 'umd',
exports: 'named',
moduleName: 'ngrx.effects.compat',
globals: {
'@angular/core': 'ng.core',
'@ngrx/effects': 'ngrx.effects',
'rxjs': 'Rx',
}
}
34 changes: 34 additions & 0 deletions modules/effects/compat/src/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Inject, Injectable } from '@angular/core';
import { Action, ScannedActionsSubject } from '@ngrx/store';
import { Observable, Operator, OperatorFunction } from 'rxjs';
import { filter } from 'rxjs/operators';

@Injectable()
export class Actions<V = Action> extends Observable<V> {
constructor(@Inject(ScannedActionsSubject) source?: Observable<V>) {
super();

if (source) {
this.source = source;
}
}

lift<R>(operator: Operator<V, R>): Observable<R> {
const observable = new Actions<R>();
observable.source = this;
observable.operator = operator;
return observable;
}

ofType<V2 extends V = V>(...allowedTypes: string[]): Actions<V2> {
return ofType<any>(...allowedTypes)(this as Actions<any>) as Actions<V2>;
}
}

export function ofType<T extends Action>(
...allowedTypes: string[]
): OperatorFunction<Action, T> {
return filter((action: Action): action is T =>
allowedTypes.some(type => type === action.type)
);
}
14 changes: 14 additions & 0 deletions modules/effects/compat/tsconfig-build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"extends": "../tsconfig-build",
"compilerOptions": {
"paths": {
"@ngrx/store": ["../../dist/packages/store"],
"@ngrx/effects": ["../../dist/packages/effects"]
}
},
"files": ["index.ts"],
"angularCompilerOptions": {
// Work around for issue: https://github.com/angular/angular/issues/22210
"strictMetadataEmit": false
}
}
34 changes: 21 additions & 13 deletions modules/effects/spec/actions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ describe('Actions', function() {
const ADD = 'ADD';
const SUBTRACT = 'SUBTRACT';

interface AddAcction extends Action {
type: 'ADD';
}

interface SubtractAction extends Action {
type: 'SUBTRACT';
}

function reducer(state: number = 0, action: Action) {
switch (action.type) {
case ADD:
Expand Down Expand Up @@ -58,10 +66,10 @@ describe('Actions', function() {
actions.forEach(action => dispatcher.next(action));
});

it('should let you filter out actions', function() {
const actions = [ADD, ADD, SUBTRACT, ADD, SUBTRACT];
const expected = actions.filter(type => type === ADD);
const actions = [ADD, ADD, SUBTRACT, ADD, SUBTRACT];
const expected = actions.filter(type => type === ADD);

it('should let you filter out actions', function() {
actions$
.pipe(
ofType(ADD),
Expand All @@ -78,16 +86,16 @@ describe('Actions', function() {
dispatcher.complete();
});

it('should support using the ofType instance operator', () => {
const action = { type: ADD };

const response = cold('-b', { b: true });
const expected = cold('--c', { c: true });

const effect$ = new Actions(hot('-a', { a: action }))
.ofType(ADD)
.pipe(switchMap(() => response));
it('should let you filter out actions and ofType can take an explicit type argument', function() {
actions$
.pipe(ofType<AddAcction>(ADD), map(update => update.type), toArray())
.subscribe({
next(actual) {
expect(actual).toEqual(expected);
},
});

expect(effect$).toBeObservable(expected);
actions.forEach(action => dispatcher.next({ type: action }));
dispatcher.complete();
});
});
5 changes: 3 additions & 2 deletions modules/effects/spec/effects_feature_module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { Observable } from 'rxjs';
import { map, withLatestFrom } from 'rxjs/operators';

import { Actions, Effect, EffectsModule } from '../';
import { Actions, Effect, EffectsModule, ofType } from '../';
import { EffectsFeatureModule } from '../src/effects_feature_module';
import { EffectsRootModule } from '../src/effects_root_module';
import { FEATURE_EFFECTS } from '../src/tokens';
Expand Down Expand Up @@ -118,7 +118,8 @@ class FeatureEffects {
constructor(private actions: Actions, private store: Store<State>) {}

@Effect()
effectWithStore = this.actions.ofType('INCREMENT').pipe(
effectWithStore = this.actions.pipe(
ofType('INCREMENT'),
withLatestFrom(this.store.select(getDataState)),
map(([action, state]) => ({ type: 'INCREASE' }))
);
Expand Down
56 changes: 46 additions & 10 deletions modules/effects/src/actions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Inject, Injectable } from '@angular/core';
import { Action, ScannedActionsSubject } from '@ngrx/store';
import { Observable, Operator, OperatorFunction } from 'rxjs';
import { Observable, OperatorFunction, Operator } from 'rxjs';
import { filter } from 'rxjs/operators';

// Removes types from T that are not assignable to U.
export type Filter<T, U> = T extends U ? T : never;

@Injectable()
export class Actions<V = Action> extends Observable<V> {
constructor(@Inject(ScannedActionsSubject) source?: Observable<V>) {
Expand All @@ -19,17 +22,50 @@ export class Actions<V = Action> extends Observable<V> {
observable.operator = operator;
return observable;
}

ofType<V2 extends V = V>(...allowedTypes: string[]): Actions<V2> {
return ofType<any>(...allowedTypes)(this as Actions<any>) as Actions<V2>;
}
}

export function ofType<T extends Action>(
/**
* Type propagation currently scales only to five input strings.
*/
export function ofType<
V extends Filter<U, { type: T1 }>,
T1 extends string = string,
U extends Action = Action
>(t1: T1): OperatorFunction<U, V>;
export function ofType<
V extends Filter<U, { type: T1 | T2 }>,
T1 extends string = string,
T2 extends string = string,
U extends Action = Action
>(t1: T1, t2: T2): OperatorFunction<U, V>;
export function ofType<
V extends Filter<U, { type: T1 | T2 | T3 }>,
T1 extends string = string,
T2 extends string = string,
T3 extends string = string,
U extends Action = Action
>(t1: T1, t2: T2, t3: T3): OperatorFunction<U, V>;
export function ofType<
V extends Filter<U, { type: T1 | T2 | T3 | T4 }>,
T1 extends string = string,
T2 extends string = string,
T3 extends string = string,
T4 extends string = string,
U extends Action = Action
>(t1: T1, t2: T2, t3: T3, t4: T4): OperatorFunction<U, V>;
export function ofType<
V extends Filter<U, { type: T1 | T2 | T3 | T4 | T5 }>,
T1 extends string = string,
T2 extends string = string,
T3 extends string = string,
T4 extends string = string,
T5 extends string = string,
U extends Action = Action
>(t1: T1, t2: T2, t3: T3, t4: T4, t5: T5): OperatorFunction<U, V>;
export function ofType<U extends Action>(
...allowedTypes: string[]
): OperatorFunction<Action, T> {
return filter(
(action: Action): action is T =>
allowedTypes.some(type => type === action.type)
): OperatorFunction<U, U> {
return filter((action: Action): action is U =>
allowedTypes.some(type => type === action.type)
);
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@
"tslib": "1.6.0",
"tslint": "^5.0.0",
"tsutils": "2.20.0",
"typescript": "~2.7.2",
"typescript": "~2.8.4",
"uglify-js": "^3.1.9"
},
"collective": {
Expand Down
6 changes: 5 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8288,10 +8288,14 @@ typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"

"typescript@>=2.6.2 <2.8", typescript@~2.7.2:
"typescript@>=2.6.2 <2.8":
version "2.7.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.2.tgz#2d615a1ef4aee4f574425cdff7026edf81919836"

typescript@~2.8.4:
version "2.8.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.4.tgz#0b1db68e6bdfb0b767fa2ab642136a35b059b199"

typescript@~2.9.1:
version "2.9.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c"
Expand Down

0 comments on commit 883cb46

Please sign in to comment.