Skip to content

Commit

Permalink
feat(schematics): add api success/failure effects/actions to ng gener…
Browse files Browse the repository at this point in the history
…ate feature (#1530)
  • Loading branch information
wesleygrimes authored and brandonroberts committed Jan 28, 2019
1 parent 48a2370 commit e17a787
Show file tree
Hide file tree
Showing 16 changed files with 295 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import { Action } from '@ngrx/store';

export enum <%= classify(name) %>ActionTypes {
Load<%= classify(name) %>s = '[<%= classify(name) %>] Load <%= classify(name) %>s'
Load<%= classify(name) %>s = '[<%= classify(name) %>] Load <%= classify(name) %>s',
<% if (api) { %>Load<%= classify(name) %>sSuccess = '[<%= classify(name) %>] Load <%= classify(name) %>s Success',<% } %>
<% if (api) { %>Load<%= classify(name) %>sFailure = '[<%= classify(name) %>] Load <%= classify(name) %>s Failure',<% } %>
}

export class Load<%= classify(name) %>s implements Action {
readonly type = <%= classify(name) %>ActionTypes.Load<%= classify(name) %>s;
}
<% if (api) { %>
export class Load<%= classify(name) %>sSuccess implements Action {
readonly type = <%= classify(name) %>ActionTypes.Load<%= classify(name) %>sSuccess;
constructor(public payload: { data: any }) { }
}

export type <%= classify(name) %>Actions = Load<%= classify(name) %>s;
export class Load<%= classify(name) %>sFailure implements Action {
readonly type = <%= classify(name) %>ActionTypes.Load<%= classify(name) %>sFailure;
constructor(public payload: { error: any }) { }
}
<% } %>
<% if (api) { %>export type <%= classify(name) %>Actions = Load<%= classify(name) %>s | Load<%= classify(name) %>sSuccess | Load<%= classify(name) %>sFailure;<% } %>
<% if (!api) { %>export type <%= classify(name) %>Actions = Load<%= classify(name) %>s;<% } %>
54 changes: 54 additions & 0 deletions modules/schematics/src/action/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,58 @@ describe('Action Schematic', () => {
tree.files.indexOf(`${projectPath}/src/app/actions/foo.actions.ts`)
).toBeGreaterThanOrEqual(0);
});

it('should create a success class based on the provided name, given api', () => {
const tree = schematicRunner.runSchematic(
'action',
{
...defaultOptions,
api: true,
},
appTree
);
const fileContent = tree.readContent(
`${projectPath}/src/app/foo.actions.ts`
);

expect(fileContent).toMatch(
/export class LoadFoosSuccess implements Action/
);
});

it('should create a failure class based on the provided name, given api', () => {
const tree = schematicRunner.runSchematic(
'action',
{
...defaultOptions,
api: true,
},
appTree
);
const fileContent = tree.readContent(
`${projectPath}/src/app/foo.actions.ts`
);

expect(fileContent).toMatch(
/export class LoadFoosFailure implements Action/
);
});

