Skip to content
This repository has been archived by the owner on Jul 27, 2018. It is now read-only.

Commit

Permalink
feat(actions): Added handling of an array for router actions (#12)
Browse files Browse the repository at this point in the history
The angular/router supports navigation provided as an array of commands. The router actions
now support an array as well as a string for navigation

Example:

go(['/path', { page: 1 }], { query: { show: true } })

Will navigate to `/path;page=1?show=true`
  • Loading branch information
brandonroberts authored and MikeRyanDev committed Aug 30, 2016
1 parent 5a1ed83 commit 3b4b827
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 63 deletions.
60 changes: 44 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
### Bindings to connect angular/router to ngrx/store


### Setup
### Installation

1. Use npm to install the bindings:
```
npm install @ngrx/router-store --save
```

2. Use the `routerReducer` when providing the `StoreModule.provideStore` and add the `RouterStoreModule.connectRouter` to the `@NgModule.imports`:
### Setup

1. Use the `routerReducer` when providing the `StoreModule.provideStore` and add the `RouterStoreModule.connectRouter` to the `@NgModule.imports`:

```ts
import { StoreModule } from '@ngrx/store';
Expand All @@ -27,26 +28,53 @@
}
```

3. Add `RouterState` to main application state:
2. Add `RouterState` to main application state:

```ts
import { RouterState } from '@ngrx/router-store';

export interface AppState {
router: RouterState;
};
```

### Dispatching actions
### Dispatching Actions

```ts
import { go, replace, search, show, back, forward } from '@ngrx/router-store';
```

#### Navigation with a new state into history

```ts
store.dispatch(go(['/path', { routeParam: 1 }], { query: 'string' }));
```

#### Navigation with replacing the current state in history

```ts
store.dispatch(replace(['/path'], { query: 'string' }));
```
#### Navigation without pushing a new state into history

```ts
store.dispatch(show(['/path'], { query: 'string' }));
```

#### Navigation with only changing query parameters

```ts
store.dispatch(search({ query: 'string' }));
```

#### Navigating back

```ts
import { go, replace, search, show, back, forward } from '@ngrx/router-store';
```ts
store.dispatch(back());
```

// ...
store.dispatch(go('/path', { query: 'string' }));
store.dispatch(replace('/path', { query: 'string' }));
store.dispatch(search({ query: 'string' }));
store.dispatch(show('/path', { query: 'string' }));
store.dispatch(back());
store.dispatch(forward());
```
#### Navigating forward

```ts
store.dispatch(forward());
```
40 changes: 32 additions & 8 deletions spec/actions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,64 @@ import * as actions from '../src/actions';

describe('Actions', function() {
it('should provide a "go" action creator', function() {
expect(actions.go('/path', 'query=string')).toEqual({
expect(actions.go('/path', { query: 'string' })).toEqual({
type: actions.routerActions.GO,
payload: {
path: '/path',
query: 'query=string'
query: { query: 'string' }
}
});

expect(actions.go(['/path'], { query: 'string' })).toEqual({
type: actions.routerActions.GO,
payload: {
path: ['/path'],
query: { query: 'string' }
}
});
});

it('should provide a "replace" action creator', function() {
expect(actions.replace('/path', 'query=string')).toEqual({
expect(actions.replace('/path', { query: 'string' })).toEqual({
type: actions.routerActions.REPLACE,
payload: {
path: '/path',
query: 'query=string'
query: { query: 'string' }
}
});

expect(actions.replace(['/path'], { query: 'string' })).toEqual({
type: actions.routerActions.REPLACE,
payload: {
path: ['/path'],
query: { query: 'string' }
}
});
});

it('should provide a "show" action creator', function() {
expect(actions.show('/path', 'query=string')).toEqual({
expect(actions.show('/path', { query: 'string' })).toEqual({
type: actions.routerActions.SHOW,
payload: {
path: '/path',
query: 'query=string'
query: { query: 'string' }
}
});

expect(actions.show(['/path'], { query: 'string' })).toEqual({
type: actions.routerActions.SHOW,
payload: {
path: ['/path'],
query: { query: 'string' }
}
});
});

it('should provide a "search" action creator', function() {
expect(actions.search('query=string')).toEqual({
expect(actions.search({ query: 'string' })).toEqual({
type: actions.routerActions.SEARCH,
payload: {
query: 'query=string'
query: { query: 'string' }
}
});
});
Expand Down
66 changes: 49 additions & 17 deletions spec/connect.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ describe('Router/Store Connectors', function() {
describe('listenForRouterMethodActions', function() {
let router: any;
let location: any;
let stringPath: string = '/path';
let stringArray: any[] = [stringPath];
let arrayPath: any[] = [stringPath, 1, { page: 1 }];

beforeEach(function() {
location = {
Expand All @@ -21,39 +24,68 @@ describe('Router/Store Connectors', function() {

router = {
navigate: jasmine.createSpy('navigate'),
navigateByUrl: jasmine.createSpy('navigate'),
parseUrl: jasmine.createSpy('navigate').and.returnValue({}),
replace: jasmine.createSpy('replace'),
search: jasmine.createSpy('search'),
show: jasmine.createSpy('show')
show: jasmine.createSpy('show'),
url: ''
};
});

it('should call Router@navigate when a "GO" action is dispatched', function() {
const action$ = Observable.of(routerActions.go('/path', 'query=string'));
listenForRouterMethodActions(router, location, action$);
describe('should call Router@navigate when a "GO" action is dispatched', function() {
it('with a string', function() {
const action$ = Observable.of(routerActions.go(stringPath, { query: 'string' }));
listenForRouterMethodActions(router, location, action$);

expect(router.navigate).toHaveBeenCalledWith(stringArray, { queryParams: { query: 'string' } });
});

it('with an array', function() {
const action$ = Observable.of(routerActions.go(arrayPath, { query: 'string' }));
listenForRouterMethodActions(router, location, action$);

expect(router.navigate).toHaveBeenCalledWith(['/path'], { queryParams: 'query=string' });
expect(router.navigate).toHaveBeenCalledWith(['/path', 1, { page: 1 }], { queryParams: { query: 'string' } });
});
});

it('should call Router@navigate when a "REPLACE" action is dispatched', function() {
const action$ = Observable.of(routerActions.replace('/path', 'query=string'));
listenForRouterMethodActions(router, location, action$);
describe('should call Router@navigateByUrl when a "REPLACE" action is dispatched', function() {
it('with a string', function() {
const action$ = Observable.of(routerActions.replace(stringPath, { query: 'string' }));
listenForRouterMethodActions(router, location, action$);

expect(router.navigate).toHaveBeenCalledWith(stringArray, { queryParams: { query: 'string' }, replaceUrl: true });
});

it('with an array', function() {
const action$ = Observable.of(routerActions.replace(arrayPath, { query: 'string' }));
listenForRouterMethodActions(router, location, action$);

expect(router.navigate).toHaveBeenCalledWith(['/path'], { queryParams: 'query=string', replaceUrl: true });
expect(router.navigate).toHaveBeenCalledWith(arrayPath, { queryParams: { query: 'string' }, replaceUrl: true });
});
});
describe('should call Router@navigate when a "SHOW" action is dispatched', function() {
it('with a string', function() {
const action$ = Observable.of(routerActions.show(stringPath, { query: 'string' }));
listenForRouterMethodActions(router, location, action$);

it('should call Router@navigate when a "SEARCH" action is dispatched', function() {
const action$ = Observable.of(routerActions.search('query=string'));
router.url = '/path';
listenForRouterMethodActions(router, location, action$);
expect(router.navigate).toHaveBeenCalledWith(stringArray, { queryParams: { query: 'string' }, skipLocationChange: true });
});

expect(router.navigate).toHaveBeenCalledWith(['/path'], { queryParams: 'query=string' });
it('with an array', function() {
const action$ = Observable.of(routerActions.show(arrayPath, { query: 'string' }));
listenForRouterMethodActions(router, location, action$);

expect(router.navigate).toHaveBeenCalledWith(arrayPath, { queryParams: { query: 'string' }, skipLocationChange: true });
});
});

it('should call Router@navigate when a "SHOW" action is dispatched', function() {
const action$ = Observable.of(routerActions.show('/path', 'query=string'));
it('should call Router@navigate when a "SEARCH" action is dispatched', function() {
const action$ = Observable.of(routerActions.search({ query: 'string' }));
router.url = '/path';
listenForRouterMethodActions(router, location, action$);

expect(router.navigate).toHaveBeenCalledWith(['/path'], { queryParams: 'query=string', skipLocationChange: true });
expect(router.navigateByUrl).toHaveBeenCalledWith({ queryParams: { query: 'string' } });
});

it('should call Location@back when a "BACK" action is dispatched', function() {
Expand Down
8 changes: 4 additions & 4 deletions src/actions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Action } from '@ngrx/store';

export interface RouterMethodCall {
path?: string;
path?: string | any[];
query?: any;
}

Expand All @@ -17,13 +17,13 @@ export const routerActions = {

export const routerActionTypes = Object.keys(routerActions).map(key => routerActions[key]);

export function go(path: string, query?: any): Action {
export function go(path: string|any[], query?: any): Action {
const payload: RouterMethodCall = { path, query };

return { type: routerActions.GO, payload };
}

export function replace(path: string, query?: any): Action {
export function replace(path: string|any[], query?: any): Action {
const payload: RouterMethodCall = { path, query };

return { type: routerActions.REPLACE, payload };
Expand All @@ -35,7 +35,7 @@ export function search(query: any): Action {
return { type: routerActions.SEARCH, payload };
}

export function show(path: string, query?: any): Action {
export function show(path: string|any[], query?: any): Action {
const payload: RouterMethodCall = { path, query };

return { type: routerActions.SHOW, payload };
Expand Down
10 changes: 5 additions & 5 deletions src/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/withLatestFrom';
import '@ngrx/core/add/operator/select';
import { Router, Event, NavigationEnd } from '@angular/router';
import { Router, Event, NavigationEnd, UrlTree } from '@angular/router';
import { Location } from '@angular/common';
import { Store, Action } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
Expand All @@ -17,7 +17,7 @@ export function listenForRouterMethodActions(router: Router, location: Location,
.filter(action => routerActionTypes.indexOf(action.type) > -1)
.subscribe(action => {
const { path, query: queryParams }: RouterMethodCall = action.payload;
let commands: any[] = [path];
let commands: any[] = Array.isArray(path) ? path : [path];

switch (action.type) {
case routerActions.GO:
Expand All @@ -29,9 +29,9 @@ export function listenForRouterMethodActions(router: Router, location: Location,
break;

case routerActions.SEARCH:
let url = router.url;
let command = url.split(/\?/)[0];
router.navigate([command], { queryParams });
let urlTree: UrlTree = router.parseUrl(router.url);
urlTree.queryParams = queryParams;
router.navigateByUrl(urlTree);
break;

case routerActions.SHOW:
Expand Down
26 changes: 13 additions & 13 deletions tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ Promise.all([
System.import('@angular/core/testing'),
System.import('@angular/platform-browser-dynamic/testing')
])
// First, initialize the Angular testing environment.
.then(([testing, testingBrowser]) => {
testing.getTestBed().initTestEnvironment(
testingBrowser.BrowserDynamicTestingModule,
testingBrowser.platformBrowserDynamicTesting()
);
})
// Then we find all the tests.
.then(() => (<any>require).context('./spec', true, /\.spec\.ts/))
// And load the modules.
.then(context => context.keys().map(context))
// Finally, start Karma to run the tests.
.then(__karma__.start, __karma__.error);
// First, initialize the Angular testing environment.
.then(([testing, testingBrowser]) => {
testing.getTestBed().initTestEnvironment(
testingBrowser.BrowserDynamicTestingModule,
testingBrowser.platformBrowserDynamicTesting()
);
})
// Then we find all the tests.
.then(() => (<any>require).context('./spec', true, /\.spec\.ts/))
// And load the modules.
.then(context => context.keys().map(context))
// Finally, start Karma to run the tests.
.then(__karma__.start, __karma__.error);

0 comments on commit 3b4b827

Please sign in to comment.