├── .github
├── .gitkeep
└── issue_template.md
├── .gitignore
├── .npmignore
├── .release-it.json
├── CHANGELOG.md
├── CONTRIBUTOR_GUIDE.md
├── LICENSE
├── Ngx-reduxor.png
├── README.md
├── README_v1.md
├── examples
├── all-effects.ts
├── app.store.ts
├── basic
│ ├── basic.actions.ts
│ ├── basic.effects.spec.ts
│ ├── basic.effects.ts
│ ├── basic.reducer.spec.ts
│ ├── basic.reducer.ts
│ ├── basic.service.spec.ts
│ └── basic.service.ts
├── crud
│ ├── crud.actions.ts
│ ├── crud.effects.spec.ts
│ ├── crud.effects.ts
│ ├── crud.reducer.spec.ts
│ ├── crud.reducer.ts
│ ├── crud.service.spec.ts
│ └── crud.service.ts
├── person
│ ├── person.actions.ts
│ ├── person.effects.spec.ts
│ ├── person.effects.ts
│ ├── person.reducer.spec.ts
│ ├── person.reducer.ts
│ ├── person.selectors.ts
│ ├── person.service.spec.ts
│ └── person.service.ts
├── router-serializer.ts
└── store-reduxor.module.ts
├── index.js
├── ngx-reduxor.config.json
├── package.json
├── plopfile.js
├── src
├── action_types
│ ├── update-all-effects.js
│ ├── update-app-store.js
│ └── update-store-reduxor.js
├── actions
│ ├── basic-actions.js
│ ├── crud-actions.js
│ ├── crud-entity-actions.js
│ ├── modules-actions.js
│ └── router-serializer.js
├── default.config.json
├── generators
│ ├── add-router-serializer.js
│ ├── create.js
│ └── exit.js
├── helpers.js
├── initConfig.js
└── validators.js
├── templates
├── Basic
│ ├── _actions.ts
│ ├── _effect.spec.ts
│ ├── _effect.ts
│ ├── _reducer.spec.ts
│ ├── _reducer.ts
│ ├── _service.spec.ts
│ └── _service.ts
├── CRUD-entity
│ ├── _actions.ts
│ ├── _effect.spec.ts
│ ├── _effect.ts
│ ├── _reducer.spec.ts
│ ├── _reducer.ts
│ ├── _selectors.ts
│ ├── _service.spec.ts
│ └── _service.ts
├── CRUD
│ ├── _actions.ts
│ ├── _effect.spec.ts
│ ├── _effect.ts
│ ├── _reducer.spec.ts
│ ├── _reducer.ts
│ ├── _service.spec.ts
│ └── _service.ts
├── _all-effects.ts
├── _app.store.ts
├── _router-serializer.ts
└── _store-reduxor.module.ts
└── yarn.lock
/.github/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmathy/ngx-reduxor/7d0d60f66826d6caba5711185651ead72d655cd4/.github/.gitkeep
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 | ## Issue Type
2 |
3 |
4 |
5 |
6 |
7 | ## version + node environment
8 |
9 |
10 | ## Actual behavior
11 |
12 |
13 | ## Expected behavior
14 |
15 |
16 | ## example (repo, code,...)
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .vscode
3 | node_modules
4 | store
5 | Todo.txt
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | node_modules
3 | store
4 | Todo.txt
5 | examples
--------------------------------------------------------------------------------
/.release-it.json:
--------------------------------------------------------------------------------
1 | {
2 | "src": {
3 | "tagName": "v%s"
4 | },
5 | "github": {
6 | "release": true,
7 | "tokenRef": "GITHUB_TOKEN"
8 | }
9 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | To get changelog of the latest versions, check [releases github](https://github.com/kmathy/ngx-reduxor/releases)
2 |
3 | ## 2.0.2 (01/01/2018)
4 | **Fix:**: remove unused option
5 | **Fix:**: make StoreDevtoolModule optional
6 |
7 | # 2.0.0 (31/12/2017)
8 | ## BREAKING CHANGES
9 | **Config:** Config file added.
10 |
11 | ## Features
12 | **Feature:** @ngrx/entity implemented for CRUD actions.
13 | **Feature:** Add StoreDevTool for a better debugging.
14 | **Feature:** Add StoreFreeze to prevent state to be mutated.
15 |
16 | **Doc:** new version, new documentation.
17 | **Dependencies:** Update dependencies needed.
18 |
19 | ## 1.0.1 (12/02/2017)
20 | **Fix:** Add forgotten HttpClientModule
21 |
--------------------------------------------------------------------------------
/CONTRIBUTOR_GUIDE.md:
--------------------------------------------------------------------------------
1 | # Contributor Guide
2 |
3 | Firstly, thanks for your interest and your willing to contribute to this project!
4 |
5 | To contribute, just follow these steps:
6 |
7 | ----- If you want to see organized issues concerning the repositories, not mandatory -----
8 |
9 | 1. Go to [Zenhub](https://www.zenhub.com/)
10 | 2. Login with your github account and add the chrome/firefox extension zenhub
11 | 3. Refresh the github repository and you will find organized issue in Board.
12 |
13 | ---
14 |
15 | 4. Fork the project
16 | 5. Do what you want
17 | 6. Send a pull request with your code! Please be explicit of what changed, if it is a patch or a minor change,...
18 |
19 | The generator use Plop.js. ([Documentation](https://plopjs.com/documentation/))
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Kevin Mathy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Ngx-reduxor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmathy/ngx-reduxor/7d0d60f66826d6caba5711185651ead72d655cd4/Ngx-reduxor.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ## [For Version 1, check this readme.md](https://github.com/kmathy/ngx-reduxor/blob/master/README_v1.md)
8 |
9 | ## Articles associated
10 | [Medium 23/02/2018](https://blog.cloudboost.io/ngx-reduxor-generate-a-complete-ngrx-architecture-in-one-command-574e3ea76f2d)
11 |
12 | [Medium 10/12/2017](https://blog.cloudboost.io/ngx-reduxor-do-you-use-ngrx-in-your-angular-app-check-this-generator-b3386e7bf8bf)
13 |
14 | ## Intro
15 | If you are a user of @ngrx libs, check this generator! This will help you to save time by creating an architecture for your ngrx files.
16 |
17 | * Create an **entity** is easy. Just provide a name and it will generate all the files!
18 | * Create a **router-serializer** to use @ngrx/router-store.
19 | * Provide a module to easily import all the reducers, effects and services in your Angular app.
20 | * MetaReducer: non-invasive logger in development.
21 | * Mainly inspired by the [ngrx styleguide](https://github.com/orizens/ngrx-styleguide)
22 | * Use the latest Angular HttpClient.
23 | * Use lettable operators in Rxjs.
24 | * Provide unit tests.
25 |
26 | ## prerequisites
27 |
28 | ```
29 | @ngrx/store@^5.0.0 // Required
30 | @ngrx/effects@^5.0.1 // Required
31 | @ngrx/entity@^5.0.1 // Optional but recommended. (Required if you generate entity)
32 | ngrx-store-freeze // Optional but recommended. Useful to prevent state mutation.
33 | ```
34 |
35 | ## Getting Started
36 | ### Globally
37 | Install it via npm:
38 |
39 | ```shell
40 | npm install -g ngx-reduxor
41 | ```
42 |
43 | or with Yarn:
44 | ```shell
45 | yarn global add ngx-reduxor
46 | ```
47 | ### Locally in a project
48 | Install it via npm:
49 |
50 | ```shell
51 | npm install ngx-reduxor --save-dev
52 | ```
53 |
54 | or with Yarn:
55 | ```shell
56 | yarn add --dev ngx-reduxor
57 | ```
58 |
59 | ## Configuration
60 |
61 | If you want to install it locally, you have to add the following in your package.json:
62 |
63 | ```
64 | ...
65 | "scripts": {
66 | "ngx-reduxor": "ngx-reduxor"
67 | },
68 | ```
69 |
70 | If installed globally, you can directly use the Generator like the Angular CLI.
71 | The first time you launch the generator, it will create a new file for the configuration:
72 |
73 | | Option | Description | Type | Default |
74 | |--------------------|-------------------------------------------------------------------------|---------|-------------------|
75 | | BASE_PATH | Path to put the generated files | String | './src/app/store' |
76 | | SEPARATE_DIRECTORY | If true, separate each action, reducer, service,... In their own folder | Boolean | false |
77 | | IGNORE_SPEC | If true, skip generation of spec files | Boolean | false |
78 |
79 | ## Usage
80 | You need to use the generator at the root of your project.
81 |
82 | If installed globally:
83 | ```shell
84 | ngx-reduxor
85 | ```
86 |
87 | Otherwise:
88 | ```
89 | npm run ngx-reduxor
90 | ```
91 |
92 | ## Import to your Angular application
93 | A module is created once you generate your first ngrx files. This module contains all the necessary: Reducers, interfaces, meta-reducers, effects,...
94 | You just need to import the module inside the AppModule and it will expose the store in your application.
95 |
96 | ```
97 | ...
98 | import { StoreReduxorModule } from './path/to/your/store-reduxor.module';
99 | ...
100 | @NgModule({
101 | ...
102 | imports: [
103 | ...
104 | StoreReduxorModule.forRoot(),
105 | ...
106 | ]
107 | ...
108 | ```
109 |
110 | ## Generate Entity
111 |
112 | If you want to generate an entity, you need to provide the **singular** of the name. After, you will be asked to provide
113 | the plural of this name in order to correctly generate the files.
114 |
115 | ## Generate router-serializer
116 |
117 | To use this feature:
118 | * You firstly need to generate your first store object. So that, the module and the required files (app.store,...) are created.
119 | * Ensure you have **@ngrx/router-store** installed in your project and existing in your package.json.
120 |
121 | If these two conditions are met, you will be able to generate the router-serializer (it also update store-reduxor.module and app.store with routerReducer ect)
122 |
123 | ## Contributors
124 |
125 | Special thanks to contributors for their hard work!! 🙏 Do you want to contribute? Check the [Contributor Guide](https://github.com/kmathy/ngx-reduxor/blob/master/CONTRIBUTOR_GUIDE.md)
126 |
127 |
128 |
129 |
130 |
131 | ## Issues/Improvements
132 | Don't hesitate to send a PR or to contribute to this project. If you have suggestion or a problem, feel free to open an issue.
133 |
--------------------------------------------------------------------------------
/README_v1.md:
--------------------------------------------------------------------------------
1 | # ngx-reduxor: save time by generating ngrx architecture with no effort!
2 |
3 | ## Intro
4 | If you are a user of @ngrx libs, check this generator! This will help you to save time by creating an architecture for your ngrx files.
5 |
6 | Initially created by @NetanelBasal (called ngrx-generator). After some times,
7 | and using the recommendations of the community, the generator has been improved.
8 |
9 | Now, it has the following features:
10 | * Create Actions, reducers, effects and services in one command! Just provide a name and the generator will do the rest.
11 | * Provide a module to easily import all the reducers, effects and services in your Angular app.
12 | * Naming convention to prevent errors.
13 | * MetaReducer: non-invasive logger in development.
14 | * Generator available: CRUD actions, Basic actions.
15 | * Mainly inspired by the [ngrx styleguide](https://github.com/orizens/ngrx-styleguide)
16 | * Use the latest Angular HttpClient.
17 | * Provide unit tests.
18 |
19 | ## prerequisites
20 |
21 | ```
22 | @ngrx/store@^4.0.0
23 | @ngrx/effects@^4.0.0
24 | ```
25 |
26 | ## Getting Started
27 |
28 | Install it via npm:
29 |
30 | ```shell
31 | npm install ngx-reduxor --save-dev
32 | ```
33 |
34 | or with Yarn:
35 | ```shell
36 | yarn add --dev ngx-reduxor
37 | ```
38 |
39 | ## Configuration
40 |
41 | in your package.json, add the following:
42 |
43 | ```
44 | ...
45 | "scripts": {
46 | "ngx-reduxor": "ngx-reduxor"
47 | },
48 | ...
49 | "ngxReduxor": {
50 | "basePath": "./src/app/store", // For example, you can set it everywhere
51 | "separateDirectory": false // Or true if you want a directory for each actions, effects, reducers,...
52 | }
53 | ...
54 | ```
55 |
56 | ## Import to your Angular app
57 | The first time you launch the generator, it creates a module called StoreReduxorModule.
58 | This module regroups all the reducers, effects and provided services created.
59 | There is also an index file containing the list of all reducers and you can also use this file to create some metaReducer,
60 | like a logger (for debugging purpose).
61 | After that, in your AppModule, you just need to import the module and it will expose the store in your app.
62 |
63 | ```
64 | ...
65 | import { StoreReduxorModule } from './path/to/your/store-reduxor.module';
66 | ...
67 | @NgModule({
68 | ...
69 | imports: [
70 | ...
71 | StoreReduxorModule.forRoot(),
72 | ...
73 | ]
74 | ...
75 | ```
76 |
77 | ## Usage
78 |
79 | ```shell
80 | npm run ngx-reduxor
81 | ```
82 |
83 | And then, follow the instructions... ;-)
84 | Congratulations! You are now able to create huge and scalable app in Redux inside your Angular applications!
85 |
86 | ## Issues/Improvements
87 | Don't hesitate to send a PR or to contribute to this project. If you have suggestion or a problem, feel free to open an issue.
88 |
89 | ## [Wiki](https://github.com/kmathy/ngx-reduxor/wiki)
90 | A wiki is being written to provide a documentation of the architecture for each generator, some best practices to use in Ngrx or to facilitate your development. Everyone can contribute. This is also a place to put some kind messages about how you love this tool (A bit of softness in this brutal world)
91 |
92 | ## Roadmap
93 | It is just the beginning! The idea is to provide a tool that can help you to manage easily your ngrx applications and also, create powerful actions. [See the next version](https://github.com/kmathy/ngx-reduxor/projects/1)
94 |
--------------------------------------------------------------------------------
/examples/all-effects.ts:
--------------------------------------------------------------------------------
1 | // -- IMPORT --
2 | import { PersonEffects } from './person/person.effects';
3 | import { CrudEffects } from './crud/crud.effects';
4 | import { BasicEffects } from './basic/basic.effects';
5 |
6 | export const AllEffects = [
7 | // -- LIST --
8 | PersonEffects,
9 | CrudEffects,
10 | BasicEffects
11 | ];
12 |
--------------------------------------------------------------------------------
/examples/app.store.ts:
--------------------------------------------------------------------------------
1 | import { routerReducer, RouterReducerState } from '@ngrx/router-store';
2 | import {
3 | ActionReducerMap,
4 | ActionReducer,
5 | MetaReducer,
6 | createSelector,
7 | createFeatureSelector,
8 | } from '@ngrx/store';
9 | /**
10 | * storeFreeze prevents state from being mutated. When mutation occurs, an
11 | * exception will be thrown. This is useful during development mode to
12 | * ensure that none of the reducers accidentally mutates the state.
13 | */
14 | import { storeFreeze } from 'ngrx-store-freeze';
15 |
16 | import { environment } from 'environments/environment';
17 |
18 | // -- IMPORT REDUCER --
19 | import * as person from './person/person.reducer';
20 | import * as router from './router-serializer';
21 | import * as crud from './crud/crud.reducer';
22 | import * as basic from './basic/basic.reducer';
23 |
24 | export interface State {
25 | // -- IMPORT STATE --
26 | person: person.PersonState;
27 | router: RouterReducerState;
28 | crud: crud.CrudState;
29 | basic: basic.BasicState;
30 | }
31 |
32 | export const reducers: ActionReducerMap = {
33 | // -- ADD REDUCER --
34 | person: person.reducer,
35 | router: routerReducer,
36 | crud: crud.reducer,
37 | basic: basic.reducer
38 | };
39 |
40 | /** For debug purpose */
41 | export function logger(reducer: ActionReducer): ActionReducer {
42 | return function (state: State, action: any): State {
43 | console.groupCollapsed(action.type);
44 | const nextState = reducer(state, action);
45 | console.log(`%c previous state`, `color: #9E9E9E; font-weight: bold`, state);
46 | console.log(`%c action`, `color: #03A9F4; font-weight: bold`, action);
47 | console.log(`%c next state`, `color: #4CAF50; font-weight: bold`, nextState);
48 | console.groupEnd();
49 | return nextState;
50 | };
51 | }
52 |
53 | export const metaReducers: MetaReducer[] = !environment.production
54 | ? [logger, storeFreeze]
55 | : [];
56 |
--------------------------------------------------------------------------------
/examples/basic/basic.actions.ts:
--------------------------------------------------------------------------------
1 | import { Action } from '@ngrx/store';
2 |
3 | export const LOAD_BASIC = '[Basic] Load Basic';
4 | export const LOAD_BASIC_SUCCESS = '[Basic] Load Basic Success';
5 | export const LOAD_BASIC_FAIL = '[Basic] Load Basic Fail';
6 |
7 | /**
8 | * Load Basic Actions
9 | */
10 | export class LoadBasicAction implements Action {
11 | readonly type = LOAD_BASIC;
12 |
13 | constructor(public payload: any) { }
14 | }
15 |
16 | export class LoadBasicSuccessAction implements Action {
17 | readonly type = LOAD_BASIC_SUCCESS;
18 |
19 | constructor(public payload: any) { }
20 | }
21 |
22 | export class LoadBasicFailAction implements Action {
23 | readonly type = LOAD_BASIC_FAIL;
24 |
25 | constructor(public error: Error) { }
26 | }
27 |
28 | export type Actions =
29 | | LoadBasicAction
30 | | LoadBasicSuccessAction
31 | | LoadBasicFailAction;
--------------------------------------------------------------------------------
/examples/basic/basic.effects.spec.ts:
--------------------------------------------------------------------------------
1 | import { fakeAsync, TestBed, tick } from '@angular/core/testing';
2 | import { BasicEffects } from './basic.effects';
3 | import { BasicService } from './basic.service';
4 |
5 | describe('BasicEffects', () => {
6 | let runner, basicEffects, basicService;
7 |
8 | beforeEach(() => TestBed.configureTestingModule({
9 | imports: [],
10 | providers: [
11 | BasicEffects,
12 | {
13 | provide: BasicService,
14 | useValue: jasmine.createSpyObj('basicService', ['get'])
15 | }
16 | ]
17 | }));
18 |
19 | beforeEach(() => {
20 | basicEffects = TestBed.get(BasicEffects);
21 | basicService = TestBed.get(BasicService);
22 | });
23 |
24 | describe('basic$', () => {
25 |
26 | it('should return a LOAD_BASIC_SUCCESS action, on success', function () {
27 |
28 | });
29 |
30 | it('should return a LOAD_BASIC_FAIL action, on error', function () {
31 |
32 | });
33 |
34 | });
35 |
36 | });
37 |
--------------------------------------------------------------------------------
/examples/basic/basic.effects.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Actions, Effect, ofType } from '@ngrx/effects';
3 | import { of as observableOf } from 'rxjs';
4 | import { switchMap,map,catchError } from 'rxjs/operators';
5 | import { BasicService } from './basic.service';
6 | import * as basicActions from './basic.actions';
7 |
8 | @Injectable()
9 | export class BasicEffects {
10 | @Effect() loadBasic$;
11 |
12 | constructor(
13 | private basicService: BasicService,
14 | private actions$: Actions
15 | ) {
16 | this.loadBasic$ = this.actions$
17 | .pipe(
18 | ofType(basicActions.LOAD_BASIC),
19 | switchMap((state: basicActions.LoadBasicAction) =>
20 | this.basicService.loadBasic().pipe(
21 | // If successful, dispatch success action with result
22 | map(res => new basicActions.LoadBasicSuccessAction(res)),
23 | // If request fails, dispatch failed action
24 | catchError((err: Error) => observableOf(new basicActions.LoadBasicFailAction(err)))
25 | )
26 | )
27 | );
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/examples/basic/basic.reducer.spec.ts:
--------------------------------------------------------------------------------
1 | import { reducer } from './basic.reducer';
2 | import * as fromBasic from './basic.reducer';
3 |
4 | describe('BasicReducer', () => {
5 |
6 | describe('undefined action', () => {
7 | it('should return the default state', () => {
8 | const action = {} as any;
9 |
10 | const result = reducer(undefined, action);
11 | expect(result).toEqual(fromBasic.initialState);
12 | });
13 | });
14 |
15 | });
--------------------------------------------------------------------------------
/examples/basic/basic.reducer.ts:
--------------------------------------------------------------------------------
1 | import * as basic from './basic.actions';
2 |
3 | export interface BasicState {
4 | loading: boolean;
5 | entities: { [id: string]: any };
6 | result: string[];
7 | error: Error;
8 | type: string;
9 | };
10 |
11 | export const initialState: BasicState = {
12 | loading: false,
13 | entities: {},
14 | result: [],
15 | error: null,
16 | type: ''
17 | };
18 |
19 | export function reducer(state = initialState, action: basic.Actions): BasicState {
20 | switch (action.type) {
21 | case basic.LOAD_BASIC: {
22 | return {
23 | ...state,
24 | loading: true,
25 | error: null,
26 | type: action.type
27 | }
28 | }
29 |
30 | case basic.LOAD_BASIC_SUCCESS: {
31 | return {
32 | ...state,
33 | result: action.payload,
34 | loading: false,
35 | error: null,
36 | type: action.type
37 | };
38 | }
39 |
40 | case basic.LOAD_BASIC_FAIL: {
41 | return {
42 | ...state,
43 | loading: false,
44 | error: action.error,
45 | type: action.type
46 | };
47 | }
48 |
49 | default: {
50 | return state;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/examples/basic/basic.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, getTestBed } from '@angular/core/testing';
2 | import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
3 | import { BasicService } from './basic.service';
4 |
5 | describe('Service: BasicService', () => {
6 |
7 | let injector: TestBed;
8 | let service: BasicService;
9 | let httpMock: HttpTestingController;
10 |
11 | beforeEach(() => {
12 | TestBed.configureTestingModule({
13 | imports: [HttpClientTestingModule],
14 | providers: [BasicService]
15 | });
16 | injector = getTestBed();
17 | service = injector.get(BasicService);
18 | httpMock = injector.get(HttpTestingController);
19 | });
20 |
21 | afterEach(() => {
22 | httpMock.verify();
23 | });
24 |
25 | // --------------------Example------------------------
26 |
27 | // const data = {
28 | // 'title': 'Book Title',
29 | // 'author': 'John Smith',
30 | // 'volumeId': '12345'
31 | // };
32 |
33 | // const books = {
34 | // items: [
35 | // {id: '12345', volumeInfo: {title: 'Title'}},
36 | // {id: '67890', volumeInfo: {title: 'Another Title'}}
37 | // ]
38 | // };
39 |
40 | // const queryTitle = 'Book Title';
41 |
42 | // it('should call the search api and return the search results', (done) => {
43 | // backend.connections.subscribe((connection: MockConnection) => {
44 | // const options = new ResponseOptions({
45 | // body: JSON.stringify(books)
46 | // });
47 | // connection.mockRespond(new Response(options));
48 | // expect(connection.request.method).toEqual(RequestMethod.Get);
49 | // expect(connection.request.url).toEqual(`https://www.googleapis.com/books/v1/volumes?q=${queryTitle}`);
50 | // });
51 |
52 | // service
53 | // .searchBooks(queryTitle)
54 | // .subscribe((res) => {
55 | // expect(res).toEqual(books.items);
56 | // done();
57 | // });
58 | // });
59 |
60 | });
61 |
--------------------------------------------------------------------------------
/examples/basic/basic.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 |
5 | @Injectable()
6 | export class BasicService {
7 |
8 | constructor(private http: HttpClient) { }
9 |
10 | loadBasic(): Observable {
11 | return this.http.get('https://api.com');
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/crud/crud.actions.ts:
--------------------------------------------------------------------------------
1 | import { Action } from '@ngrx/store';
2 | import { HttpErrorResponse } from '@angular/common/http';
3 |
4 | export const GET_CRUD = '[Crud] Get Crud';
5 | export const GET_CRUD_SUCCESS = '[Crud] Get Crud Success';
6 | export const GET_CRUD_FAIL = '[Crud] Get Crud Fail';
7 |
8 | export const CREATE_CRUD = '[Crud] Create Crud';
9 | export const CREATE_CRUD_SUCCESS = '[Crud] Create Crud Success';
10 | export const CREATE_CRUD_FAIL = '[Crud] Create Crud Fail';
11 |
12 | export const UPDATE_CRUD = '[Crud] Update Crud';
13 | export const UPDATE_CRUD_SUCCESS = '[Crud] Update Crud Success';
14 | export const UPDATE_CRUD_FAIL = '[Crud] Update Crud Fail';
15 |
16 | export const DELETE_CRUD = '[Crud] Delete Crud';
17 | export const DELETE_CRUD_SUCCESS = '[Crud] Delete Crud Success';
18 | export const DELETE_CRUD_FAIL = '[Crud] Delete Crud Fail';
19 |
20 | /**
21 | * Get Crud Actions
22 | * e.g GetAuthAction
23 | */
24 | export class GetCrudAction implements Action {
25 | readonly type = GET_CRUD;
26 |
27 | constructor(public payload: any) { }
28 | }
29 |
30 | export class GetCrudSuccessAction implements Action {
31 | readonly type = GET_CRUD_SUCCESS;
32 |
33 | constructor(public payload: any) { }
34 | }
35 |
36 | export class GetCrudFailAction implements Action {
37 | readonly type = GET_CRUD_FAIL;
38 |
39 | constructor(public error: HttpErrorResponse) { }
40 | }
41 |
42 | /**
43 | * Create Crud Actions
44 | */
45 | export class CreateCrudAction implements Action {
46 | readonly type = CREATE_CRUD;
47 |
48 | constructor(public payload: any) { }
49 | }
50 |
51 | export class CreateCrudSuccessAction implements Action {
52 | readonly type = CREATE_CRUD_SUCCESS;
53 |
54 | constructor(public payload: any) { }
55 | }
56 |
57 | export class CreateCrudFailAction implements Action {
58 | readonly type = CREATE_CRUD_FAIL;
59 |
60 | constructor(public error: HttpErrorResponse) { }
61 | }
62 |
63 | /**
64 | * Update Crud Actions
65 | */
66 | export class UpdateCrudAction implements Action {
67 | readonly type = UPDATE_CRUD;
68 |
69 | constructor(public payload: any) { }
70 | }
71 |
72 | export class UpdateCrudSuccessAction implements Action {
73 | readonly type = UPDATE_CRUD_SUCCESS;
74 |
75 | constructor(public payload: any) { }
76 | }
77 |
78 | export class UpdateCrudFailAction implements Action {
79 | readonly type = UPDATE_CRUD_FAIL;
80 |
81 | constructor(public error: HttpErrorResponse) { }
82 | }
83 |
84 | /**
85 | * Delete Crud Actions
86 | */
87 | export class DeleteCrudAction implements Action {
88 | readonly type = DELETE_CRUD;
89 |
90 | constructor(public payload: any) { }
91 | }
92 |
93 | export class DeleteCrudSuccessAction implements Action {
94 | readonly type = DELETE_CRUD_SUCCESS;
95 |
96 | constructor(public payload: any) { }
97 | }
98 |
99 | export class DeleteCrudFailAction implements Action {
100 | readonly type = DELETE_CRUD_FAIL;
101 |
102 | constructor(public error: HttpErrorResponse) { }
103 | }
104 |
105 | export type Actions =
106 | | GetCrudAction
107 | | GetCrudSuccessAction
108 | | GetCrudFailAction
109 | | CreateCrudAction
110 | | CreateCrudSuccessAction
111 | | CreateCrudFailAction
112 | | UpdateCrudAction
113 | | UpdateCrudSuccessAction
114 | | UpdateCrudFailAction
115 | | DeleteCrudAction
116 | | DeleteCrudSuccessAction
117 | | DeleteCrudFailAction
118 | ;
119 |
--------------------------------------------------------------------------------
/examples/crud/crud.effects.spec.ts:
--------------------------------------------------------------------------------
1 | import { fakeAsync, TestBed, tick } from '@angular/core/testing';
2 | import { CrudEffects } from './crud.effects';
3 | import { CrudService } from './crud.service';
4 |
5 | describe('CrudEffects', () => {
6 | let runner, crudEffects, crudService;
7 |
8 | beforeEach(() => TestBed.configureTestingModule({
9 | imports: [],
10 | providers: [
11 | CrudEffects,
12 | {
13 | provide: CrudService,
14 | useValue: jasmine.createSpyObj('crudService', ['get'])
15 | }
16 | ]
17 | }));
18 |
19 | beforeEach(() => {
20 | crudEffects = TestBed.get(CrudEffects);
21 | crudService = TestBed.get(CrudService);
22 | });
23 |
24 | describe('crud$', () => {
25 |
26 | it('should return a LOAD_CRUD_SUCCESS action, on success', function () {
27 |
28 | });
29 |
30 | it('should return a LOAD_CRUD_FAIL action, on error', function () {
31 |
32 | });
33 |
34 | });
35 |
36 | });
37 |
--------------------------------------------------------------------------------
/examples/crud/crud.effects.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Actions, Effect, ofType } from '@ngrx/effects';
3 | import { of as observableOf } from 'rxjs';
4 | import { switchMap, map, catchError } from 'rxjs/operators';
5 | import { HttpErrorResponse } from '@angular/common/http';
6 | import { CrudService } from './crud.service';
7 | import * as crudActions from './crud.actions';
8 |
9 | @Injectable()
10 | export class CrudEffects {
11 | @Effect() get$;
12 | @Effect() create$;
13 | @Effect() update$;
14 | @Effect() delete$;
15 |
16 | constructor(
17 | private crudService: CrudService,
18 | private actions$: Actions
19 | ) {
20 | this.get$ = this.actions$
21 | .pipe(
22 | ofType(crudActions.GET_CRUD),
23 | switchMap((state: crudActions.GetCrudAction) =>
24 | this.crudService.getCrud().pipe(
25 | // If successful, dispatch success action with result
26 | map(res => new crudActions.GetCrudSuccessAction(res)),
27 | // If request fails, dispatch failed action
28 | catchError((err: HttpErrorResponse) => observableOf(new crudActions.GetCrudFailAction(err)))
29 | )
30 | ));
31 | this.create$ = this.actions$
32 | .pipe(
33 | ofType(crudActions.CREATE_CRUD),
34 | switchMap((state: crudActions.CreateCrudAction) =>
35 | this.crudService.createCrud(state.payload).pipe(
36 | // If successful, dispatch success action with result
37 | map(res => new crudActions.CreateCrudSuccessAction(res)),
38 | // If request fails, dispatch failed action
39 | catchError((err: HttpErrorResponse) => observableOf(new crudActions.CreateCrudFailAction(err)))
40 | )
41 | ));
42 | this.update$ = this.actions$
43 | .pipe(
44 | ofType(crudActions.UPDATE_CRUD),
45 | switchMap((state: crudActions.UpdateCrudAction) =>
46 | this.crudService.updateCrud(state.payload).pipe(
47 | // If successful, dispatch success action with result
48 | map(res => new crudActions.UpdateCrudSuccessAction(res)),
49 | // If request fails, dispatch failed action
50 | catchError((err: HttpErrorResponse) => observableOf(new crudActions.UpdateCrudFailAction(err)))
51 | )
52 | ));
53 | this.delete$ = this.actions$
54 | .pipe(
55 | ofType(crudActions.DELETE_CRUD),
56 | switchMap((state: crudActions.DeleteCrudAction) =>
57 | this.crudService.deleteCrud(state.payload).pipe(
58 | // If successful, dispatch success action with result
59 | map(res => new crudActions.DeleteCrudSuccessAction(res)),
60 | // If request fails, dispatch failed action
61 | catchError((err: HttpErrorResponse) => observableOf(new crudActions.DeleteCrudFailAction(err)))
62 | )
63 | ));
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/examples/crud/crud.reducer.spec.ts:
--------------------------------------------------------------------------------
1 | import { reducer } from './crud.reducer';
2 | import * as fromCrud from './crud.reducer';
3 |
4 | describe('CrudReducer', () => {
5 |
6 | describe('undefined action', () => {
7 | it('should return the default state', () => {
8 | const action = {} as any;
9 |
10 | const result = reducer(undefined, action);
11 | expect(result).toEqual(fromCrud.initialState);
12 | });
13 | });
14 |
15 | });
--------------------------------------------------------------------------------
/examples/crud/crud.reducer.ts:
--------------------------------------------------------------------------------
1 | import * as crud from './crud.actions';
2 | import { HttpErrorResponse } from '@angular/common/http';
3 |
4 | export interface CrudState {
5 | loading: boolean;
6 | entities: { [id: string]: any };
7 | result: any[];
8 | error: HttpErrorResponse;
9 | type: string;
10 | };
11 |
12 | export const initialState: CrudState = {
13 | loading: false,
14 | entities: {},
15 | result: [],
16 | error: null,
17 | type: ''
18 | };
19 |
20 | export function reducer(state = initialState, action: crud.Actions): CrudState {
21 | switch (action.type) {
22 | case crud.GET_CRUD: {
23 | return {
24 | ...state,
25 | loading: true,
26 | error: null,
27 | type: action.type
28 | }
29 | }
30 |
31 | case crud.GET_CRUD_SUCCESS: {
32 | return {
33 | ...state,
34 | result: action.payload,
35 | loading: false,
36 | error: null,
37 | type: action.type
38 | };
39 | }
40 |
41 | case crud.GET_CRUD_FAIL: {
42 | return {
43 | ...state,
44 | loading: false,
45 | error: action.error,
46 | type: action.type
47 | };
48 | }
49 | case crud.CREATE_CRUD: {
50 | return {
51 | ...state,
52 | loading: true,
53 | error: null,
54 | type: action.type
55 | }
56 | }
57 |
58 | case crud.CREATE_CRUD_SUCCESS: {
59 | const result = [...state.result, action.payload];
60 | return {
61 | ...state,
62 | result: result,
63 | loading: false,
64 | error: null,
65 | type: action.type
66 | };
67 | }
68 |
69 | case crud.CREATE_CRUD_FAIL: {
70 | return {
71 | ...state,
72 | loading: false,
73 | error: action.error,
74 | type: action.type
75 | };
76 | }
77 | case crud.UPDATE_CRUD: {
78 | return {
79 | ...state,
80 | loading: true,
81 | error: null,
82 | type: action.type
83 | }
84 | }
85 |
86 | case crud.UPDATE_CRUD_SUCCESS: {
87 | return {
88 | ...state,
89 | loading: false,
90 | error: null,
91 | type: action.type
92 | };
93 | }
94 |
95 | case crud.UPDATE_CRUD_FAIL: {
96 | return {
97 | ...state,
98 | loading: false,
99 | error: action.error,
100 | type: action.type
101 | };
102 | }
103 | case crud.DELETE_CRUD: {
104 | return {
105 | ...state,
106 | loading: true,
107 | error: null,
108 | type: action.type
109 | }
110 | }
111 |
112 | case crud.DELETE_CRUD_SUCCESS: {
113 | return {
114 | ...state,
115 | loading: false,
116 | error: null,
117 | type: action.type
118 | };
119 | }
120 |
121 | case crud.DELETE_CRUD_FAIL: {
122 | return {
123 | ...state,
124 | loading: false,
125 | error: action.error,
126 | type: action.type
127 | };
128 | }
129 |
130 | default: {
131 | return state;
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/examples/crud/crud.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, getTestBed } from '@angular/core/testing';
2 | import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
3 | import { CrudService } from './crud.service';
4 |
5 | describe('Service: CrudService', () => {
6 |
7 | let injector: TestBed;
8 | let service: CrudService;
9 | let httpMock: HttpTestingController;
10 |
11 | beforeEach(() => {
12 | TestBed.configureTestingModule({
13 | imports: [HttpClientTestingModule],
14 | providers: [CrudService]
15 | });
16 | injector = getTestBed();
17 | service = injector.get(CrudService);
18 | httpMock = injector.get(HttpTestingController);
19 | });
20 |
21 | afterEach(() => {
22 | httpMock.verify();
23 | });
24 |
25 | // --------------------Example------------------------
26 |
27 | // const data = {
28 | // 'title': 'Book Title',
29 | // 'author': 'John Smith',
30 | // 'volumeId': '12345'
31 | // };
32 |
33 | // const books = {
34 | // items: [
35 | // {id: '12345', volumeInfo: {title: 'Title'}},
36 | // {id: '67890', volumeInfo: {title: 'Another Title'}}
37 | // ]
38 | // };
39 |
40 | // const queryTitle = 'Book Title';
41 |
42 | // it('should call the search api and return the search results', (done) => {
43 | // backend.connections.subscribe((connection: MockConnection) => {
44 | // const options = new ResponseOptions({
45 | // body: JSON.stringify(books)
46 | // });
47 | // connection.mockRespond(new Response(options));
48 | // expect(connection.request.method).toEqual(RequestMethod.Get);
49 | // expect(connection.request.url).toEqual(`https://www.googleapis.com/books/v1/volumes?q=${queryTitle}`);
50 | // });
51 |
52 | // service
53 | // .searchBooks(queryTitle)
54 | // .subscribe((res) => {
55 | // expect(res).toEqual(books.items);
56 | // done();
57 | // });
58 | // });
59 |
60 | });
61 |
--------------------------------------------------------------------------------
/examples/crud/crud.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 |
5 | @Injectable()
6 | export class CrudService {
7 |
8 | constructor(private http: HttpClient) { }
9 |
10 | getCrud(): Observable {
11 | return this.http.get('https://api.com');
12 | }
13 |
14 | createCrud(param: any): Observable {
15 | return this.http.post('https://api.com', { body: param });
16 | }
17 |
18 | updateCrud(param: any): Observable {
19 | return this.http.patch('https://api.com', { body: param });
20 | }
21 |
22 | deleteCrud(param: any): Observable {
23 | return this.http.delete('https://api.com');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/person/person.actions.ts:
--------------------------------------------------------------------------------
1 | import { Action } from '@ngrx/store';
2 | import { HttpErrorResponse } from '@angular/common/http';
3 |
4 | /**
5 | * Generate constants based on the given name
6 | * e.g export const LOAD_USERS = '[Auth] Load USERS'
7 | */
8 |
9 | export const LOAD_PEOPLE = '[People] Load People';
10 | export const LOAD_PEOPLE_SUCCESS = '[People] Load People Success';
11 | export const LOAD_PEOPLE_FAIL = '[People] Load People Fail';
12 |
13 | export const SELECT_PERSON = '[Person] Select Person';
14 |
15 | export const ADD_PERSON = '[Person] Add Person';
16 | export const ADD_PERSON_SUCCESS = '[Person] Add Person Success';
17 | export const ADD_PERSON_FAIL = '[Person] Add Person Fail';
18 |
19 | export const ADD_PEOPLE = '[People] Add People';
20 | export const ADD_PEOPLE_SUCCESS = '[People] Add People Success';
21 | export const ADD_PEOPLE_FAIL = '[People] Add People Fail';
22 |
23 | export const UPDATE_PERSON = '[Person] Update Person';
24 | export const UPDATE_PERSON_SUCCESS = '[Person] Update Person Success';
25 | export const UPDATE_PERSON_FAIL = '[Person] Update Person Fail';
26 |
27 | export const UPDATE_PEOPLE = '[People] Update People';
28 | export const UPDATE_PEOPLE_SUCCESS = '[People] Update People Success';
29 | export const UPDATE_PEOPLE_FAIL = '[People] Update People Fail';
30 |
31 | export const DELETE_PERSON = '[Person] Delete Person';
32 | export const DELETE_PERSON_SUCCESS = '[Person] Delete Person Success';
33 | export const DELETE_PERSON_FAIL = '[Person] Delete Person Fail';
34 |
35 | export const DELETE_PEOPLE = '[People] Delete People';
36 | export const DELETE_PEOPLE_SUCCESS = '[People] Delete People Success';
37 | export const DELETE_PEOPLE_FAIL = '[People] Delete People Fail';
38 |
39 | export const CLEAR_PEOPLE = '[People] Clear People';
40 |
41 | /**
42 | * Load People Actions
43 | * e.g LoadUsersAction
44 | */
45 | export class LoadPeopleAction implements Action {
46 | readonly type = LOAD_PEOPLE;
47 |
48 | constructor(public payload = '') { }
49 | }
50 |
51 | export class LoadPeopleSuccessAction implements Action {
52 | readonly type = LOAD_PEOPLE_SUCCESS;
53 |
54 | constructor(public payload: any) { }
55 | }
56 |
57 | export class LoadPeopleFailAction implements Action {
58 | readonly type = LOAD_PEOPLE_FAIL;
59 |
60 | constructor(public error: HttpErrorResponse) { }
61 | }
62 |
63 | /**
64 | * Select Person Action
65 | */
66 |
67 | export class SelectPersonAction implements Action {
68 | readonly type = SELECT_PERSON;
69 | constructor(public personID: string | number) {}
70 | }
71 |
72 | /**
73 | * Add Person Actions
74 | */
75 | export class AddPersonAction implements Action {
76 | readonly type = ADD_PERSON;
77 |
78 | constructor(public payload: any) { }
79 | }
80 |
81 | export class AddPersonSuccessAction implements Action {
82 | readonly type = ADD_PERSON_SUCCESS;
83 |
84 | constructor(public payload: any) { }
85 | }
86 |
87 | export class AddPersonFailAction implements Action {
88 | readonly type = ADD_PERSON_FAIL;
89 |
90 | constructor(public error: HttpErrorResponse) { }
91 | }
92 |
93 | /**
94 | * Add People Actions
95 | */
96 | export class AddPeopleAction implements Action {
97 | readonly type = ADD_PEOPLE;
98 |
99 | constructor(public payload: any) { }
100 | }
101 |
102 | export class AddPeopleSuccessAction implements Action {
103 | readonly type = ADD_PEOPLE_SUCCESS;
104 |
105 | constructor(public payload: any) { }
106 | }
107 |
108 | export class AddPeopleFailAction implements Action {
109 | readonly type = ADD_PEOPLE_FAIL;
110 |
111 | constructor(public error: HttpErrorResponse) { }
112 | }
113 |
114 | /**
115 | * Update Person Actions
116 | */
117 | export class UpdatePersonAction implements Action {
118 | readonly type = UPDATE_PERSON;
119 |
120 | constructor(public payload: any) { }
121 | }
122 |
123 | export class UpdatePersonSuccessAction implements Action {
124 | readonly type = UPDATE_PERSON_SUCCESS;
125 |
126 | constructor(public payload: any) { }
127 | }
128 |
129 | export class UpdatePersonFailAction implements Action {
130 | readonly type = UPDATE_PERSON_FAIL;
131 |
132 | constructor(public error: HttpErrorResponse) { }
133 | }
134 |
135 | /**
136 | * Update People Actions
137 | */
138 | export class UpdatePeopleAction implements Action {
139 | readonly type = UPDATE_PEOPLE;
140 |
141 | constructor(public payload: any) { }
142 | }
143 |
144 | export class UpdatePeopleSuccessAction implements Action {
145 | readonly type = UPDATE_PEOPLE_SUCCESS;
146 |
147 | constructor(public payload: any) { }
148 | }
149 |
150 | export class UpdatePeopleFailAction implements Action {
151 | readonly type = UPDATE_PEOPLE_FAIL;
152 |
153 | constructor(public error: HttpErrorResponse) { }
154 | }
155 |
156 | /**
157 | * Delete Person Actions
158 | */
159 | export class DeletePersonAction implements Action {
160 | readonly type = DELETE_PERSON;
161 |
162 | constructor(public payload: any) { }
163 | }
164 |
165 | export class DeletePersonSuccessAction implements Action {
166 | readonly type = DELETE_PERSON_SUCCESS;
167 |
168 | constructor(public payload: any) { }
169 | }
170 |
171 | export class DeletePersonFailAction implements Action {
172 | readonly type = DELETE_PERSON_FAIL;
173 |
174 | constructor(public error: HttpErrorResponse) { }
175 | }
176 |
177 | /**
178 | * Delete People Actions
179 | */
180 | export class DeletePeopleAction implements Action {
181 | readonly type = DELETE_PEOPLE;
182 |
183 | constructor(public payload: any) { }
184 | }
185 |
186 | export class DeletePeopleSuccessAction implements Action {
187 | readonly type = DELETE_PEOPLE_SUCCESS;
188 |
189 | constructor(public payload: any) { }
190 | }
191 |
192 | export class DeletePeopleFailAction implements Action {
193 | readonly type = DELETE_PEOPLE_FAIL;
194 |
195 | constructor(public error: HttpErrorResponse) { }
196 | }
197 |
198 | /**
199 | * Clear People Actions
200 | */
201 | export class ClearPeopleAction implements Action {
202 | readonly type = CLEAR_PEOPLE;
203 |
204 | constructor(public payload: any) { }
205 | }
206 |
207 | export type Actions =
208 | LoadPeopleAction
209 | | LoadPeopleSuccessAction
210 | | LoadPeopleFailAction
211 | | SelectPersonAction
212 | | AddPersonAction
213 | | AddPersonFailAction
214 | | AddPersonSuccessAction
215 | | AddPeopleAction
216 | | AddPeopleFailAction
217 | | AddPeopleSuccessAction
218 | | UpdatePersonAction
219 | | UpdatePersonSuccessAction
220 | | UpdatePersonFailAction
221 | | UpdatePeopleAction
222 | | UpdatePeopleSuccessAction
223 | | UpdatePeopleFailAction
224 | | DeletePersonAction
225 | | DeletePersonSuccessAction
226 | | DeletePersonFailAction
227 | | DeletePeopleAction
228 | | DeletePeopleSuccessAction
229 | | DeletePeopleFailAction
230 | | ClearPeopleAction;
231 |
--------------------------------------------------------------------------------
/examples/person/person.effects.spec.ts:
--------------------------------------------------------------------------------
1 | import { fakeAsync, TestBed, tick } from '@angular/core/testing';
2 | import { PersonEffects } from './person.effects';
3 | import { PersonService } from './person.service';
4 |
5 | describe('PersonEffects', () => {
6 | let runner, personEffects, personService;
7 |
8 | beforeEach(() => TestBed.configureTestingModule({
9 | imports: [],
10 | providers: [
11 | PersonEffects,
12 | {
13 | provide: PersonService,
14 | useValue: jasmine.createSpyObj('personService', ['get'])
15 | }
16 | ]
17 | }));
18 |
19 | beforeEach(() => {
20 | personEffects = TestBed.get(PersonEffects);
21 | personService = TestBed.get(PersonService);
22 | });
23 |
24 | describe('person$', () => {
25 |
26 | it('should return a LOAD_PERSON_SUCCESS action, on success', function () {
27 |
28 | });
29 |
30 | it('should return a LOAD_PERSON_FAIL action, on error', function () {
31 |
32 | });
33 |
34 | });
35 |
36 | });
37 |
--------------------------------------------------------------------------------
/examples/person/person.effects.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Actions, Effect, ofType } from '@ngrx/effects';
3 | import { of as observableOf } from 'rxjs';
4 | import { switchMap, map, catchError } from 'rxjs/operators';
5 | import { HttpErrorResponse } from '@angular/common/http';
6 | import { PersonService } from './person.service';
7 | import * as personActions from './person.actions';
8 |
9 | @Injectable()
10 | export class PersonEffects {
11 | @Effect() loadPeople$;
12 | @Effect() addPerson$;
13 | @Effect() addPeople$;
14 | @Effect() updatePerson$;
15 | @Effect() updatePeople$;
16 | @Effect() deletePerson$;
17 | @Effect() deletePeople$;
18 |
19 | constructor(
20 | private personService: PersonService,
21 | private actions$: Actions
22 | ) {
23 | this.loadPeople$ = this.actions$
24 | .pipe(
25 | ofType(personActions.LOAD_PEOPLE),
26 | switchMap((state: personActions.LoadPeopleAction) =>
27 | this.personService.loadPeople().pipe(
28 | // If successful, dispatch success action with result
29 | map(res => new personActions.LoadPeopleSuccessAction(res)),
30 | // If request fails, dispatch failed action
31 | catchError((err: HttpErrorResponse) => observableOf(new personActions.LoadPeopleFailAction(err)))
32 | )
33 | ));
34 |
35 | this.addPerson$ = this.actions$
36 | .pipe(
37 | ofType(personActions.ADD_PERSON),
38 | switchMap((state: personActions.AddPersonAction) =>
39 | this.personService.addPerson(state.payload).pipe(
40 | map(res => new personActions.AddPersonSuccessAction(res)),
41 | catchError((err: HttpErrorResponse) => observableOf(new personActions.AddPersonFailAction(err)))
42 | )
43 | ));
44 |
45 | this.addPeople$ = this.actions$
46 | .pipe(
47 | ofType(personActions.ADD_PEOPLE),
48 | switchMap((state: personActions.AddPeopleAction) =>
49 | this.personService.addPeople(state.payload).pipe(
50 | map(res => new personActions.AddPeopleSuccessAction(res)),
51 | catchError((err: HttpErrorResponse) => observableOf(new personActions.AddPeopleFailAction(err)))
52 | )
53 | ));
54 |
55 | this.updatePerson$ = this.actions$
56 | .pipe(
57 | ofType(personActions.UPDATE_PERSON),
58 | switchMap((state: personActions.UpdatePersonAction) =>
59 | this.personService.updatePerson(state.payload).pipe(
60 | map(res => new personActions.UpdatePersonSuccessAction(res)),
61 | catchError((err: HttpErrorResponse) => observableOf(new personActions.UpdatePersonFailAction(err)))
62 | )
63 | ));
64 |
65 | this.updatePeople$ = this.actions$
66 | .pipe(
67 | ofType(personActions.UPDATE_PEOPLE),
68 | switchMap((state: personActions.UpdatePeopleAction) =>
69 | this.personService.updatePeople(state.payload).pipe(
70 | map(res => new personActions.UpdatePeopleSuccessAction(res)),
71 | catchError((err: HttpErrorResponse) => observableOf(new personActions.UpdatePeopleFailAction(err)))
72 | )
73 | ));
74 |
75 | this.deletePerson$ = this.actions$
76 | .pipe(
77 | ofType(personActions.DELETE_PERSON),
78 | switchMap((state: personActions.DeletePersonAction) =>
79 | this.personService.deletePerson(state.payload).pipe(
80 | map(res => new personActions.DeletePersonSuccessAction(res)),
81 | catchError((err: HttpErrorResponse) => observableOf(new personActions.DeletePersonFailAction(err)))
82 | )
83 | ));
84 |
85 | this.deletePeople$ = this.actions$
86 | .pipe(
87 | ofType(personActions.DELETE_PEOPLE),
88 | switchMap((state: personActions.DeletePeopleAction) =>
89 | this.personService.deletePeople(state.payload).pipe(
90 | map(res => new personActions.DeletePeopleSuccessAction(res)),
91 | catchError((err: HttpErrorResponse) => observableOf(new personActions.DeletePeopleFailAction(err)))
92 | )
93 | ));
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/examples/person/person.reducer.spec.ts:
--------------------------------------------------------------------------------
1 | import { reducer } from './person.reducer';
2 | import * as fromPerson from './person.reducer';
3 |
4 | describe('PersonReducer', () => {
5 |
6 | describe('undefined action', () => {
7 | it('should return the default state', () => {
8 | const action = {} as any;
9 |
10 | const result = reducer(undefined, action);
11 | expect(result).toEqual(fromPerson.initialState);
12 | });
13 | });
14 |
15 | });
--------------------------------------------------------------------------------
/examples/person/person.reducer.ts:
--------------------------------------------------------------------------------
1 | import * as person from './person.actions';
2 | import { HttpErrorResponse } from '@angular/common/http';
3 |
4 | import { createEntityAdapter, EntityState, EntityAdapter } from '@ngrx/entity';
5 |
6 | export interface Person { // Or you can replace it by importing your own model
7 | id: number;
8 | }
9 |
10 | export const personAdapter: EntityAdapter = createEntityAdapter({
11 | sortComparer: false
12 | });
13 | // tslint:disable-next-line:no-empty-interface
14 | export interface PersonState extends EntityState {
15 | loading: boolean;
16 | error: HttpErrorResponse;
17 | selectedPersonID: string | number | null;
18 | }
19 |
20 | export const defaultPersonState = {
21 | loading: false,
22 | error: null,
23 | selectedPersonID: null
24 | };
25 |
26 | export const initialPersonState: PersonState = personAdapter.getInitialState(defaultPersonState);
27 |
28 | export function reducer(state = initialPersonState, action: person.Actions): PersonState {
29 | switch (action.type) {
30 | case person.LOAD_PEOPLE:
31 | return {
32 | ...state,
33 | loading: true,
34 | error: null
35 | };
36 |
37 | case person.LOAD_PEOPLE_SUCCESS:
38 | return {
39 | ...personAdapter.addAll(action.payload, state),
40 | loading: false,
41 | error: null
42 | };
43 |
44 | case person.LOAD_PEOPLE_FAIL:
45 | return {
46 | ...state,
47 | loading: false,
48 | error: action.error
49 | };
50 |
51 | case person.SELECT_PERSON:
52 | return {
53 | ...state,
54 | loading: false,
55 | error: null,
56 | selectedPersonID: action.personID
57 | };
58 |
59 | case person.ADD_PERSON:
60 | return {
61 | ...state,
62 | loading: true,
63 | error: null
64 | };
65 |
66 | case person.ADD_PERSON_SUCCESS:
67 | return {
68 | ...personAdapter.addOne(action.payload.person, state),
69 | loading: false,
70 | error: null
71 | };
72 |
73 | case person.ADD_PERSON_FAIL:
74 | return {
75 | ...state,
76 | loading: false,
77 | error: action.error
78 | };
79 |
80 | case person.ADD_PEOPLE:
81 | return {
82 | ...state,
83 | loading: true,
84 | error: null
85 | };
86 |
87 | case person.ADD_PEOPLE_SUCCESS:
88 | return {
89 | ...personAdapter.addMany(action.payload.people, state),
90 | loading: false,
91 | error: null
92 | };
93 |
94 | case person.ADD_PEOPLE_FAIL:
95 | return {
96 | ...state,
97 | loading: false,
98 | error: action.error
99 | };
100 |
101 | case person.UPDATE_PERSON:
102 | return {
103 | ...state,
104 | loading: true,
105 | error: null
106 | };
107 |
108 | case person.UPDATE_PERSON_SUCCESS:
109 | return {
110 | ...personAdapter.updateOne(action.payload.id, state),
111 | loading: false,
112 | error: null
113 | };
114 |
115 | case person.UPDATE_PERSON_FAIL:
116 | return {
117 | ...state,
118 | loading: false,
119 | error: action.error
120 | };
121 |
122 | case person.UPDATE_PEOPLE:
123 | return {
124 | ...state,
125 | loading: true,
126 | error: null
127 | };
128 |
129 | case person.UPDATE_PEOPLE_SUCCESS:
130 | return {
131 | ...personAdapter.updateMany(action.payload.ids, state),
132 | loading: false,
133 | error: null
134 | };
135 |
136 | case person.UPDATE_PEOPLE_FAIL:
137 | return {
138 | ...state,
139 | loading: false,
140 | error: action.error
141 | };
142 |
143 | case person.DELETE_PERSON:
144 | return {
145 | ...state,
146 | loading: true,
147 | error: null
148 | };
149 |
150 | case person.DELETE_PERSON_SUCCESS:
151 | return {
152 | ...personAdapter.removeOne(action.payload.id, state),
153 | loading: false,
154 | error: null
155 | };
156 |
157 | case person.DELETE_PERSON_FAIL:
158 | return {
159 | ...state,
160 | loading: false,
161 | error: action.error
162 | };
163 |
164 | case person.DELETE_PEOPLE:
165 | return {
166 | ...state,
167 | loading: true,
168 | error: null
169 | };
170 |
171 | case person.DELETE_PEOPLE_SUCCESS:
172 | return {
173 | ...personAdapter.removeOne(action.payload.ids, state),
174 | loading: false,
175 | error: null
176 | };
177 |
178 | case person.DELETE_PEOPLE_FAIL:
179 | return {
180 | ...state,
181 | loading: false,
182 | error: action.error
183 | };
184 |
185 | case person.CLEAR_PEOPLE:
186 | return {
187 | ...personAdapter.removeAll({ ...state, loading: true, error: null, selectedPersonID: null }),
188 | };
189 |
190 | default: {
191 | return state;
192 | }
193 | }
194 | }
195 |
196 | export const {
197 | selectIds: selectPersonIDs,
198 | selectEntities: selectPersonEntities,
199 | selectAll: selectAllPeople,
200 | selectTotal: selectPersonTotal
201 | } = personAdapter.getSelectors();
202 |
--------------------------------------------------------------------------------
/examples/person/person.selectors.ts:
--------------------------------------------------------------------------------
1 | import { createFeatureSelector, createSelector } from '@ngrx/store';
2 | import * as fromPerson from './person.reducer';
3 |
4 | export const selectPersonState = createFeatureSelector('person');
5 |
6 | export const getSelectedPersonID = (state: fromPerson.PersonState) => state.selectedPersonID;
7 |
8 | export const selectPersonIds = createSelector(selectPersonState, fromPerson.selectPersonIds);
9 | export const selectPersonEntities = createSelector(selectPersonState, fromPerson.selectPersonEntities);
10 | export const selectAllPeople = createSelector(selectPersonState, fromPerson.selectAllPeople);
11 | export const selectPersonTotal = createSelector(selectPersonState, fromPerson.selectPersonTotal);
12 | export const selectCurrentPersonID = createSelector(selectPersonState, getSelectedPersonID);
13 |
14 | export const selectCurrentPerson = createSelector(
15 | selectPersonEntities,
16 | selectCurrentPersonID,
17 | (personEntities, personID) => personEntities[personID]
18 | );
--------------------------------------------------------------------------------
/examples/person/person.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, getTestBed } from '@angular/core/testing';
2 | import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
3 | import { PersonService } from './person.service';
4 |
5 | describe('Service: PersonService', () => {
6 |
7 | let injector: TestBed;
8 | let service: PersonService;
9 | let httpMock: HttpTestingController;
10 |
11 | beforeEach(() => {
12 | TestBed.configureTestingModule({
13 | imports: [HttpClientTestingModule],
14 | providers: [PersonService]
15 | });
16 | injector = getTestBed();
17 | service = injector.get(PersonService);
18 | httpMock = injector.get(HttpTestingController);
19 | });
20 |
21 | afterEach(() => {
22 | httpMock.verify();
23 | });
24 |
25 | // --------------------Example------------------------
26 |
27 | // const data = {
28 | // 'title': 'Book Title',
29 | // 'author': 'John Smith',
30 | // 'volumeId': '12345'
31 | // };
32 |
33 | // const books = {
34 | // items: [
35 | // {id: '12345', volumeInfo: {title: 'Title'}},
36 | // {id: '67890', volumeInfo: {title: 'Another Title'}}
37 | // ]
38 | // };
39 |
40 | // const queryTitle = 'Book Title';
41 |
42 | // it('should call the search api and return the search results', (done) => {
43 | // backend.connections.subscribe((connection: MockConnection) => {
44 | // const options = new ResponseOptions({
45 | // body: JSON.stringify(books)
46 | // });
47 | // connection.mockRespond(new Response(options));
48 | // expect(connection.request.method).toEqual(RequestMethod.Get);
49 | // expect(connection.request.url).toEqual(`https://www.googleapis.com/books/v1/volumes?q=${queryTitle}`);
50 | // });
51 |
52 | // service
53 | // .searchBooks(queryTitle)
54 | // .subscribe((res) => {
55 | // expect(res).toEqual(books.items);
56 | // done();
57 | // });
58 | // });
59 |
60 | });
61 |
--------------------------------------------------------------------------------
/examples/person/person.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 |
5 | @Injectable()
6 | export class PersonService {
7 |
8 | constructor(private http: HttpClient) { }
9 |
10 | loadPeople(): Observable {
11 | return this.http.get('https://api.com');
12 | }
13 |
14 | addPerson(param: any): Observable {
15 | return this.http.post('https://api.com', { body: param });
16 | }
17 |
18 | addPeople(param: any): Observable {
19 | return this.http.post('https://api.com', { body: param });
20 | }
21 |
22 | updatePerson(param: any): Observable {
23 | return this.http.patch('https://api.com', { body: param });
24 | }
25 |
26 | updatePeople(param: any): Observable {
27 | return this.http.patch('https://api.com', { body: param });
28 | }
29 |
30 | deletePerson(param: any): Observable {
31 | return this.http.delete('https://api.com');
32 | }
33 |
34 | deletePeople(param: any): Observable {
35 | return this.http.delete('https://api.com');
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/examples/router-serializer.ts:
--------------------------------------------------------------------------------
1 | import { createSelector } from '@ngrx/store';
2 | import { RouterStateSnapshot, ParamMap } from '@angular/router';
3 | import { RouterStateSerializer, RouterReducerState } from '@ngrx/router-store';
4 |
5 | export interface RouterStateUrl {
6 | url: string;
7 | paramMap: ParamMap;
8 | queryParamMap: ParamMap;
9 | }
10 |
11 | export class CustomSerializer implements RouterStateSerializer {
12 | serialize(routerState: RouterStateSnapshot): RouterStateUrl {
13 | let route = routerState.root;
14 |
15 | while (route.firstChild) {
16 | route = route.firstChild;
17 | }
18 |
19 | const { url, root: { queryParamMap } } = routerState;
20 | const { paramMap } = route;
21 |
22 | // Only return an object including the URL, params and query params
23 | // instead of the entire snapshot
24 | return { url, paramMap, queryParamMap };
25 | }
26 | }
27 |
28 | export const getRouterState = (state) => state.router;
29 | export const getCurrentUrl = createSelector(getRouterState,
30 | (router: RouterReducerState) => router.state && router.state.url);
31 | export const getAllParams = createSelector(getRouterState,
32 | (router: RouterReducerState) => router.state && router.state.paramMap);
33 | export const getAllQueryParams = createSelector(getRouterState,
34 | (router: RouterReducerState) => router.state && router.state.queryParamMap);
35 |
--------------------------------------------------------------------------------
/examples/store-reduxor.module.ts:
--------------------------------------------------------------------------------
1 | import { StoreRouterConnectingModule, RouterStateSerializer } from '@ngrx/router-store';
2 | import { CustomSerializer } from './router-serializer';
3 | import { ModuleWithProviders, NgModule } from '@angular/core';
4 | import { StoreModule } from '@ngrx/store';
5 | import { EffectsModule } from '@ngrx/effects';
6 |
7 | // Uncomment this line if you want to use the StoreDevtool
8 | // import { StoreDevtoolsModule } from '@ngrx/store-devtools';
9 |
10 | import { reducers, metaReducers } from './app.store';
11 | import { AllEffects } from './all-effects';
12 | import { HttpClientModule } from '@angular/common/http';
13 |
14 | // -- IMPORT SERVICES --
15 | import { PersonService } from './person/person.service';
16 | import { CrudService } from './crud/crud.service';
17 | import { BasicService } from './basic/basic.service';
18 |
19 | @NgModule({
20 | imports: [
21 | HttpClientModule,
22 | StoreModule.forRoot(reducers, { metaReducers }),
23 | EffectsModule.forRoot([...AllEffects]),
24 | StoreRouterConnectingModule.forRoot({
25 | stateKey: 'router'
26 | }),
27 | // StoreDevtoolsModule.instrument({
28 | // maxAge: 25, // Retains last 25 states
29 | // })
30 | ],
31 | exports: [],
32 | providers: [
33 | { provide: RouterStateSerializer, useClass: CustomSerializer },
34 | // -- PROVIDERS --
35 | PersonService,
36 | CrudService,
37 | BasicService
38 | ]
39 | })
40 | export class StoreReduxorModule {
41 | static forRoot(): ModuleWithProviders {
42 | return {
43 | ngModule: StoreReduxorModule
44 | };
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | process.argv.push('--plopfile', __dirname + '/plopfile.js');
3 | require('plop');
4 |
--------------------------------------------------------------------------------
/ngx-reduxor.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "BASE_PATH": "./examples",
3 | "SEPARATE_DIRECTORY": false,
4 | "IGNORE_SPEC": false
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-reduxor",
3 | "version": "3.4.1",
4 | "description": "Angular+Redux: templates generator to scaffold easily and with consistency, all in a simple command",
5 | "main": "index.js",
6 | "keywords": [
7 | "ngrx",
8 | "ngrx templates",
9 | "ngrx generator",
10 | "ngrx/store",
11 | "ngrx/effects"
12 | ],
13 | "author": "Kevin Mathy",
14 | "bin": {
15 | "ngx-reduxor": "index.js"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "https://github.com/kmathy/ngx-reduxor"
20 | },
21 | "scripts": {
22 | "release": "release-it"
23 | },
24 | "peerDependencies": {
25 | "@ngrx/effects": "^5.0.1",
26 | "@ngrx/entity": "^5.0.1",
27 | "@ngrx/store": "^5.0.0",
28 | "@ngrx/store-devtools": "^5.0.1",
29 | "ngrx-store-freeze": "^0.2.1",
30 | "rxjs": "^5.5.0"
31 | },
32 | "dependencies": {
33 | "fs-extra": "^5.0.0",
34 | "lodash.camelcase": "^4.3.0",
35 | "lodash.snakecase": "^4.1.1",
36 | "plop": "^1.9.1"
37 | },
38 | "devDependencies": {
39 | "release-it": "^6.2.0"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/plopfile.js:
--------------------------------------------------------------------------------
1 | // Generators
2 | const createGenerator = require('./src/generators/create');
3 | const addRouterSerializer = require('./src/generators/add-router-serializer');
4 | const exit = require('./src/generators/exit');
5 | // Action types
6 | const updateAppStore = require('./src/action_types/update-app-store');
7 | const updateAllEffects = require('./src/action_types/update-all-effects');
8 | const updateStoreReduxor = require('./src/action_types/update-store-reduxor');
9 | // Initializers
10 | const initConfig = require('./src/initConfig');
11 | const initHelpers = require('./src/helpers');
12 | const path = require('path');
13 | const fs = require('fs-extra');
14 |
15 | module.exports = function (plop) {
16 | const options = initConfig();
17 | initHelpers(plop, options);
18 |
19 | plop.setActionType('update app.store', updateAppStore);
20 | plop.setActionType('update all-effects', updateAllEffects);
21 | plop.setActionType('update store-reduxor', updateStoreReduxor);
22 |
23 | const hasRouterSerializer = fs.existsSync(path.resolve(options.BASE_PATH, 'router-serializer.ts'));
24 |
25 | plop.setGenerator('New entity', createGenerator(options));
26 | if(!hasRouterSerializer) plop.setGenerator('Add router-store', addRouterSerializer(options));
27 | plop.setGenerator('Exit', exit())
28 | };
--------------------------------------------------------------------------------
/src/action_types/update-all-effects.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs-extra');
3 |
4 | module.exports = function(data, config, plop) {
5 | const makeDestPath = p => path.resolve(plop.getDestBasePath(), p);
6 | const fileDestPath = makeDestPath(plop.renderString(config.path));
7 | try {
8 | let fileData = fs.readFileSync(fileDestPath, 'utf-8');
9 | const importFile = "$1\r\nimport { {{ properCase name }}Effects } from './{{ folder name 'effects' }}/{{ kebabCase name }}.effects';";
10 | const listEffect = "$1\r\n\t{{ properCase name }}Effects,";
11 | fileData = fileData
12 | .replace(/(\/\/ -- IMPORT --)/, plop.renderString(importFile, data))
13 | .replace(/(\/\/ -- LIST --)/, plop.renderString(listEffect, data))
14 | fs.writeFileSync(fileDestPath, fileData);
15 | return fileDestPath.replace(path.resolve(plop.getDestBasePath()), '');
16 | } catch(err) {
17 | throw typeof err === 'string' ? err : err.message || JSON.stringify(err);
18 | }
19 | }
--------------------------------------------------------------------------------
/src/action_types/update-app-store.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs-extra');
3 |
4 | module.exports = function(data, config, plop) {
5 | const makeDestPath = p => path.resolve(plop.getDestBasePath(), p);
6 | const fileDestPath = makeDestPath(plop.renderString(config.path));
7 | try {
8 | let fileData = fs.readFileSync(fileDestPath, 'utf-8');
9 | if (data.name) {
10 | const importFile = "$1\r\nimport * as {{ camelCase name }} from './{{ folder name 'reducers' }}/{{ kebabCase name }}.reducer';";
11 | const importState = "$1\r\n\t{{ camelCase name }}: {{ camelCase name }}.{{ titleCase name }}State;";
12 | const addReducer = "$1\r\n\t{{ camelCase name }}: {{ camelCase name }}.reducer,";
13 | fileData = fileData
14 | .replace(/(\/\/ -- IMPORT REDUCER --)/, plop.renderString(importFile, data))
15 | .replace(/(\/\/ -- IMPORT STATE --)/, plop.renderString(importState, data))
16 | .replace(/(\/\/ -- ADD REDUCER --)/, plop.renderString(addReducer, data));
17 | } else {
18 | const importRouterStore = "import { routerReducer, RouterReducerState } from '@ngrx/router-store';\r\n"
19 | const importFile = "$1\r\nimport * as router from './router-serializer';";
20 | const importState = "$1\r\n\trouter: RouterReducerState;";
21 | const addReducer = "$1\r\n\trouter: routerReducer,";
22 | fileData = importRouterStore.concat(fileData);
23 | fileData = fileData
24 | .replace(/(\/\/ -- IMPORT REDUCER --)/, plop.renderString(importFile, data))
25 | .replace(/(\/\/ -- IMPORT STATE --)/, plop.renderString(importState, data))
26 | .replace(/(\/\/ -- ADD REDUCER --)/, plop.renderString(addReducer, data));
27 | }
28 | fs.writeFileSync(fileDestPath, fileData);
29 | return fileDestPath.replace(path.resolve(plop.getDestBasePath()), '');
30 | } catch(err) {
31 | throw typeof err === 'string' ? err : err.message || JSON.stringify(err);
32 | }
33 | }
--------------------------------------------------------------------------------
/src/action_types/update-store-reduxor.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs-extra');
3 |
4 | module.exports = function(data, config, plop) {
5 | const makeDestPath = p => path.resolve(plop.getDestBasePath(), p);
6 | const fileDestPath = makeDestPath(plop.renderString(config.path));
7 | try {
8 | let fileData = fs.readFileSync(fileDestPath, 'utf-8');
9 | if(data.name) {
10 | const importFile = "$1\r\nimport { {{ properCase name }}Service } from './{{ folder name 'services' }}/{{ kebabCase name }}.service';";
11 | const provider = "$1\r\n\t\t{{ properCase name }}Service,";
12 | fileData = fileData
13 | .replace(/(\/\/ -- IMPORT SERVICES --)/, plop.renderString(importFile, data))
14 | .replace(/(\/\/ -- PROVIDERS --)/, plop.renderString(provider, data))
15 | } else {
16 | const importCustomSerializer = "import { CustomSerializer } from './router-serializer';\r\n";
17 | const importRouterStore = "import { StoreRouterConnectingModule, RouterStateSerializer } from '@ngrx/router-store';\r\n";
18 | const StoreRouterConnectingModule =`$1\r
19 | StoreRouterConnectingModule.forRoot({
20 | stateKey: 'router'
21 | }),`
22 | const provider = "$1\r\n\t\t{ provide: RouterStateSerializer, useClass: CustomSerializer },"
23 | fileData = importRouterStore.concat(importCustomSerializer, fileData);
24 | fileData = fileData
25 | .replace(/(EffectsModule\.forRoot\(\[\.\.\.AllEffects\]\)\,)/, plop.renderString(StoreRouterConnectingModule, data))
26 | .replace(/(providers\: \[)/, plop.renderString(provider, data))
27 | }
28 | fs.writeFileSync(fileDestPath, fileData);
29 | return fileDestPath.replace(path.resolve(plop.getDestBasePath()), '');
30 | } catch(err) {
31 | throw typeof err === 'string' ? err : err.message || JSON.stringify(err);
32 | }
33 | }
--------------------------------------------------------------------------------
/src/actions/basic-actions.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | /*
3 | * Actions generator
4 | */
5 | action: [{
6 | type: 'add',
7 | path: '{{ basePath }}/{{ folder name "actions" }}/{{kebabCase name}}.actions.ts',
8 | templateFile: './templates/Basic/_actions.ts'
9 | }],
10 |
11 | /*
12 | * Reducer generator
13 | */
14 | reducer: [{
15 | type: 'add',
16 | path: '{{ basePath }}/{{ folder name "reducers" }}/{{kebabCase name}}.reducer.ts',
17 | templateFile: './templates/Basic/_reducer.ts'
18 | },
19 | {
20 | type: 'add',
21 | path: '{{ basePath }}/{{ folder name "reducers"}}/{{kebabCase name}}.reducer.spec.ts',
22 | templateFile: './templates/Basic/_reducer.spec.ts'
23 | }],
24 |
25 | reducerWithoutSpec: [{
26 | type: 'add',
27 | path: '{{ basePath }}/{{ folder name "reducers" }}/{{kebabCase name}}.reducer.ts',
28 | templateFile: './templates/Basic/_reducer.ts'
29 | }],
30 |
31 | /*
32 | * Effect generator
33 | */
34 | effect: [{
35 | type: 'add',
36 | path: '{{ basePath }}/{{ folder name "effects" }}/{{kebabCase name}}.effects.ts',
37 | templateFile: './templates/Basic/_effect.ts'
38 | }, {
39 | type: 'add',
40 | path: '{{ basePath }}/{{ folder name "effects" }}/{{kebabCase name}}.effects.spec.ts',
41 | templateFile: './templates/Basic/_effect.spec.ts'
42 | }],
43 |
44 | effectWithoutSpec: [{
45 | type: 'add',
46 | path: '{{ basePath }}/{{ folder name "effects" }}/{{kebabCase name}}.effects.ts',
47 | templateFile: './templates/Basic/_effect.ts'
48 | }],
49 |
50 | /*
51 | * Service generator
52 | */
53 | service: [{
54 | type: 'add',
55 | path: '{{ basePath }}/{{ folder name "services" }}/{{kebabCase name}}.service.ts',
56 | templateFile: './templates/Basic/_service.ts'
57 | }, {
58 | type: 'add',
59 | path: '{{ basePath }}/{{ folder name "services" }}/{{kebabCase name}}.service.spec.ts',
60 | templateFile: './templates/Basic/_service.spec.ts'
61 | }],
62 |
63 | serviceWithoutSpec: [{
64 | type: 'add',
65 | path: '{{ basePath }}/{{ folder name "services" }}/{{kebabCase name}}.service.ts',
66 | templateFile: './templates/Basic/_service.ts'
67 | }]
68 | }
--------------------------------------------------------------------------------
/src/actions/crud-actions.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | /*
3 | * Actions generator
4 | */
5 | action: [{
6 | type: 'add',
7 | path: '{{ basePath }}/{{ folder name "actions" }}/{{kebabCase name}}.actions.ts',
8 | templateFile: './templates/CRUD/_actions.ts'
9 | }],
10 |
11 | /*
12 | * Reducer generator
13 | */
14 | reducer: [{
15 | type: 'add',
16 | path: '{{ basePath }}/{{ folder name "reducers" }}/{{kebabCase name}}.reducer.ts',
17 | templateFile: './templates/CRUD/_reducer.ts'
18 | },
19 | {
20 | type: 'add',
21 | path: '{{ basePath }}/{{ folder name "reducers"}}/{{kebabCase name}}.reducer.spec.ts',
22 | templateFile: './templates/CRUD/_reducer.spec.ts'
23 | }
24 | ],
25 |
26 | reducerWithoutSpec: [{
27 | type: 'add',
28 | path: '{{ basePath }}/{{ folder name "reducers" }}/{{kebabCase name}}.reducer.ts',
29 | templateFile: './templates/CRUD/_reducer.ts'
30 | }],
31 |
32 | /*
33 | * Effect generator
34 | */
35 | effect: [{
36 | type: 'add',
37 | path: '{{ basePath }}/{{ folder name "effects" }}/{{kebabCase name}}.effects.ts',
38 | templateFile: './templates/CRUD/_effect.ts'
39 | }, {
40 | type: 'add',
41 | path: '{{ basePath }}/{{ folder name "effects" }}/{{kebabCase name}}.effects.spec.ts',
42 | templateFile: './templates/CRUD/_effect.spec.ts'
43 | }],
44 |
45 | effectWithoutSpec: [{
46 | type: 'add',
47 | path: '{{ basePath }}/{{ folder name "effects" }}/{{kebabCase name}}.effects.ts',
48 | templateFile: './templates/CRUD/_effect.ts'
49 | }],
50 |
51 | /*
52 | * Service generator
53 | */
54 | service: [{
55 | type: 'add',
56 | path: '{{ basePath }}/{{ folder name "services" }}/{{kebabCase name}}.service.ts',
57 | templateFile: './templates/CRUD/_service.ts'
58 | }, {
59 | type: 'add',
60 | path: '{{ basePath }}/{{ folder name "services" }}/{{kebabCase name}}.service.spec.ts',
61 | templateFile: './templates/CRUD/_service.spec.ts'
62 | }],
63 |
64 | serviceWithoutSpec: [{
65 | type: 'add',
66 | path: '{{ basePath }}/{{ folder name "services" }}/{{kebabCase name}}.service.ts',
67 | templateFile: './templates/CRUD/_service.ts'
68 | }]
69 | }
70 |
--------------------------------------------------------------------------------
/src/actions/crud-entity-actions.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | /*
3 | * Actions generator
4 | */
5 | action: [{
6 | type: 'add',
7 | path: '{{ basePath }}/{{ folder name "actions" }}/{{kebabCase name}}.actions.ts',
8 | templateFile: './templates/CRUD-entity/_actions.ts'
9 | }],
10 |
11 | /*
12 | * Selector generator
13 | */
14 |
15 | selector: [{
16 | type: 'add',
17 | path: '{{ basePath }}/{{ folder name "selectors" }}/{{kebabCase name}}.selectors.ts',
18 | templateFile: './templates/CRUD-entity/_selectors.ts'
19 | }],
20 |
21 | /*
22 | * Reducer generator
23 | */
24 | reducer: [{
25 | type: 'add',
26 | path: '{{ basePath }}/{{ folder name "reducers" }}/{{kebabCase name}}.reducer.ts',
27 | templateFile: './templates/CRUD-entity/_reducer.ts'
28 | },
29 | {
30 | type: 'add',
31 | path: '{{ basePath }}/{{ folder name "reducers"}}/{{kebabCase name}}.reducer.spec.ts',
32 | templateFile: './templates/CRUD-entity/_reducer.spec.ts'
33 | }],
34 |
35 | reducerWithoutSpec: [{
36 | type: 'add',
37 | path: '{{ basePath }}/{{ folder name "reducers" }}/{{kebabCase name}}.reducer.ts',
38 | templateFile: './templates/CRUD-entity/_reducer.ts'
39 | }],
40 |
41 | /*
42 | * Effect generator
43 | */
44 | effect: [{
45 | type: 'add',
46 | path: '{{ basePath }}/{{ folder name "effects" }}/{{kebabCase name}}.effects.ts',
47 | templateFile: './templates/CRUD-entity/_effect.ts'
48 | }, {
49 | type: 'add',
50 | path: '{{ basePath }}/{{ folder name "effects" }}/{{kebabCase name}}.effects.spec.ts',
51 | templateFile: './templates/CRUD-entity/_effect.spec.ts'
52 | }],
53 |
54 | effectWithoutSpec: [{
55 | type: 'add',
56 | path: '{{ basePath }}/{{ folder name "effects" }}/{{kebabCase name}}.effects.ts',
57 | templateFile: './templates/CRUD-entity/_effect.ts'
58 | }],
59 |
60 | /*
61 | * Service generator
62 | */
63 | service: [{
64 | type: 'add',
65 | path: '{{ basePath }}/{{ folder name "services" }}/{{kebabCase name}}.service.ts',
66 | templateFile: './templates/CRUD-entity/_service.ts'
67 | }, {
68 | type: 'add',
69 | path: '{{ basePath }}/{{ folder name "services" }}/{{kebabCase name}}.service.spec.ts',
70 | templateFile: './templates/CRUD-entity/_service.spec.ts'
71 | }],
72 |
73 | serviceWithoutSpec: [{
74 | type: 'add',
75 | path: '{{ basePath }}/{{ folder name "services" }}/{{kebabCase name}}.service.ts',
76 | templateFile: './templates/CRUD-entity/_service.ts'
77 | }]
78 | }
79 |
--------------------------------------------------------------------------------
/src/actions/modules-actions.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | /*
3 | * Create file index.ts if doesn't exist
4 | */
5 | addIndex: [{
6 | type: "add",
7 | path: "{{ basePath }}/app.store.ts",
8 | templateFile: './templates/_app.store.ts'
9 | }],
10 |
11 | /*
12 | * Modify index.ts with new generated files
13 | */
14 | updateIndex: [{
15 | type: "update app.store",
16 | path: "{{ basePath }}/app.store.ts"
17 | }],
18 |
19 | /*
20 | * Create file all-effects.ts if doesn't exist
21 | */
22 | addAllEffects: [{
23 | type: 'add',
24 | path: '{{ basePath }}/all-effects.ts',
25 | templateFile: './templates/_all-effects.ts'
26 | }],
27 |
28 | /*
29 | * Modify all-effects.ts with new effect generated
30 | */
31 | updateAllEffects: [{
32 | type: 'update all-effects',
33 | path: '{{ basePath }}/all-effects.ts'
34 | }],
35 |
36 | /*
37 | * Create store-reduxor.module.ts if doesn't exist
38 | */
39 | addStoreReduxorModule: [{
40 | type: 'add',
41 | path: '{{ basePath }}/store-reduxor.module.ts',
42 | templateFile: './templates/_store-reduxor.module.ts'
43 | }],
44 |
45 | /*
46 | * Modify store-reduxor.module.ts with new generated files
47 | */
48 | updateStoreReduxorModule: [{
49 | type: 'update store-reduxor',
50 | path: '{{ basePath }}/store-reduxor.module.ts'
51 | }]
52 | }
--------------------------------------------------------------------------------
/src/actions/router-serializer.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | serializer: [{
3 | type: 'add',
4 | path: '{{ basePath }}/router-serializer.ts',
5 | templateFile: './templates/_router-serializer.ts'
6 | }]
7 | }
--------------------------------------------------------------------------------
/src/default.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "BASE_PATH": "./src/app/store",
3 | "SEPARATE_DIRECTORY": false,
4 | "IGNORE_SPEC": false
5 | }
--------------------------------------------------------------------------------
/src/generators/add-router-serializer.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs-extra');
2 |
3 | // actions
4 | const routerSerializer = require('../actions/router-serializer');
5 |
6 | // modules
7 | const modulesActions = require('../actions/modules-actions');
8 | const path = require('path');
9 | const pjson = require(path.resolve('./package.json'));
10 |
11 | const hasNgrxRouterStoreInstalled = () => {
12 | return Object.keys(pjson.dependencies).indexOf('@ngrx/router-store') >= 0;
13 | }
14 |
15 | module.exports = function(options) {
16 | return {
17 | description: 'Generate a router serializer',
18 | prompts: [],
19 | actions: () => {
20 | if(!hasNgrxRouterStoreInstalled()) {
21 | console.log(`@ngrx/router-store is not present in your package.json. Please install it and save it as a dependency of your project`.red);
22 | process.exit();
23 | }
24 | let actions = [routerSerializer.serializer, modulesActions.updateIndex, modulesActions.updateStoreReduxorModule];
25 | return actions.reduce((a, b) => a.concat(b));
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/generators/create.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs-extra');
2 | const snakeCase = require('lodash.snakecase');
3 | // actions
4 | const basicActions = require('../actions/basic-actions');
5 | const crudActions = require('../actions/crud-actions');
6 | const crudEntityActions = require('../actions/crud-entity-actions');
7 |
8 | // modules
9 | const modulesActions = require('../actions/modules-actions');
10 |
11 | // utils
12 | const validators = require('../validators');
13 |
14 | const crudMethods = [
15 | {name: 'Get', value: 'GET', checked: true},
16 | {name: 'Create', value: 'CREATE', checked: true},
17 | {name: 'Update', value: 'UPDATE', checked: true},
18 | {name: 'Delete', value: 'DELETE', checked: true}
19 | ];
20 | const entityMethods = [
21 | {name: 'Get', checked: true, value: 'GET'},
22 | {name: 'Create', checked: true, value: 'CREATE'},
23 | {name: 'Create many', checked: true, value: 'CREATE_MANY'},
24 | {name: 'Update', checked: true, value: 'UPDATE'},
25 | {name: 'Update many', checked: true, value: 'UPDATE_MANY'},
26 | {name: 'Delete', checked: true, value: 'DELETE'},
27 | {name: 'Delete many', checked: true, value: 'DELETE_MANY'},
28 | {name: 'Clear', checked: true, value: 'CLEAR'}
29 | ];
30 |
31 | module.exports = function(options) {
32 | return {
33 | description: 'Generate Actions, Reducers, Services and Effect',
34 | prompts: [
35 | {
36 | type: 'input',
37 | name: 'name',
38 | message: 'Name for the new store object?',
39 | validate: name => validators.required(name),
40 | description: 'object must be singular'
41 | },
42 | {
43 | type: 'list',
44 | name: 'store',
45 | message: 'What kind of store do you want to generate?',
46 | choices: ['Basic', 'CRUD', 'Entity'],
47 | filter: val => val.toUpperCase()
48 | },
49 | {
50 | type: 'input',
51 | name: 'plural',
52 | message: answers => `Enter the plural of '${answers.name}':`,
53 | when: answers => answers.store === 'ENTITY',
54 | validate: plural => validators.required(plural)
55 | }, {
56 | type: 'checkbox',
57 | name: 'crudMethods',
58 | message: 'Select which method you want to be implemented:',
59 | when: answers => answers.store === 'CRUD',
60 | choices: crudMethods,
61 | validate: array => validators.minOptionSelected(1, array)
62 | }, {
63 | type: 'checkbox',
64 | name: 'entityMethods',
65 | message: 'Select which method you want to be implemented:',
66 | when: answers => answers.store === 'ENTITY',
67 | choices: entityMethods,
68 | validate: array => validators.minOptionSelected(1, array)
69 | }
70 | ],
71 | actions: (data) => {
72 | let actions = [];
73 | switch (data.store) {
74 | case 'BASIC': actions = options.IGNORE_SPEC
75 | ? [basicActions.action, basicActions.reducerWithoutSpec, basicActions.effectWithoutSpec, basicActions.serviceWithoutSpec]
76 | : [basicActions.action, basicActions.reducer, basicActions.effect, basicActions.service]
77 | break;
78 | case 'CRUD': actions = options.IGNORE_SPEC
79 | ? [crudActions.action, crudActions.reducerWithoutSpec, crudActions.effectWithoutSpec, crudActions.serviceWithoutSpec]
80 | : [crudActions.action, crudActions.reducer, crudActions.effect, crudActions.service]
81 | break;
82 | case 'ENTITY': actions = options.IGNORE_SPEC
83 | ? [crudEntityActions.action, crudEntityActions.selector, crudEntityActions.reducerWithoutSpec, crudEntityActions.effectWithoutSpec, crudEntityActions.serviceWithoutSpec]
84 | : [crudEntityActions.action, crudEntityActions.selector, crudEntityActions.reducer, crudEntityActions.effect, crudEntityActions.service]
85 | break;
86 | }
87 |
88 | const indexExists = fs.existsSync(options.BASE_PATH, 'app.store.ts');
89 | const allEffectsExists = fs.existsSync(options.BASE_PATH, 'all-effects.ts');
90 | const storeReduxorModuleExists = fs.existsSync(options.BASE_PATH, 'store-reduxor.module.ts');
91 | actions = indexExists ? actions.concat(modulesActions.updateIndex) : actions.concat(modulesActions.addIndex);
92 | actions = allEffectsExists ? actions.concat(modulesActions.updateAllEffects) : actions.concat(modulesActions.addAllEffects);
93 | actions = storeReduxorModuleExists ? actions.concat(modulesActions.updateStoreReduxorModule) : actions.concat(modulesActions.addStoreReduxorModule);
94 |
95 | const actionsFlattened = actions.reduce((a, b) => a.concat(b));
96 | return actionsFlattened;
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/src/generators/exit.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | return {
3 | description: 'Close program',
4 | prompts: [],
5 | actions: () => {
6 | process.abort();
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/src/helpers.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const camelCase = require('lodash.camelcase');
3 |
4 | module.exports = function (plop, options) {
5 | plop.addHelper('basePath', () => path.resolve(options.BASE_PATH));
6 |
7 | plop.addHelper('folder', (name, type) => options.SEPARATE_DIRECTORY ? type : camelCase(name));
8 |
9 | plop.addHelper('position', (name) => options.SEPARATE_DIRECTORY ? '../' + name : '.');
10 |
11 | plop.addHelper('ifIn', function (elem, list, options) {
12 | return list.indexOf(elem) > -1 ? options.fn(this) : options.inverse(this);
13 | })
14 |
15 | plop.addHelper('isEqual', function (elem1, elem2, options) {
16 | return elem1 === elem2 ? options.fn(this) : options.inverse(this);
17 | })
18 |
19 | plop.addHelper('isNotEqual', function(elem1, elem2, options) {
20 | return elem1 !== elem2 ? options.fn(this) : options.inverse(this);
21 | })
22 | }
--------------------------------------------------------------------------------
/src/initConfig.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs-extra');
2 | const path = require('path');
3 | const defaultOptions = fs.readJsonSync(path.resolve(__dirname, './default.config.json'), 'utf-8')
4 |
5 | module.exports = function () {
6 | if(!fs.existsSync('./ngx-reduxor.config.json')) {
7 | console.log(`The config file doesn't exist`.yellow)
8 | try {
9 | fs.outputJsonSync('./ngx-reduxor.config.json', defaultOptions, {spaces: '\t'});
10 | console.log(`The config file has been created.`.green)
11 | return fs.readJsonSync('./ngx-reduxor.config.json', 'utf-8')
12 | } catch(err) {
13 | console.log(`${err}`.red);
14 | process.exit(1);
15 | }
16 | } else {
17 | return fs.readJsonSync('./ngx-reduxor.config.json', 'utf-8');
18 | }
19 | }
--------------------------------------------------------------------------------
/src/validators.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | required(name) {
3 | return (/.+/).test(name) ? true : `Required`;
4 | },
5 |
6 | minOptionSelected(number, array) {
7 | return array.length >= number ? true : `At least ${number} ${number === 1 ? 'option' : 'options'} must be selected`;
8 | }
9 | }
--------------------------------------------------------------------------------
/templates/Basic/_actions.ts:
--------------------------------------------------------------------------------
1 | import { Action } from '@ngrx/store';
2 |
3 | export const LOAD_{{ constantCase name }} = '[{{ properCase name }}] Load {{ properCase name }}';
4 | export const LOAD_{{ constantCase name }}_SUCCESS = '[{{ properCase name }}] Load {{ properCase name }} Success';
5 | export const LOAD_{{ constantCase name }}_FAIL = '[{{ properCase name }}] Load {{ properCase name }} Fail';
6 |
7 | /**
8 | * Load {{ properCase name }} Actions
9 | */
10 | export class Load{{ properCase name }}Action implements Action {
11 | readonly type = LOAD_{{ constantCase name }};
12 |
13 | constructor(public payload: any) { }
14 | }
15 |
16 | export class Load{{ properCase name }}SuccessAction implements Action {
17 | readonly type = LOAD_{{ constantCase name }}_SUCCESS;
18 |
19 | constructor(public payload: any) { }
20 | }
21 |
22 | export class Load{{ properCase name }}FailAction implements Action {
23 | readonly type = LOAD_{{ constantCase name }}_FAIL;
24 |
25 | constructor(public error: Error) { }
26 | }
27 |
28 | export type Actions =
29 | | Load{{ properCase name }}Action
30 | | Load{{ properCase name }}SuccessAction
31 | | Load{{ properCase name }}FailAction;
--------------------------------------------------------------------------------
/templates/Basic/_effect.spec.ts:
--------------------------------------------------------------------------------
1 | import { fakeAsync, TestBed, tick } from '@angular/core/testing';
2 | import { {{properCase name}}Effects } from '{{position "effects"}}/{{kebabCase name}}.effects';
3 | import { {{properCase name}}Service } from '{{position "services"}}/{{kebabCase name}}.service';
4 |
5 | describe('{{properCase name}}Effects', () => {
6 | let runner, {{camelCase name}}Effects, {{camelCase name}}Service;
7 |
8 | beforeEach(() => TestBed.configureTestingModule({
9 | imports: [],
10 | providers: [
11 | {{properCase name}}Effects,
12 | {
13 | provide: {{properCase name}}Service,
14 | useValue: jasmine.createSpyObj('{{camelCase name}}Service', ['get'])
15 | }
16 | ]
17 | }));
18 |
19 | beforeEach(() => {
20 | {{camelCase name}}Effects = TestBed.get({{properCase name}}Effects);
21 | {{camelCase name}}Service = TestBed.get({{properCase name}}Service);
22 | });
23 |
24 | describe('{{camelCase name}}$', () => {
25 |
26 | it('should return a LOAD_{{ constantCase name }}_SUCCESS action, on success', function () {
27 |
28 | });
29 |
30 | it('should return a LOAD_{{ constantCase name }}_FAIL action, on error', function () {
31 |
32 | });
33 |
34 | });
35 |
36 | });
37 |
--------------------------------------------------------------------------------
/templates/Basic/_effect.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Actions, Effect, ofType } from '@ngrx/effects';
3 | import { of as observableOf } from 'rxjs';
4 | import { switchMap, map, catchError } from 'rxjs/operators';
5 | import { {{properCase name }}Service } from '{{position "services"}}/{{ kebabCase name }}.service';
6 | import * as {{ camelCase name }}Actions from '{{position "actions"}}/{{ kebabCase name }}.actions';
7 |
8 | @Injectable()
9 | export class {{ properCase name }}Effects {
10 | @Effect() load{{ properCase name }}$;
11 |
12 | constructor(
13 | private {{ camelCase name }}Service: {{ properCase name }}Service,
14 | private actions$: Actions
15 | ) {
16 | this.load{{ properCase name }}$ = this.actions$
17 | .pipe(
18 | ofType({{ camelCase name }}Actions.LOAD_{{ constantCase name }}),
19 | switchMap((state: {{ camelCase name }}Actions.Load{{ properCase name }}Action) =>
20 | this.{{ camelCase name }}Service.load{{ properCase name }}().pipe(
21 | // If successful, dispatch success action with result
22 | map(res => new {{ camelCase name }}Actions.Load{{ properCase name }}SuccessAction(res)),
23 | // If request fails, dispatch failed action
24 | catchError((err: Error) => observableOf(new {{ camelCase name }}Actions.Load{{ properCase name }}FailAction(err)))
25 | )
26 | )
27 | );
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/templates/Basic/_reducer.spec.ts:
--------------------------------------------------------------------------------
1 | import { reducer } from '{{position "reducers"}}/{{kebabCase name}}.reducer';
2 | import * as from{{ properCase name }} from '{{position "reducers"}}/{{kebabCase name}}.reducer';
3 |
4 | describe('{{properCase name}}Reducer', () => {
5 |
6 | describe('undefined action', () => {
7 | it('should return the default state', () => {
8 | const action = {} as any;
9 |
10 | const result = reducer(undefined, action);
11 | expect(result).toEqual(from{{ properCase name }}.initialState);
12 | });
13 | });
14 |
15 | });
--------------------------------------------------------------------------------
/templates/Basic/_reducer.ts:
--------------------------------------------------------------------------------
1 | import * as {{ camelCase name }} from '{{position "actions"}}/{{ kebabCase name }}.actions';
2 |
3 | export interface {{ properCase name }}State {
4 | loading: boolean;
5 | entities: { [id: string]: any };
6 | result: string[];
7 | error: Error;
8 | type: string;
9 | };
10 |
11 | export const initialState: {{ properCase name }}State = {
12 | loading: false,
13 | entities: {},
14 | result: [],
15 | error: null,
16 | type: ''
17 | };
18 |
19 | export function reducer(state = initialState, action: {{ camelCase name }}.Actions): {{ properCase name }}State {
20 | switch (action.type) {
21 | case {{ camelCase name }}.LOAD_{{ constantCase name }}: {
22 | return {
23 | ...state,
24 | loading: true,
25 | error: null,
26 | type: action.type
27 | }
28 | }
29 |
30 | case {{ camelCase name }}.LOAD_{{ constantCase name }}_SUCCESS: {
31 | return {
32 | ...state,
33 | result: action.payload,
34 | loading: false,
35 | error: null,
36 | type: action.type
37 | };
38 | }
39 |
40 | case {{ camelCase name }}.LOAD_{{ constantCase name }}_FAIL: {
41 | return {
42 | ...state,
43 | loading: false,
44 | error: action.error,
45 | type: action.type
46 | };
47 | }
48 |
49 | default: {
50 | return state;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/templates/Basic/_service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, getTestBed } from '@angular/core/testing';
2 | import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
3 | import { {{ properCase name }}Service } from '{{position "services"}}/{{ kebabCase name }}.service';
4 |
5 | describe('Service: {{ properCase name }}Service', () => {
6 |
7 | let injector: TestBed;
8 | let service: {{ properCase name }}Service;
9 | let httpMock: HttpTestingController;
10 |
11 | beforeEach(() => {
12 | TestBed.configureTestingModule({
13 | imports: [HttpClientTestingModule],
14 | providers: [{{ properCase name }}Service]
15 | });
16 | injector = getTestBed();
17 | service = injector.get({{ properCase name }}Service);
18 | httpMock = injector.get(HttpTestingController);
19 | });
20 |
21 | afterEach(() => {
22 | httpMock.verify();
23 | });
24 |
25 | // --------------------Example------------------------
26 |
27 | // const data = {
28 | // 'title': 'Book Title',
29 | // 'author': 'John Smith',
30 | // 'volumeId': '12345'
31 | // };
32 |
33 | // const books = {
34 | // items: [
35 | // {id: '12345', volumeInfo: {title: 'Title'}},
36 | // {id: '67890', volumeInfo: {title: 'Another Title'}}
37 | // ]
38 | // };
39 |
40 | // const queryTitle = 'Book Title';
41 |
42 | // it('should call the search api and return the search results', (done) => {
43 | // backend.connections.subscribe((connection: MockConnection) => {
44 | // const options = new ResponseOptions({
45 | // body: JSON.stringify(books)
46 | // });
47 | // connection.mockRespond(new Response(options));
48 | // expect(connection.request.method).toEqual(RequestMethod.Get);
49 | // expect(connection.request.url).toEqual(`https://www.googleapis.com/books/v1/volumes?q=${queryTitle}`);
50 | // });
51 |
52 | // service
53 | // .searchBooks(queryTitle)
54 | // .subscribe((res) => {
55 | // expect(res).toEqual(books.items);
56 | // done();
57 | // });
58 | // });
59 |
60 | });
61 |
--------------------------------------------------------------------------------
/templates/Basic/_service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 |
5 | @Injectable()
6 | export class {{ properCase name }}Service {
7 |
8 | constructor(private http: HttpClient) { }
9 |
10 | load{{ properCase name }}(): Observable {
11 | return this.http.get('https://api.com');
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/templates/CRUD-entity/_actions.ts:
--------------------------------------------------------------------------------
1 | import { Action } from '@ngrx/store';
2 | import { HttpErrorResponse } from '@angular/common/http';
3 |
4 | /**
5 | * Generate constants based on the given name
6 | * e.g export const LOAD_USERS = '[Auth] Load USERS'
7 | */
8 |
9 | {{#ifIn 'GET' entityMethods }}
10 | export const LOAD_{{ constantCase plural }} = '[{{ properCase plural }}] Load {{ properCase plural }}';
11 | export const LOAD_{{ constantCase plural }}_SUCCESS = '[{{ properCase plural }}] Load {{ properCase plural }} Success';
12 | export const LOAD_{{ constantCase plural }}_FAIL = '[{{ properCase plural }}] Load {{ properCase plural }} Fail';
13 |
14 | {{/ifIn}}
15 | export const SELECT_{{ constantCase name }} = '[{{ properCase name }}] Select {{ properCase name }}';
16 |
17 | {{#ifIn 'CREATE' entityMethods }}
18 | export const ADD_{{ constantCase name }} = '[{{ properCase name }}] Add {{ properCase name }}';
19 | export const ADD_{{ constantCase name }}_SUCCESS = '[{{ properCase name }}] Add {{ properCase name }} Success';
20 | export const ADD_{{ constantCase name }}_FAIL = '[{{ properCase name }}] Add {{ properCase name }} Fail';
21 |
22 | {{/ifIn}}
23 | {{#ifIn 'CREATE_MANY' entityMethods }}
24 | export const ADD_{{ constantCase plural }} = '[{{ properCase plural }}] Add {{ properCase plural }}';
25 | export const ADD_{{ constantCase plural }}_SUCCESS = '[{{ properCase plural }}] Add {{ properCase plural }} Success';
26 | export const ADD_{{ constantCase plural }}_FAIL = '[{{ properCase plural }}] Add {{ properCase plural }} Fail';
27 |
28 | {{/ifIn}}
29 | {{#ifIn 'UPDATE' entityMethods }}
30 | export const UPDATE_{{ constantCase name }} = '[{{ properCase name }}] Update {{ properCase name }}';
31 | export const UPDATE_{{ constantCase name }}_SUCCESS = '[{{ properCase name }}] Update {{ properCase name }} Success';
32 | export const UPDATE_{{ constantCase name }}_FAIL = '[{{ properCase name }}] Update {{ properCase name }} Fail';
33 |
34 | {{/ifIn}}
35 | {{#ifIn 'UPDATE_MANY' entityMethods }}
36 | export const UPDATE_{{ constantCase plural }} = '[{{ properCase plural }}] Update {{ properCase plural }}';
37 | export const UPDATE_{{ constantCase plural }}_SUCCESS = '[{{ properCase plural }}] Update {{ properCase plural }} Success';
38 | export const UPDATE_{{ constantCase plural }}_FAIL = '[{{ properCase plural }}] Update {{ properCase plural }} Fail';
39 |
40 | {{/ifIn}}
41 | {{#ifIn 'DELETE' entityMethods }}
42 | export const DELETE_{{ constantCase name }} = '[{{ properCase name }}] Delete {{ properCase name }}';
43 | export const DELETE_{{ constantCase name }}_SUCCESS = '[{{ properCase name }}] Delete {{ properCase name }} Success';
44 | export const DELETE_{{ constantCase name }}_FAIL = '[{{ properCase name }}] Delete {{ properCase name }} Fail';
45 |
46 | {{/ifIn}}
47 | {{#ifIn 'DELETE_MANY' entityMethods }}
48 | export const DELETE_{{ constantCase plural }} = '[{{ properCase plural }}] Delete {{ properCase plural }}';
49 | export const DELETE_{{ constantCase plural }}_SUCCESS = '[{{ properCase plural }}] Delete {{ properCase plural }} Success';
50 | export const DELETE_{{ constantCase plural }}_FAIL = '[{{ properCase plural }}] Delete {{ properCase plural }} Fail';
51 |
52 | {{/ifIn}}
53 | {{#ifIn 'CLEAR' entityMethods }}
54 | export const CLEAR_{{ constantCase plural }} = '[{{ properCase plural }}] Clear {{ properCase plural }}';
55 |
56 | {{/ifIn}}
57 | {{#ifIn 'GET' entityMethods }}
58 | /**
59 | * Load {{ properCase plural }} Actions
60 | * e.g LoadUsersAction
61 | */
62 | export class Load{{ properCase plural }}Action implements Action {
63 | readonly type = LOAD_{{ constantCase plural }};
64 |
65 | constructor(public payload = '') { }
66 | }
67 |
68 | export class Load{{ properCase plural }}SuccessAction implements Action {
69 | readonly type = LOAD_{{ constantCase plural }}_SUCCESS;
70 |
71 | constructor(public payload: any) { }
72 | }
73 |
74 | export class Load{{ properCase plural }}FailAction implements Action {
75 | readonly type = LOAD_{{ constantCase plural }}_FAIL;
76 |
77 | constructor(public error: HttpErrorResponse) { }
78 | }
79 |
80 | {{/ifIn}}
81 | /**
82 | * Select {{ properCase name }} Action
83 | */
84 |
85 | export class Select{{ properCase name }}Action implements Action {
86 | readonly type = SELECT_{{ constantCase name }};
87 | constructor(public {{ camelCase name }}ID: string | number) {}
88 | }
89 |
90 | {{#ifIn 'CREATE' entityMethods }}
91 | /**
92 | * Add {{ properCase name }} Actions
93 | */
94 | export class Add{{ properCase name }}Action implements Action {
95 | readonly type = ADD_{{ constantCase name }};
96 |
97 | constructor(public payload: any) { }
98 | }
99 |
100 | export class Add{{ properCase name }}SuccessAction implements Action {
101 | readonly type = ADD_{{ constantCase name }}_SUCCESS;
102 |
103 | constructor(public payload: any) { }
104 | }
105 |
106 | export class Add{{ properCase name }}FailAction implements Action {
107 | readonly type = ADD_{{ constantCase name }}_FAIL;
108 |
109 | constructor(public error: HttpErrorResponse) { }
110 | }
111 |
112 | {{/ifIn}}
113 | {{#ifIn 'CREATE_MANY' entityMethods }}
114 | /**
115 | * Add {{ properCase plural }} Actions
116 | */
117 | export class Add{{ properCase plural }}Action implements Action {
118 | readonly type = ADD_{{ constantCase plural }};
119 |
120 | constructor(public payload: any) { }
121 | }
122 |
123 | export class Add{{ properCase plural }}SuccessAction implements Action {
124 | readonly type = ADD_{{ constantCase plural }}_SUCCESS;
125 |
126 | constructor(public payload: any) { }
127 | }
128 |
129 | export class Add{{ properCase plural }}FailAction implements Action {
130 | readonly type = ADD_{{ constantCase plural }}_FAIL;
131 |
132 | constructor(public error: HttpErrorResponse) { }
133 | }
134 |
135 | {{/ifIn}}
136 | {{#ifIn 'UPDATE' entityMethods }}
137 | /**
138 | * Update {{ properCase name }} Actions
139 | */
140 | export class Update{{ properCase name }}Action implements Action {
141 | readonly type = UPDATE_{{ constantCase name }};
142 |
143 | constructor(public payload: any) { }
144 | }
145 |
146 | export class Update{{ properCase name }}SuccessAction implements Action {
147 | readonly type = UPDATE_{{ constantCase name }}_SUCCESS;
148 |
149 | constructor(public payload: any) { }
150 | }
151 |
152 | export class Update{{ properCase name }}FailAction implements Action {
153 | readonly type = UPDATE_{{ constantCase name }}_FAIL;
154 |
155 | constructor(public error: HttpErrorResponse) { }
156 | }
157 |
158 | {{/ifIn}}
159 | {{#ifIn 'UPDATE_MANY' entityMethods }}
160 | /**
161 | * Update {{ properCase plural }} Actions
162 | */
163 | export class Update{{ properCase plural }}Action implements Action {
164 | readonly type = UPDATE_{{ constantCase plural }};
165 |
166 | constructor(public payload: any) { }
167 | }
168 |
169 | export class Update{{ properCase plural }}SuccessAction implements Action {
170 | readonly type = UPDATE_{{ constantCase plural }}_SUCCESS;
171 |
172 | constructor(public payload: any) { }
173 | }
174 |
175 | export class Update{{ properCase plural }}FailAction implements Action {
176 | readonly type = UPDATE_{{ constantCase plural }}_FAIL;
177 |
178 | constructor(public error: HttpErrorResponse) { }
179 | }
180 |
181 | {{/ifIn}}
182 | {{#ifIn 'DELETE' entityMethods }}
183 | /**
184 | * Delete {{ properCase name }} Actions
185 | */
186 | export class Delete{{ properCase name }}Action implements Action {
187 | readonly type = DELETE_{{ constantCase name }};
188 |
189 | constructor(public payload: any) { }
190 | }
191 |
192 | export class Delete{{ properCase name }}SuccessAction implements Action {
193 | readonly type = DELETE_{{ constantCase name }}_SUCCESS;
194 |
195 | constructor(public payload: any) { }
196 | }
197 |
198 | export class Delete{{ properCase name }}FailAction implements Action {
199 | readonly type = DELETE_{{ constantCase name }}_FAIL;
200 |
201 | constructor(public error: HttpErrorResponse) { }
202 | }
203 |
204 | {{/ifIn}}
205 | {{#ifIn 'DELETE_MANY' entityMethods}}
206 | /**
207 | * Delete {{ properCase plural }} Actions
208 | */
209 | export class Delete{{ properCase plural }}Action implements Action {
210 | readonly type = DELETE_{{ constantCase plural }};
211 |
212 | constructor(public payload: any) { }
213 | }
214 |
215 | export class Delete{{ properCase plural }}SuccessAction implements Action {
216 | readonly type = DELETE_{{ constantCase plural }}_SUCCESS;
217 |
218 | constructor(public payload: any) { }
219 | }
220 |
221 | export class Delete{{ properCase plural }}FailAction implements Action {
222 | readonly type = DELETE_{{ constantCase plural }}_FAIL;
223 |
224 | constructor(public error: HttpErrorResponse) { }
225 | }
226 |
227 | {{/ifIn}}
228 | {{#ifIn 'CLEAR' entityMethods }}
229 | /**
230 | * Clear {{ properCase plural }} Actions
231 | */
232 | export class Clear{{ properCase plural }}Action implements Action {
233 | readonly type = CLEAR_{{ constantCase plural }};
234 |
235 | constructor(public payload: any) { }
236 | }
237 |
238 | {{/ifIn}}
239 | export type Actions =
240 | {{#ifIn 'GET' entityMethods }}
241 | Load{{ properCase plural }}Action
242 | | Load{{ properCase plural }}SuccessAction
243 | | Load{{ properCase plural }}FailAction
244 | {{/ifIn}}
245 | | Select{{ properCase name }}Action
246 | {{#ifIn 'CREATE' entityMethods }}
247 | | Add{{ properCase name }}Action
248 | | Add{{ properCase name }}FailAction
249 | | Add{{ properCase name }}SuccessAction
250 | {{/ifIn}}
251 | {{#ifIn 'CREATE_MANY' entityMethods }}
252 | | Add{{ properCase plural }}Action
253 | | Add{{ properCase plural }}FailAction
254 | | Add{{ properCase plural }}SuccessAction
255 | {{/ifIn}}
256 | {{#ifIn 'UPDATE' entityMethods }}
257 | | Update{{ properCase name }}Action
258 | | Update{{ properCase name }}SuccessAction
259 | | Update{{ properCase name }}FailAction
260 | {{/ifIn}}
261 | {{#ifIn 'UPDATE_MANY' entityMethods }}
262 | | Update{{ properCase plural }}Action
263 | | Update{{ properCase plural }}SuccessAction
264 | | Update{{ properCase plural }}FailAction
265 | {{/ifIn}}
266 | {{#ifIn 'DELETE' entityMethods }}
267 | | Delete{{ properCase name }}Action
268 | | Delete{{ properCase name }}SuccessAction
269 | | Delete{{ properCase name }}FailAction
270 | {{/ifIn}}
271 | {{#ifIn 'DELETE_MANY' entityMethods }}
272 | | Delete{{ properCase plural }}Action
273 | | Delete{{ properCase plural }}SuccessAction
274 | | Delete{{ properCase plural }}FailAction
275 | {{/ifIn}}
276 | {{#ifIn 'CLEAR' entityMethods }}
277 | | Clear{{ properCase plural }}Action{{/ifIn}};
278 |
--------------------------------------------------------------------------------
/templates/CRUD-entity/_effect.spec.ts:
--------------------------------------------------------------------------------
1 | import { fakeAsync, TestBed, tick } from '@angular/core/testing';
2 | import { {{properCase name}}Effects } from '{{position "effects"}}/{{kebabCase name}}.effects';
3 | import { {{properCase name}}Service } from '{{position "services"}}/{{kebabCase name}}.service';
4 |
5 | describe('{{properCase name}}Effects', () => {
6 | let runner, {{camelCase name}}Effects, {{camelCase name}}Service;
7 |
8 | beforeEach(() => TestBed.configureTestingModule({
9 | imports: [],
10 | providers: [
11 | {{properCase name}}Effects,
12 | {
13 | provide: {{properCase name}}Service,
14 | useValue: jasmine.createSpyObj('{{camelCase name}}Service', ['get'])
15 | }
16 | ]
17 | }));
18 |
19 | beforeEach(() => {
20 | {{camelCase name}}Effects = TestBed.get({{properCase name}}Effects);
21 | {{camelCase name}}Service = TestBed.get({{properCase name}}Service);
22 | });
23 |
24 | describe('{{camelCase name}}$', () => {
25 |
26 | it('should return a LOAD_{{ constantCase name }}_SUCCESS action, on success', function () {
27 |
28 | });
29 |
30 | it('should return a LOAD_{{ constantCase name }}_FAIL action, on error', function () {
31 |
32 | });
33 |
34 | });
35 |
36 | });
37 |
--------------------------------------------------------------------------------
/templates/CRUD-entity/_effect.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Actions, Effect, ofType } from '@ngrx/effects';
3 | import { of as observableOf } from 'rxjs';
4 | import { switchMap, map, catchError } from 'rxjs/operators';
5 | import { HttpErrorResponse } from '@angular/common/http';
6 | import { {{properCase name }}Service } from '{{position "services"}}/{{ kebabCase name }}.service';
7 | import * as {{ camelCase name }}Actions from '{{position "actions"}}/{{ kebabCase name }}.actions';
8 |
9 | @Injectable()
10 | export class {{ properCase name }}Effects {
11 | {{#ifIn 'GET' entityMethods }} @Effect() load{{ properCase plural }}$;{{/ifIn}}
12 | {{#ifIn 'CREATE' entityMethods }} @Effect() add{{ properCase name }}$;{{/ifIn}}
13 | {{#ifIn 'CREATE_MANY' entityMethods }} @Effect() add{{ properCase plural }}$;{{/ifIn}}
14 | {{#ifIn 'UPDATE' entityMethods }} @Effect() update{{ properCase name }}$;{{/ifIn}}
15 | {{#ifIn 'UPDATE_MANY' entityMethods }} @Effect() update{{ properCase plural }}$;{{/ifIn}}
16 | {{#ifIn 'DELETE' entityMethods }} @Effect() delete{{ properCase name }}$;{{/ifIn}}
17 | {{#ifIn 'DELETE_MANY' entityMethods }} @Effect() delete{{ properCase plural }}$;{{/ifIn}}
18 |
19 | constructor(
20 | private {{ camelCase name }}Service: {{ properCase name }}Service,
21 | private actions$: Actions
22 | ) {
23 | {{#ifIn 'GET' entityMethods }}
24 | this.load{{ properCase plural }}$ = this.actions$
25 | .pipe(
26 | ofType({{ camelCase name }}Actions.LOAD_{{ constantCase plural }}),
27 | switchMap((state: {{ camelCase name }}Actions.Load{{ properCase plural }}Action) =>
28 | this.{{ camelCase name }}Service.load{{ properCase plural }}().pipe(
29 | // If successful, dispatch success action with result
30 | map(res => new {{ camelCase name }}Actions.Load{{ properCase plural }}SuccessAction(res)),
31 | // If request fails, dispatch failed action
32 | catchError((err: HttpErrorResponse) => observableOf(new {{ camelCase name }}Actions.Load{{ properCase plural }}FailAction(err)))
33 | )
34 | ));
35 |
36 | {{/ifIn}}
37 | {{#ifIn 'CREATE' entityMethods }}
38 | this.add{{ properCase name }}$ = this.actions$
39 | .pipe(
40 | ofType({{ camelCase name }}Actions.ADD_{{ constantCase name }}),
41 | switchMap((state: {{ camelCase name }}Actions.Add{{ properCase name }}Action) =>
42 | this.{{ camelCase name}}Service.add{{ properCase name }}(state.payload).pipe(
43 | map(res => new {{ camelCase name }}Actions.Add{{ properCase name }}SuccessAction(res)),
44 | catchError((err: HttpErrorResponse) => observableOf(new {{ camelCase name }}Actions.Add{{ properCase name }}FailAction(err)))
45 | )
46 | ));
47 |
48 | {{/ifIn}}
49 | {{#ifIn 'CREATE_MANY' entityMethods }}
50 | this.add{{ properCase plural }}$ = this.actions$
51 | .pipe(
52 | ofType({{ camelCase name }}Actions.ADD_{{ constantCase plural }}),
53 | switchMap((state: {{ camelCase name }}Actions.Add{{ properCase plural }}Action) =>
54 | this.{{ camelCase name}}Service.add{{ properCase plural }}(state.payload).pipe(
55 | map(res => new {{ camelCase name }}Actions.Add{{ properCase plural }}SuccessAction(res)),
56 | catchError((err: HttpErrorResponse) => observableOf(new {{ camelCase name }}Actions.Add{{ properCase plural }}FailAction(err)))
57 | )
58 | ));
59 |
60 | {{/ifIn}}
61 | {{#ifIn 'UPDATE' entityMethods }}
62 | this.update{{ properCase name }}$ = this.actions$
63 | .pipe(
64 | ofType({{ camelCase name }}Actions.UPDATE_{{ constantCase name }}),
65 | switchMap((state: {{ camelCase name }}Actions.Update{{ properCase name }}Action) =>
66 | this.{{ camelCase name }}Service.update{{ properCase name }}(state.payload).pipe(
67 | map(res => new {{ camelCase name }}Actions.Update{{ properCase name }}SuccessAction(res)),
68 | catchError((err: HttpErrorResponse) => observableOf(new {{ camelCase name }}Actions.Update{{ properCase name }}FailAction(err)))
69 | )
70 | ));
71 |
72 | {{/ifIn}}
73 | {{#ifIn 'UPDATE_MANY' entityMethods }}
74 | this.update{{ properCase plural }}$ = this.actions$
75 | .pipe(
76 | ofType({{ camelCase name }}Actions.UPDATE_{{ constantCase plural }}),
77 | switchMap((state: {{ camelCase name }}Actions.Update{{ properCase plural }}Action) =>
78 | this.{{ camelCase name }}Service.update{{ properCase plural }}(state.payload).pipe(
79 | map(res => new {{ camelCase name }}Actions.Update{{ properCase plural }}SuccessAction(res)),
80 | catchError((err: HttpErrorResponse) => observableOf(new {{ camelCase name }}Actions.Update{{ properCase plural }}FailAction(err)))
81 | )
82 | ));
83 |
84 | {{/ifIn}}
85 | {{#ifIn 'DELETE' entityMethods }}
86 | this.delete{{ properCase name }}$ = this.actions$
87 | .pipe(
88 | ofType({{ camelCase name }}Actions.DELETE_{{ constantCase name }}),
89 | switchMap((state: {{ camelCase name }}Actions.Delete{{ properCase name }}Action) =>
90 | this.{{ camelCase name }}Service.delete{{ properCase name }}(state.payload).pipe(
91 | map(res => new {{ camelCase name }}Actions.Delete{{ properCase name }}SuccessAction(res)),
92 | catchError((err: HttpErrorResponse) => observableOf(new {{ camelCase name }}Actions.Delete{{ properCase name }}FailAction(err)))
93 | )
94 | ));
95 |
96 | {{/ifIn}}
97 | {{#ifIn 'DELETE' entityMethods }}
98 | this.delete{{ properCase plural }}$ = this.actions$
99 | .pipe(
100 | ofType({{ camelCase name }}Actions.DELETE_{{ constantCase plural }}),
101 | switchMap((state: {{ camelCase name }}Actions.Delete{{ properCase plural }}Action) =>
102 | this.{{ camelCase name }}Service.delete{{ properCase plural }}(state.payload).pipe(
103 | map(res => new {{ camelCase name }}Actions.Delete{{ properCase plural }}SuccessAction(res)),
104 | catchError((err: HttpErrorResponse) => observableOf(new {{ camelCase name }}Actions.Delete{{ properCase plural }}FailAction(err)))
105 | )
106 | ));
107 | {{/ifIn}}
108 | }
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/templates/CRUD-entity/_reducer.spec.ts:
--------------------------------------------------------------------------------
1 | import { reducer } from '{{position "reducers"}}/{{kebabCase name}}.reducer';
2 | import * as from{{ properCase name }} from '{{position "reducers"}}/{{kebabCase name}}.reducer';
3 |
4 | describe('{{properCase name}}Reducer', () => {
5 |
6 | describe('undefined action', () => {
7 | it('should return the default state', () => {
8 | const action = {} as any;
9 |
10 | const result = reducer(undefined, action);
11 | expect(result).toEqual(from{{ properCase name }}.initialState);
12 | });
13 | });
14 |
15 | });
--------------------------------------------------------------------------------
/templates/CRUD-entity/_reducer.ts:
--------------------------------------------------------------------------------
1 | import * as {{ camelCase name }} from '{{position "actions"}}/{{ kebabCase name }}.actions';
2 | import { HttpErrorResponse } from '@angular/common/http';
3 |
4 | import { createEntityAdapter, EntityState, EntityAdapter } from '@ngrx/entity';
5 |
6 | export interface {{ properCase name }} { // Or you can replace it by importing your own model
7 | id: number;
8 | }
9 |
10 | export const {{ camelCase name }}Adapter: EntityAdapter<{{ properCase name }}> = createEntityAdapter<{{ properCase name }}>({
11 | sortComparer: false
12 | });
13 | // tslint:disable-next-line:no-empty-interface
14 | export interface {{ properCase name }}State extends EntityState<{{ properCase name }}> {
15 | loading: boolean;
16 | error: HttpErrorResponse;
17 | selected{{ properCase name }}ID: string | number | null;
18 | }
19 |
20 | export const default{{ properCase name }}State = {
21 | loading: false,
22 | error: null,
23 | selected{{ properCase name }}ID: null
24 | };
25 |
26 | export const initial{{ properCase name }}State: {{ properCase name }}State = {{ camelCase name }}Adapter.getInitialState(default{{ properCase name }}State);
27 |
28 | export function reducer(state = initial{{ properCase name }}State, action: {{ camelCase name }}.Actions): {{ properCase name }}State {
29 | switch (action.type) {
30 | {{#ifIn 'GET' entityMethods }}
31 | case {{ camelCase name }}.LOAD_{{ constantCase plural }}:
32 | return {
33 | ...state,
34 | loading: true,
35 | error: null
36 | };
37 |
38 | case {{ camelCase name }}.LOAD_{{ constantCase plural }}_SUCCESS:
39 | return {
40 | ...{{ camelCase name }}Adapter.addAll(action.payload, state),
41 | loading: false,
42 | error: null
43 | };
44 |
45 | case {{ camelCase name }}.LOAD_{{ constantCase plural }}_FAIL:
46 | return {
47 | ...state,
48 | loading: false,
49 | error: action.error
50 | };
51 |
52 | {{/ifIn}}
53 | case {{ camelCase name }}.SELECT_{{ constantCase name }}:
54 | return {
55 | ...state,
56 | loading: false,
57 | error: null,
58 | selected{{ properCase name }}ID: action.{{ camelCase name }}ID
59 | };
60 |
61 | {{#ifIn 'CREATE' entityMethods }}
62 | case {{ camelCase name }}.ADD_{{ constantCase name }}:
63 | return {
64 | ...state,
65 | loading: true,
66 | error: null
67 | };
68 |
69 | case {{ camelCase name }}.ADD_{{ constantCase name }}_SUCCESS:
70 | return {
71 | ...{{ camelCase name }}Adapter.addOne(action.payload.{{ camelCase name }}, state),
72 | loading: false,
73 | error: null
74 | };
75 |
76 | case {{ camelCase name }}.ADD_{{ constantCase name }}_FAIL:
77 | return {
78 | ...state,
79 | loading: false,
80 | error: action.error
81 | };
82 |
83 | {{/ifIn}}
84 | {{#ifIn 'CREATE_MANY' entityMethods }}
85 | case {{ camelCase name }}.ADD_{{ constantCase plural }}:
86 | return {
87 | ...state,
88 | loading: true,
89 | error: null
90 | };
91 |
92 | case {{ camelCase name }}.ADD_{{ constantCase plural }}_SUCCESS:
93 | return {
94 | ...{{ camelCase name }}Adapter.addMany(action.payload.{{ camelCase plural }}, state),
95 | loading: false,
96 | error: null
97 | };
98 |
99 | case {{ camelCase name }}.ADD_{{ constantCase plural }}_FAIL:
100 | return {
101 | ...state,
102 | loading: false,
103 | error: action.error
104 | };
105 |
106 | {{/ifIn}}
107 | {{#ifIn 'UPDATE' entityMethods }}
108 | case {{ camelCase name }}.UPDATE_{{ constantCase name }}:
109 | return {
110 | ...state,
111 | loading: true,
112 | error: null
113 | };
114 |
115 | case {{ camelCase name }}.UPDATE_{{ constantCase name }}_SUCCESS:
116 | return {
117 | ...{{ camelCase name }}Adapter.updateOne(action.payload.id, state),
118 | loading: false,
119 | error: null
120 | };
121 |
122 | case {{ camelCase name }}.UPDATE_{{ constantCase name }}_FAIL:
123 | return {
124 | ...state,
125 | loading: false,
126 | error: action.error
127 | };
128 |
129 | {{/ifIn}}
130 | {{#ifIn 'UPDATE_MANY' entityMethods }}
131 | case {{ camelCase name }}.UPDATE_{{ constantCase plural }}:
132 | return {
133 | ...state,
134 | loading: true,
135 | error: null
136 | };
137 |
138 | case {{ camelCase name }}.UPDATE_{{ constantCase plural }}_SUCCESS:
139 | return {
140 | ...{{ camelCase name }}Adapter.updateMany(action.payload.ids, state),
141 | loading: false,
142 | error: null
143 | };
144 |
145 | case {{ camelCase name }}.UPDATE_{{ constantCase plural }}_FAIL:
146 | return {
147 | ...state,
148 | loading: false,
149 | error: action.error
150 | };
151 |
152 | {{/ifIn}}
153 | {{#ifIn 'DELETE' entityMethods }}
154 | case {{ camelCase name }}.DELETE_{{ constantCase name }}:
155 | return {
156 | ...state,
157 | loading: true,
158 | error: null
159 | };
160 |
161 | case {{ camelCase name }}.DELETE_{{ constantCase name }}_SUCCESS:
162 | return {
163 | ...{{ camelCase name }}Adapter.removeOne(action.payload.id, state),
164 | loading: false,
165 | error: null
166 | };
167 |
168 | case {{ camelCase name }}.DELETE_{{ constantCase name }}_FAIL:
169 | return {
170 | ...state,
171 | loading: false,
172 | error: action.error
173 | };
174 |
175 | {{/ifIn}}
176 | {{#ifIn 'DELETE_MANY' entityMethods }}
177 | case {{ camelCase name }}.DELETE_{{ constantCase plural }}:
178 | return {
179 | ...state,
180 | loading: true,
181 | error: null
182 | };
183 |
184 | case {{ camelCase name }}.DELETE_{{ constantCase plural }}_SUCCESS:
185 | return {
186 | ...{{ camelCase name }}Adapter.removeOne(action.payload.ids, state),
187 | loading: false,
188 | error: null
189 | };
190 |
191 | case {{ camelCase name }}.DELETE_{{ constantCase plural }}_FAIL:
192 | return {
193 | ...state,
194 | loading: false,
195 | error: action.error
196 | };
197 |
198 | {{/ifIn}}
199 | {{#ifIn 'CLEAR' entityMethods }}
200 | case {{ camelCase name }}.CLEAR_{{ constantCase plural }}:
201 | return {
202 | ...{{ camelCase name }}Adapter.removeAll({ ...state, loading: true, error: null, selected{{ properCase name }}ID: null }),
203 | };
204 |
205 | {{/ifIn}}
206 | default: {
207 | return state;
208 | }
209 | }
210 | }
211 |
212 | export const {
213 | selectIds: select{{ properCase name }}IDs,
214 | selectEntities: select{{ properCase name }}Entities,
215 | selectAll: selectAll{{ properCase plural }},
216 | selectTotal: select{{ properCase name }}Total
217 | } = {{ camelCase name }}Adapter.getSelectors();
218 |
--------------------------------------------------------------------------------
/templates/CRUD-entity/_selectors.ts:
--------------------------------------------------------------------------------
1 | import { createFeatureSelector, createSelector } from '@ngrx/store';
2 | import * as from{{ properCase name }} from './{{ kebabCase name }}.reducer';
3 |
4 | export const select{{ properCase name }}State = createFeatureSelector('{{ camelCase name }}');
5 |
6 | export const getSelected{{ properCase name }}ID = (state: from{{ properCase name }}.{{ properCase name }}State) => state.selected{{ properCase name }}ID;
7 |
8 | export const select{{ properCase name }}Ids = createSelector(select{{ properCase name }}State, from{{ properCase name }}.select{{ properCase name }}IDs);
9 | export const select{{ properCase name }}Entities = createSelector(select{{ properCase name }}State, from{{ properCase name }}.select{{ properCase name }}Entities);
10 | export const selectAll{{ properCase plural }} = createSelector(select{{ properCase name }}State, from{{ properCase name }}.selectAll{{ properCase plural }});
11 | export const select{{ properCase name }}Total = createSelector(select{{ properCase name }}State, from{{ properCase name }}.select{{ properCase name }}Total);
12 | export const selectCurrent{{ properCase name }}ID = createSelector(select{{ properCase name }}State, getSelected{{ properCase name }}ID);
13 |
14 | export const selectCurrent{{ properCase name }} = createSelector(
15 | select{{ properCase name }}Entities,
16 | selectCurrent{{ properCase name }}ID,
17 | ({{ camelCase name }}Entities, {{ camelCase name }}ID) => {{ camelCase name }}Entities[{{ camelCase name }}ID]
18 | );
--------------------------------------------------------------------------------
/templates/CRUD-entity/_service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, getTestBed } from '@angular/core/testing';
2 | import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
3 | import { {{ properCase name }}Service } from '{{position "services"}}/{{ kebabCase name }}.service';
4 |
5 | describe('Service: {{ properCase name }}Service', () => {
6 |
7 | let injector: TestBed;
8 | let service: {{ properCase name }}Service;
9 | let httpMock: HttpTestingController;
10 |
11 | beforeEach(() => {
12 | TestBed.configureTestingModule({
13 | imports: [HttpClientTestingModule],
14 | providers: [{{ properCase name }}Service]
15 | });
16 | injector = getTestBed();
17 | service = injector.get({{ properCase name }}Service);
18 | httpMock = injector.get(HttpTestingController);
19 | });
20 |
21 | afterEach(() => {
22 | httpMock.verify();
23 | });
24 |
25 | // --------------------Example------------------------
26 |
27 | // const data = {
28 | // 'title': 'Book Title',
29 | // 'author': 'John Smith',
30 | // 'volumeId': '12345'
31 | // };
32 |
33 | // const books = {
34 | // items: [
35 | // {id: '12345', volumeInfo: {title: 'Title'}},
36 | // {id: '67890', volumeInfo: {title: 'Another Title'}}
37 | // ]
38 | // };
39 |
40 | // const queryTitle = 'Book Title';
41 |
42 | // it('should call the search api and return the search results', (done) => {
43 | // backend.connections.subscribe((connection: MockConnection) => {
44 | // const options = new ResponseOptions({
45 | // body: JSON.stringify(books)
46 | // });
47 | // connection.mockRespond(new Response(options));
48 | // expect(connection.request.method).toEqual(RequestMethod.Get);
49 | // expect(connection.request.url).toEqual(`https://www.googleapis.com/books/v1/volumes?q=${queryTitle}`);
50 | // });
51 |
52 | // service
53 | // .searchBooks(queryTitle)
54 | // .subscribe((res) => {
55 | // expect(res).toEqual(books.items);
56 | // done();
57 | // });
58 | // });
59 |
60 | });
61 |
--------------------------------------------------------------------------------
/templates/CRUD-entity/_service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 |
5 | @Injectable()
6 | export class {{ properCase name }}Service {
7 |
8 | constructor(private http: HttpClient) { }
9 |
10 | {{#ifIn 'GET' entityMethods }}
11 | load{{ properCase plural }}(): Observable {
12 | return this.http.get('https://api.com');
13 | }
14 |
15 | {{/ifIn}}
16 | {{#ifIn 'CREATE' entityMethods }}
17 | add{{ properCase name }}(param: any): Observable {
18 | return this.http.post('https://api.com', { body: param });
19 | }
20 |
21 | {{/ifIn}}
22 | {{#ifIn 'CREATE_MANY' entityMethods }}
23 | add{{ properCase plural }}(param: any): Observable {
24 | return this.http.post('https://api.com', { body: param });
25 | }
26 |
27 | {{/ifIn}}
28 | {{#ifIn 'UPDATE' entityMethods }}
29 | update{{ properCase name }}(param: any): Observable {
30 | return this.http.patch('https://api.com', { body: param });
31 | }
32 |
33 | {{/ifIn}}
34 | {{#ifIn 'UPDATE_MANY' entityMethods }}
35 | update{{ properCase plural }}(param: any): Observable {
36 | return this.http.patch('https://api.com', { body: param });
37 | }
38 |
39 | {{/ifIn}}
40 | {{#ifIn 'DELETE' entityMethods }}
41 | delete{{ properCase name }}(param: any): Observable {
42 | return this.http.delete('https://api.com');
43 | }
44 |
45 | {{/ifIn}}
46 | {{#ifIn 'DELETE_MANY' entityMethods }}
47 | delete{{ properCase plural }}(param: any): Observable {
48 | return this.http.delete('https://api.com');
49 | }
50 | {{/ifIn}}
51 | }
52 |
--------------------------------------------------------------------------------
/templates/CRUD/_actions.ts:
--------------------------------------------------------------------------------
1 | import { Action } from '@ngrx/store';
2 | import { HttpErrorResponse } from '@angular/common/http';
3 |
4 | {{#each crudMethods }}
5 | export const {{this}}_{{ constantCase ../name }} = '[{{ properCase ../name }}] {{ properCase this }} {{ properCase ../name }}';
6 | export const {{this}}_{{ constantCase ../name }}_SUCCESS = '[{{ properCase ../name }}] {{ properCase this }} {{ properCase ../name }} Success';
7 | export const {{this}}_{{ constantCase ../name }}_FAIL = '[{{ properCase ../name }}] {{ properCase this }} {{ properCase ../name }} Fail';
8 |
9 | {{/each}}
10 | {{#ifIn 'GET' crudMethods }}
11 | /**
12 | * Get {{ properCase name }} Actions
13 | * e.g GetAuthAction
14 | */
15 | export class Get{{ properCase name }}Action implements Action {
16 | readonly type = GET_{{ constantCase name }};
17 |
18 | constructor(public payload: any) { }
19 | }
20 |
21 | export class Get{{ properCase name }}SuccessAction implements Action {
22 | readonly type = GET_{{ constantCase name }}_SUCCESS;
23 |
24 | constructor(public payload: any) { }
25 | }
26 |
27 | export class Get{{ properCase name }}FailAction implements Action {
28 | readonly type = GET_{{ constantCase name }}_FAIL;
29 |
30 | constructor(public error: HttpErrorResponse) { }
31 | }
32 | {{/ifIn}}
33 |
34 | {{#ifIn 'CREATE' crudMethods }}
35 | /**
36 | * Create {{ properCase name }} Actions
37 | */
38 | export class Create{{ properCase name }}Action implements Action {
39 | readonly type = CREATE_{{ constantCase name }};
40 |
41 | constructor(public payload: any) { }
42 | }
43 |
44 | export class Create{{ properCase name }}SuccessAction implements Action {
45 | readonly type = CREATE_{{ constantCase name }}_SUCCESS;
46 |
47 | constructor(public payload: any) { }
48 | }
49 |
50 | export class Create{{ properCase name }}FailAction implements Action {
51 | readonly type = CREATE_{{ constantCase name }}_FAIL;
52 |
53 | constructor(public error: HttpErrorResponse) { }
54 | }
55 | {{/ifIn}}
56 |
57 | {{#ifIn 'UPDATE' crudMethods }}
58 | /**
59 | * Update {{ properCase name }} Actions
60 | */
61 | export class Update{{ properCase name }}Action implements Action {
62 | readonly type = UPDATE_{{ constantCase name }};
63 |
64 | constructor(public payload: any) { }
65 | }
66 |
67 | export class Update{{ properCase name }}SuccessAction implements Action {
68 | readonly type = UPDATE_{{ constantCase name }}_SUCCESS;
69 |
70 | constructor(public payload: any) { }
71 | }
72 |
73 | export class Update{{ properCase name }}FailAction implements Action {
74 | readonly type = UPDATE_{{ constantCase name }}_FAIL;
75 |
76 | constructor(public error: HttpErrorResponse) { }
77 | }
78 | {{/ifIn}}
79 |
80 | {{#ifIn 'DELETE' crudMethods }}
81 | /**
82 | * Delete {{ properCase name }} Actions
83 | */
84 | export class Delete{{ properCase name }}Action implements Action {
85 | readonly type = DELETE_{{ constantCase name }};
86 |
87 | constructor(public payload: any) { }
88 | }
89 |
90 | export class Delete{{ properCase name }}SuccessAction implements Action {
91 | readonly type = DELETE_{{ constantCase name }}_SUCCESS;
92 |
93 | constructor(public payload: any) { }
94 | }
95 |
96 | export class Delete{{ properCase name }}FailAction implements Action {
97 | readonly type = DELETE_{{ constantCase name }}_FAIL;
98 |
99 | constructor(public error: HttpErrorResponse) { }
100 | }
101 | {{/ifIn}}
102 |
103 | export type Actions =
104 | {{#each crudMethods }}
105 | | {{properCase this}}{{ properCase ../name }}Action
106 | | {{properCase this}}{{ properCase ../name }}SuccessAction
107 | | {{properCase this}}{{ properCase ../name }}FailAction
108 | {{/each}};
109 |
--------------------------------------------------------------------------------
/templates/CRUD/_effect.spec.ts:
--------------------------------------------------------------------------------
1 | import { fakeAsync, TestBed, tick } from '@angular/core/testing';
2 | import { {{properCase name}}Effects } from '{{position "effects"}}/{{kebabCase name}}.effects';
3 | import { {{properCase name}}Service } from '{{position "services"}}/{{kebabCase name}}.service';
4 |
5 | describe('{{properCase name}}Effects', () => {
6 | let runner, {{camelCase name}}Effects, {{camelCase name}}Service;
7 |
8 | beforeEach(() => TestBed.configureTestingModule({
9 | imports: [],
10 | providers: [
11 | {{properCase name}}Effects,
12 | {
13 | provide: {{properCase name}}Service,
14 | useValue: jasmine.createSpyObj('{{camelCase name}}Service', ['get'])
15 | }
16 | ]
17 | }));
18 |
19 | beforeEach(() => {
20 | {{camelCase name}}Effects = TestBed.get({{properCase name}}Effects);
21 | {{camelCase name}}Service = TestBed.get({{properCase name}}Service);
22 | });
23 |
24 | describe('{{camelCase name}}$', () => {
25 |
26 | it('should return a LOAD_{{ constantCase name }}_SUCCESS action, on success', function () {
27 |
28 | });
29 |
30 | it('should return a LOAD_{{ constantCase name }}_FAIL action, on error', function () {
31 |
32 | });
33 |
34 | });
35 |
36 | });
37 |
--------------------------------------------------------------------------------
/templates/CRUD/_effect.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Actions, Effect, ofType } from '@ngrx/effects';
3 | import { of as observableOf } from 'rxjs';
4 | import { switchMap, map, catchError } from 'rxjs/operators';
5 | import { HttpErrorResponse } from '@angular/common/http';
6 | import { {{properCase name }}Service } from '{{position "services"}}/{{ kebabCase name }}.service';
7 | import * as {{ camelCase name }}Actions from '{{position "actions"}}/{{ kebabCase name }}.actions';
8 |
9 | @Injectable()
10 | export class {{ properCase name }}Effects {
11 | {{#each crudMethods }}
12 | @Effect() {{ lowerCase this }}$;
13 | {{/each}}
14 |
15 | constructor(
16 | private {{ camelCase name }}Service: {{ properCase name }}Service,
17 | private actions$: Actions
18 | ) {
19 | {{#each crudMethods}}
20 | this.{{ lowerCase this }}$ = this.actions$
21 | .pipe(
22 | ofType({{ camelCase ../name }}Actions.{{this}}_{{ constantCase ../name }}),
23 | switchMap((state: {{ camelCase ../name }}Actions.{{ properCase this }}{{ properCase ../name }}Action) =>
24 | this.{{ camelCase ../name }}Service.{{ lowerCase this }}{{ properCase ../name }}({{#isNotEqual this 'GET' }}state.payload{{/isNotEqual}}).pipe(
25 | // If successful, dispatch success action with result
26 | map(res => new {{ camelCase ../name }}Actions.{{ properCase this }}{{ properCase ../name }}SuccessAction(res)),
27 | // If request fails, dispatch failed action
28 | catchError((err: HttpErrorResponse) => observableOf(new {{ camelCase ../name }}Actions.{{ properCase this }}{{ properCase ../name }}FailAction(err)))
29 | )
30 | ));
31 | {{/each}}
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/templates/CRUD/_reducer.spec.ts:
--------------------------------------------------------------------------------
1 | import { reducer } from '{{position "reducers"}}/{{kebabCase name}}.reducer';
2 | import * as from{{ properCase name }} from '{{position "reducers"}}/{{kebabCase name}}.reducer';
3 |
4 | describe('{{properCase name}}Reducer', () => {
5 |
6 | describe('undefined action', () => {
7 | it('should return the default state', () => {
8 | const action = {} as any;
9 |
10 | const result = reducer(undefined, action);
11 | expect(result).toEqual(from{{ properCase name }}.initialState);
12 | });
13 | });
14 |
15 | });
--------------------------------------------------------------------------------
/templates/CRUD/_reducer.ts:
--------------------------------------------------------------------------------
1 | import * as {{ camelCase name }} from '{{position "actions"}}/{{ kebabCase name }}.actions';
2 | import { HttpErrorResponse } from '@angular/common/http';
3 |
4 | export interface {{ properCase name }}State {
5 | loading: boolean;
6 | entities: { [id: string]: any };
7 | result: any[];
8 | error: HttpErrorResponse;
9 | type: string;
10 | };
11 |
12 | export const initialState: {{ properCase name }}State = {
13 | loading: false,
14 | entities: {},
15 | result: [],
16 | error: null,
17 | type: ''
18 | };
19 |
20 | export function reducer(state = initialState, action: {{ camelCase name }}.Actions): {{ properCase name }}State {
21 | switch (action.type) {
22 | {{#ifIn 'GET' crudMethods }}
23 | case {{ camelCase name }}.GET_{{ constantCase name }}: {
24 | return {
25 | ...state,
26 | loading: true,
27 | error: null,
28 | type: action.type
29 | }
30 | }
31 |
32 | case {{ camelCase name }}.GET_{{ constantCase name }}_SUCCESS: {
33 | return {
34 | ...state,
35 | result: action.payload,
36 | loading: false,
37 | error: null,
38 | type: action.type
39 | };
40 | }
41 |
42 | case {{ camelCase name }}.GET_{{ constantCase name }}_FAIL: {
43 | return {
44 | ...state,
45 | loading: false,
46 | error: action.error,
47 | type: action.type
48 | };
49 | }
50 | {{/ifIn}}
51 | {{#ifIn 'CREATE' crudMethods }}
52 | case {{ camelCase name }}.CREATE_{{ constantCase name }}: {
53 | return {
54 | ...state,
55 | loading: true,
56 | error: null,
57 | type: action.type
58 | }
59 | }
60 |
61 | case {{ camelCase name }}.CREATE_{{ constantCase name }}_SUCCESS: {
62 | const result = [...state.result, action.payload];
63 | return {
64 | ...state,
65 | result: result,
66 | loading: false,
67 | error: null,
68 | type: action.type
69 | };
70 | }
71 |
72 | case {{ camelCase name }}.CREATE_{{ constantCase name }}_FAIL: {
73 | return {
74 | ...state,
75 | loading: false,
76 | error: action.error,
77 | type: action.type
78 | };
79 | }
80 | {{/ifIn}}
81 | {{#ifIn 'UPDATE' crudMethods }}
82 | case {{ camelCase name }}.UPDATE_{{ constantCase name }}: {
83 | return {
84 | ...state,
85 | loading: true,
86 | error: null,
87 | type: action.type
88 | }
89 | }
90 |
91 | case {{ camelCase name }}.UPDATE_{{ constantCase name }}_SUCCESS: {
92 | return {
93 | ...state,
94 | loading: false,
95 | error: null,
96 | type: action.type
97 | };
98 | }
99 |
100 | case {{ camelCase name }}.UPDATE_{{ constantCase name }}_FAIL: {
101 | return {
102 | ...state,
103 | loading: false,
104 | error: action.error,
105 | type: action.type
106 | };
107 | }
108 | {{/ifIn}}
109 | {{#ifIn 'DELETE' crudMethods }}
110 | case {{ camelCase name }}.DELETE_{{ constantCase name }}: {
111 | return {
112 | ...state,
113 | loading: true,
114 | error: null,
115 | type: action.type
116 | }
117 | }
118 |
119 | case {{ camelCase name }}.DELETE_{{ constantCase name }}_SUCCESS: {
120 | return {
121 | ...state,
122 | loading: false,
123 | error: null,
124 | type: action.type
125 | };
126 | }
127 |
128 | case {{ camelCase name }}.DELETE_{{ constantCase name }}_FAIL: {
129 | return {
130 | ...state,
131 | loading: false,
132 | error: action.error,
133 | type: action.type
134 | };
135 | }
136 | {{/ifIn}}
137 |
138 | default: {
139 | return state;
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/templates/CRUD/_service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, getTestBed } from '@angular/core/testing';
2 | import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
3 | import { {{ properCase name }}Service } from '{{position "services"}}/{{ kebabCase name }}.service';
4 |
5 | describe('Service: {{ properCase name }}Service', () => {
6 |
7 | let injector: TestBed;
8 | let service: {{ properCase name }}Service;
9 | let httpMock: HttpTestingController;
10 |
11 | beforeEach(() => {
12 | TestBed.configureTestingModule({
13 | imports: [HttpClientTestingModule],
14 | providers: [{{ properCase name }}Service]
15 | });
16 | injector = getTestBed();
17 | service = injector.get({{ properCase name }}Service);
18 | httpMock = injector.get(HttpTestingController);
19 | });
20 |
21 | afterEach(() => {
22 | httpMock.verify();
23 | });
24 |
25 | // --------------------Example------------------------
26 |
27 | // const data = {
28 | // 'title': 'Book Title',
29 | // 'author': 'John Smith',
30 | // 'volumeId': '12345'
31 | // };
32 |
33 | // const books = {
34 | // items: [
35 | // {id: '12345', volumeInfo: {title: 'Title'}},
36 | // {id: '67890', volumeInfo: {title: 'Another Title'}}
37 | // ]
38 | // };
39 |
40 | // const queryTitle = 'Book Title';
41 |
42 | // it('should call the search api and return the search results', (done) => {
43 | // backend.connections.subscribe((connection: MockConnection) => {
44 | // const options = new ResponseOptions({
45 | // body: JSON.stringify(books)
46 | // });
47 | // connection.mockRespond(new Response(options));
48 | // expect(connection.request.method).toEqual(RequestMethod.Get);
49 | // expect(connection.request.url).toEqual(`https://www.googleapis.com/books/v1/volumes?q=${queryTitle}`);
50 | // });
51 |
52 | // service
53 | // .searchBooks(queryTitle)
54 | // .subscribe((res) => {
55 | // expect(res).toEqual(books.items);
56 | // done();
57 | // });
58 | // });
59 |
60 | });
61 |
--------------------------------------------------------------------------------
/templates/CRUD/_service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 |
5 | @Injectable()
6 | export class {{ properCase name }}Service {
7 |
8 | constructor(private http: HttpClient) { }
9 |
10 | {{#ifIn 'GET' crudMethods }}
11 | get{{ properCase name }}(): Observable {
12 | return this.http.get('https://api.com');
13 | }
14 | {{/ifIn}}
15 |
16 | {{#ifIn 'CREATE' crudMethods }}
17 | create{{ properCase name }}(param: any): Observable {
18 | return this.http.post('https://api.com', { body: param });
19 | }
20 | {{/ifIn}}
21 |
22 | {{#ifIn 'UPDATE' crudMethods }}
23 | update{{ properCase name }}(param: any): Observable {
24 | return this.http.patch('https://api.com', { body: param });
25 | }
26 | {{/ifIn}}
27 |
28 | {{#ifIn 'DELETE' crudMethods }}
29 | delete{{ properCase name }}(param: any): Observable {
30 | return this.http.delete('https://api.com');
31 | }
32 | {{/ifIn}}
33 | }
34 |
--------------------------------------------------------------------------------
/templates/_all-effects.ts:
--------------------------------------------------------------------------------
1 | // -- IMPORT --
2 | import { {{ properCase name }}Effects } from './{{ folder name "effects" }}/{{ kebabCase name }}.effects';
3 |
4 | export const AllEffects = [
5 | // -- LIST --
6 | {{ properCase name }}Effects
7 | ];
8 |
--------------------------------------------------------------------------------
/templates/_app.store.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ActionReducerMap,
3 | ActionReducer,
4 | MetaReducer,
5 | createSelector,
6 | createFeatureSelector,
7 | } from '@ngrx/store';
8 | /**
9 | * storeFreeze prevents state from being mutated. When mutation occurs, an
10 | * exception will be thrown. This is useful during development mode to
11 | * ensure that none of the reducers accidentally mutates the state.
12 | */
13 | import { storeFreeze } from 'ngrx-store-freeze';
14 |
15 | import { environment } from 'environments/environment';
16 |
17 | // -- IMPORT REDUCER --
18 | import * as {{ camelCase name }} from './{{ folder name 'reducers' }}/{{ kebabCase name }}.reducer';
19 |
20 | export interface State {
21 | // -- IMPORT STATE --
22 | {{ camelCase name }}: {{ camelCase name }}.{{ properCase name }}State;
23 | }
24 |
25 | export const reducers: ActionReducerMap = {
26 | // -- ADD REDUCER --
27 | {{ camelCase name }}: {{ camelCase name }}.reducer
28 | };
29 |
30 | /** For debug purpose */
31 | export function logger(reducer: ActionReducer): ActionReducer {
32 | return function (state: State, action: any): State {
33 | console.groupCollapsed(action.type);
34 | const nextState = reducer(state, action);
35 | console.log(`%c previous state`, `color: #9E9E9E; font-weight: bold`, state);
36 | console.log(`%c action`, `color: #03A9F4; font-weight: bold`, action);
37 | console.log(`%c next state`, `color: #4CAF50; font-weight: bold`, nextState);
38 | console.groupEnd();
39 | return nextState;
40 | };
41 | }
42 |
43 | export const metaReducers: MetaReducer[] = !environment.production
44 | ? [logger, storeFreeze]
45 | : [];
46 |
--------------------------------------------------------------------------------
/templates/_router-serializer.ts:
--------------------------------------------------------------------------------
1 | import { createSelector } from '@ngrx/store';
2 | import { RouterStateSnapshot, ParamMap } from '@angular/router';
3 | import { RouterStateSerializer, RouterReducerState } from '@ngrx/router-store';
4 |
5 | export interface RouterStateUrl {
6 | url: string;
7 | paramMap: ParamMap;
8 | queryParamMap: ParamMap;
9 | }
10 |
11 | export class CustomSerializer implements RouterStateSerializer {
12 | serialize(routerState: RouterStateSnapshot): RouterStateUrl {
13 | let route = routerState.root;
14 |
15 | while (route.firstChild) {
16 | route = route.firstChild;
17 | }
18 |
19 | const { url, root: { queryParamMap } } = routerState;
20 | const { paramMap } = route;
21 |
22 | // Only return an object including the URL, params and query params
23 | // instead of the entire snapshot
24 | return { url, paramMap, queryParamMap };
25 | }
26 | }
27 |
28 | export const getRouterState = (state) => state.router;
29 | export const getCurrentUrl = createSelector(getRouterState,
30 | (router: RouterReducerState) => router.state && router.state.url);
31 | export const getAllParams = createSelector(getRouterState,
32 | (router: RouterReducerState) => router.state && router.state.paramMap);
33 | export const getAllQueryParams = createSelector(getRouterState,
34 | (router: RouterReducerState) => router.state && router.state.queryParamMap);
35 |
--------------------------------------------------------------------------------
/templates/_store-reduxor.module.ts:
--------------------------------------------------------------------------------
1 | import { ModuleWithProviders, NgModule } from '@angular/core';
2 | import { StoreModule } from '@ngrx/store';
3 | import { EffectsModule } from '@ngrx/effects';
4 |
5 | // Uncomment this line if you want to use the StoreDevtool
6 | // import { StoreDevtoolsModule } from '@ngrx/store-devtools';
7 |
8 | import { reducers, metaReducers } from './app.store';
9 | import { AllEffects } from './all-effects';
10 | import { HttpClientModule } from '@angular/common/http';
11 |
12 | // -- IMPORT SERVICES --
13 | import { {{ properCase name }}Service } from './{{ folder name "services" }}/{{ kebabCase name }}.service';
14 |
15 | @NgModule({
16 | imports: [
17 | HttpClientModule,
18 | StoreModule.forRoot(reducers, { metaReducers }),
19 | EffectsModule.forRoot([...AllEffects]),
20 | // StoreDevtoolsModule.instrument({
21 | // maxAge: 25, // Retains last 25 states
22 | // })
23 | ],
24 | exports: [],
25 | providers: [
26 | // -- PROVIDERS --
27 | {{ properCase name }}Service
28 | ]
29 | })
30 | export class StoreReduxorModule {
31 | static forRoot(): ModuleWithProviders {
32 | return {
33 | ngModule: StoreReduxorModule
34 | };
35 | }
36 | }
37 |
--------------------------------------------------------------------------------