it('should create the union type with success and failure based on the provided name, given api', () => {
const tree = schematicRunner.runSchematic(
'action',
{
...defaultOptions,
api: true,
},
appTree
);
const fileContent = tree.readContent(
`${projectPath}/src/app/foo.actions.ts`
);

expect(fileContent).toMatch(
/export type FooActions = LoadFoos \| LoadFoosSuccess \| LoadFoosFailure/
);
});
});
7 changes: 7 additions & 0 deletions modules/schematics/src/action/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@
"default": false,
"description": "Group actions file within 'actions' folder",
"aliases": ["g"]
},
"api": {
"type": "boolean",
"default": false,
"description":
"Specifies if api success and failure actions should be generated.",
"aliases": ["a"]
}
},
"required": []
Expand Down
14 changes: 11 additions & 3 deletions modules/schematics/src/action/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,37 @@ export interface Schema {
/**
* The name of the component.
*/

name: string;

/**
* The path to create the component.
*/

path?: string;

/**
* The name of the project.
*/
project?: string;

/**
* Specifies if a spec file is generated.
*/
spec?: boolean;

/**
* Flag to indicate if a dir is created.
*/

flat?: boolean;

/**
* Group actions file within 'actions' folder
*/

group?: boolean;

/**
* Specifies if api success and failure actions
* should be generated.
*/
api?: boolean;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
import { Injectable } from '@angular/core';
import { Actions, Effect<% if (feature) { %>, ofType<% } %> } from '@ngrx/effects';
<% if (feature) { %>import { <%= classify(name) %>ActionTypes } from '<%= featurePath(group, flat, "actions", dasherize(name)) %><%= dasherize(name) %>.actions';<% } %>
<% if (feature && api) { %>import { catchError, map, concatMap } from 'rxjs/operators';<% } %>
<% if (feature && api) { %>import { EMPTY, of } from 'rxjs';<% } %>
<% if (feature && api) { %>import { Load<%= classify(name) %>sFailure, Load<%= classify(name) %>sSuccess, <%= classify(name) %>ActionTypes, <%= classify(name) %>Actions } from '<%= featurePath(group, flat, "actions", dasherize(name)) %><%= dasherize(name) %>.actions';<% } %>
<% if (feature && !api) { %>import { <%= classify(name) %>ActionTypes } from '<%= featurePath(group, flat, "actions", dasherize(name)) %><%= dasherize(name) %>.actions';<% } %>

@Injectable()
export class <%= classify(name) %>Effects {
<% if (feature) { %>
<% if (feature && api) { %>
@Effect()
load<%= classify(name) %>s$ = this.actions$.pipe(
ofType(<%= classify(name) %>ActionTypes.Load<%= classify(name) %>s),
concatMap(() =>
/** An EMPTY observable only emits completion. Replace with your own observable API request */
EMPTY.pipe(
map(data => new Load<%= classify(name) %>sSuccess({ data })),
catchError(error => of(new Load<%= classify(name) %>sFailure({ error }))))
)
);
<% } %>
<% if (feature && !api) { %>
@Effect()
load<%= classify(name) %>s$ = this.actions$.pipe(ofType(<%= classify(name) %>ActionTypes.Load<%= classify(name) %>s));
<% } %>
<% if (feature && api) { %>
constructor(private actions$: Actions<<%= classify(name) %>Actions>) {}
<% } else { %>
constructor(private actions$: Actions) {}
<% } %>
}
33 changes: 33 additions & 0 deletions modules/schematics/src/effect/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,4 +277,37 @@ describe('Effect Schematic', () => {
/loadFoos\$ = this\.actions\$.pipe\(ofType\(FooActionTypes\.LoadFoos\)\);/
);
});

it('should create an api effect that describes a source of actions within a feature', () => {
const options = { ...defaultOptions, feature: true, api: true };

const tree = schematicRunner.runSchematic('effect', options, appTree);
const content = tree.readContent(
`${projectPath}/src/app/foo/foo.effects.ts`
);
expect(content).toMatch(
/import { Actions, Effect, ofType } from '@ngrx\/effects';/
);
expect(content).toMatch(
/import { catchError, map, concatMap } from 'rxjs\/operators';/
);
expect(content).toMatch(/import { EMPTY, of } from 'rxjs';/);
expect(content).toMatch(
/import { LoadFoosFailure, LoadFoosSuccess, FooActionTypes, FooActions } from '\.\/foo.actions';/
);

expect(content).toMatch(/export class FooEffects/);
expect(content).toMatch(/loadFoos\$ = this\.actions\$.pipe\(/);
expect(content).toMatch(/ofType\(FooActionTypes\.LoadFoos\),/);
expect(content).toMatch(/concatMap\(\(\) =>/);
expect(content).toMatch(/EMPTY\.pipe\(/);
expect(content).toMatch(/map\(data => new LoadFoosSuccess\({ data }\)\),/);
expect(content).toMatch(
/catchError\(error => of\(new LoadFoosFailure\({ error }\)\)\)\)/
);

expect(content).toMatch(
/constructor\(private actions\$: Actions<FooActions>\) {}/
);
});
});
7 changes: 7 additions & 0 deletions modules/schematics/src/effect/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@
"default": false,
"description": "Group effects file within 'effects' folder",
"aliases": ["g"]
},
"api": {
"type": "boolean",
"default": false,
"description":
"Specifies if effect has api success and failure actions wired up",
"aliases": ["a"]
}
},
"required": []
Expand Down
16 changes: 13 additions & 3 deletions modules/schematics/src/effect/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,50 @@ export interface Schema {
/**
* The name of the component.
*/

name: string;

/**
* The path to create the effect.
*/

path?: string;

/**
* The name of the project.
*/
project?: string;

/**
* Flag to indicate if a dir is created.
*/
flat?: boolean;

/**
* Specifies if a spec file is generated.
*/
spec?: boolean;

/**
* Allows specification of the declaring module.
*/
module?: string;

/**
* Specifies if this is a root-level effect
*/
root?: boolean;

/**
* Specifies if this is grouped within a feature
*/
feature?: boolean;

/**
* Specifies if this is grouped within an 'effects' folder
*/

group?: boolean;

/**
* Specifies if effect has api success and failure actions wired up
*/
api?: boolean;
}
66 changes: 66 additions & 0 deletions modules/schematics/src/feature/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,70 @@ describe('Feature Schematic', () => {
/import \* as fromFoo from '\.\/foo\/reducers\/foo.reducer';/
);
});

it('should have all three api actions in actions type union if api flag enabled', () => {
const options = {
...defaultOptions,
api: true,
};

const tree = schematicRunner.runSchematic('feature', options, appTree);
const fileContent = tree.readContent(
`${projectPath}/src/app/foo.actions.ts`
);

expect(fileContent).toMatch(
/export type FooActions = LoadFoos \| LoadFoosSuccess \| LoadFoosFailure/
);
});

it('should have all api effect if api flag enabled', () => {
const options = {
...defaultOptions,
api: true,
};

const tree = schematicRunner.runSchematic('feature', options, appTree);
const fileContent = tree.readContent(
`${projectPath}/src/app/foo.effects.ts`
);

expect(fileContent).toMatch(
/import { Actions, Effect, ofType } from '@ngrx\/effects';/
);
expect(fileContent).toMatch(
/import { catchError, map, concatMap } from 'rxjs\/operators';/
);
expect(fileContent).toMatch(/import { EMPTY, of } from 'rxjs';/);
expect(fileContent).toMatch(
/import { LoadFoosFailure, LoadFoosSuccess, FooActionTypes, FooActions } from '\.\/foo.actions';/
);

expect(fileContent).toMatch(/export class FooEffects/);
expect(fileContent).toMatch(/loadFoos\$ = this\.actions\$.pipe\(/);
expect(fileContent).toMatch(/ofType\(FooActionTypes\.LoadFoos\),/);
expect(fileContent).toMatch(/concatMap\(\(\) =>/);
expect(fileContent).toMatch(/EMPTY\.pipe\(/);
expect(fileContent).toMatch(
/map\(data => new LoadFoosSuccess\({ data }\)\),/
);
expect(fileContent).toMatch(
/catchError\(error => of\(new LoadFoosFailure\({ error }\)\)\)\)/
);
});

it('should have all api actions in reducer if api flag enabled', () => {
const options = {
...defaultOptions,
api: true,
};

const tree = schematicRunner.runSchematic('feature', options, appTree);
const fileContent = tree.readContent(
`${projectPath}/src/app/foo.reducer.ts`
);

expect(fileContent).toMatch(/case FooActionTypes\.LoadFoosSuccess/);
expect(fileContent).toMatch(/case FooActionTypes\.LoadFoosFailure/);
});
});
3 changes: 3 additions & 0 deletions modules/schematics/src/feature/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default function(options: FeatureOptions): Rule {
path: options.path,
project: options.project,
spec: false,
api: options.api,
}),
schematic('reducer', {
flat: options.flat,
Expand All @@ -28,6 +29,7 @@ export default function(options: FeatureOptions): Rule {
spec: options.spec,
reducers: options.reducers,
feature: true,
api: options.api,
}),
schematic('effect', {
flat: options.flat,
Expand All @@ -38,6 +40,7 @@ export default function(options: FeatureOptions): Rule {
project: options.project,
spec: options.spec,
feature: true,
api: options.api,
}),
])(host, context);
};
Expand Down
7 changes: 7 additions & 0 deletions modules/schematics/src/feature/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@
"description":
"Group actions, reducers and effects within relative subfolders",
"aliases": ["g"]
},
"api": {
"type": "boolean",
"default": false,
"description":
"Specifies if api success and failure actions, reducer, and effects should be generated as part of this feature.",
"aliases": ["a"]
}
},
"required": []
Expand Down
Loading

0 comments on commit e17a787

Please sign in to comment.