Skip to content

Commit e17a787

Browse files
wesleygrimesbrandonroberts
authored andcommitted
feat(schematics): add api success/failure effects/actions to ng generate feature (#1530)
1 parent 48a2370 commit e17a787

File tree

16 files changed

+295
-21
lines changed

16 files changed

+295
-21
lines changed
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
import { Action } from '@ngrx/store';
22

33
export enum <%= classify(name) %>ActionTypes {
4-
Load<%= classify(name) %>s = '[<%= classify(name) %>] Load <%= classify(name) %>s'
4+
Load<%= classify(name) %>s = '[<%= classify(name) %>] Load <%= classify(name) %>s',
5+
<% if (api) { %>Load<%= classify(name) %>sSuccess = '[<%= classify(name) %>] Load <%= classify(name) %>s Success',<% } %>
6+
<% if (api) { %>Load<%= classify(name) %>sFailure = '[<%= classify(name) %>] Load <%= classify(name) %>s Failure',<% } %>
57
}
68

79
export class Load<%= classify(name) %>s implements Action {
810
readonly type = <%= classify(name) %>ActionTypes.Load<%= classify(name) %>s;
911
}
12+
<% if (api) { %>
13+
export class Load<%= classify(name) %>sSuccess implements Action {
14+
readonly type = <%= classify(name) %>ActionTypes.Load<%= classify(name) %>sSuccess;
15+
constructor(public payload: { data: any }) { }
16+
}
1017

11-
export type <%= classify(name) %>Actions = Load<%= classify(name) %>s;
18+
export class Load<%= classify(name) %>sFailure implements Action {
19+
readonly type = <%= classify(name) %>ActionTypes.Load<%= classify(name) %>sFailure;
20+
constructor(public payload: { error: any }) { }
21+
}
22+
<% } %>
23+
<% if (api) { %>export type <%= classify(name) %>Actions = Load<%= classify(name) %>s | Load<%= classify(name) %>sSuccess | Load<%= classify(name) %>sFailure;<% } %>
24+
<% if (!api) { %>export type <%= classify(name) %>Actions = Load<%= classify(name) %>s;<% } %>

modules/schematics/src/action/index.spec.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,58 @@ describe('Action Schematic', () => {
127127
tree.files.indexOf(`${projectPath}/src/app/actions/foo.actions.ts`)
128128
).toBeGreaterThanOrEqual(0);
129129
});
130+
131+
it('should create a success class based on the provided name, given api', () => {
132+
const tree = schematicRunner.runSchematic(
133+
'action',
134+
{
135+
...defaultOptions,
136+
api: true,
137+
},
138+
appTree
139+
);
140+
const fileContent = tree.readContent(
141+
`${projectPath}/src/app/foo.actions.ts`
142+
);
143+
144+
expect(fileContent).toMatch(
145+
/export class LoadFoosSuccess implements Action/
146+
);
147+
});
148+
149+
it('should create a failure class based on the provided name, given api', () => {
150+
const tree = schematicRunner.runSchematic(
151+
'action',
152+
{
153+
...defaultOptions,
154+
api: true,
155+
},
156+
appTree
157+
);
158+
const fileContent = tree.readContent(
159+
`${projectPath}/src/app/foo.actions.ts`
160+
);
161+
162+
expect(fileContent).toMatch(
163+
/export class LoadFoosFailure implements Action/
164+
);
165+
});
166+
167+
it('should create the union type with success and failure based on the provided name, given api', () => {
168+
const tree = schematicRunner.runSchematic(
169+
'action',
170+
{
171+
...defaultOptions,
172+
api: true,
173+
},
174+
appTree
175+
);
176+
const fileContent = tree.readContent(
177+
`${projectPath}/src/app/foo.actions.ts`
178+
);
179+
180+
expect(fileContent).toMatch(
181+
/export type FooActions = LoadFoos \| LoadFoosSuccess \| LoadFoosFailure/
182+
);
183+
});
130184
});

modules/schematics/src/action/schema.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@
3838
"default": false,
3939
"description": "Group actions file within 'actions' folder",
4040
"aliases": ["g"]
41+
},
42+
"api": {
43+
"type": "boolean",
44+
"default": false,
45+
"description":
46+
"Specifies if api success and failure actions should be generated.",
47+
"aliases": ["a"]
4148
}
4249
},
4350
"required": []

modules/schematics/src/action/schema.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,37 @@ export interface Schema {
22
/**
33
* The name of the component.
44
*/
5-
65
name: string;
6+
77
/**
88
* The path to create the component.
99
*/
10-
1110
path?: string;
11+
1212
/**
1313
* The name of the project.
1414
*/
1515
project?: string;
16+
1617
/**
1718
* Specifies if a spec file is generated.
1819
*/
1920
spec?: boolean;
21+
2022
/**
2123
* Flag to indicate if a dir is created.
2224
*/
2325

2426
flat?: boolean;
27+
2528
/**
2629
* Group actions file within 'actions' folder
2730
*/
28-
2931
group?: boolean;
32+
33+
/**
34+
* Specifies if api success and failure actions
35+
* should be generated.
36+
*/
37+
api?: boolean;
3038
}
Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,31 @@
11
import { Injectable } from '@angular/core';
22
import { Actions, Effect<% if (feature) { %>, ofType<% } %> } from '@ngrx/effects';
3-
<% if (feature) { %>import { <%= classify(name) %>ActionTypes } from '<%= featurePath(group, flat, "actions", dasherize(name)) %><%= dasherize(name) %>.actions';<% } %>
3+
<% if (feature && api) { %>import { catchError, map, concatMap } from 'rxjs/operators';<% } %>
4+
<% if (feature && api) { %>import { EMPTY, of } from 'rxjs';<% } %>
5+
<% 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';<% } %>
6+
<% if (feature && !api) { %>import { <%= classify(name) %>ActionTypes } from '<%= featurePath(group, flat, "actions", dasherize(name)) %><%= dasherize(name) %>.actions';<% } %>
47

58
@Injectable()
69
export class <%= classify(name) %>Effects {
7-
<% if (feature) { %>
10+
<% if (feature && api) { %>
11+
@Effect()
12+
load<%= classify(name) %>s$ = this.actions$.pipe(
13+
ofType(<%= classify(name) %>ActionTypes.Load<%= classify(name) %>s),
14+
concatMap(() =>
15+
/** An EMPTY observable only emits completion. Replace with your own observable API request */
16+
EMPTY.pipe(
17+
map(data => new Load<%= classify(name) %>sSuccess({ data })),
18+
catchError(error => of(new Load<%= classify(name) %>sFailure({ error }))))
19+
)
20+
);
21+
<% } %>
22+
<% if (feature && !api) { %>
823
@Effect()
924
load<%= classify(name) %>s$ = this.actions$.pipe(ofType(<%= classify(name) %>ActionTypes.Load<%= classify(name) %>s));
1025
<% } %>
26+
<% if (feature && api) { %>
27+
constructor(private actions$: Actions<<%= classify(name) %>Actions>) {}
28+
<% } else { %>
1129
constructor(private actions$: Actions) {}
30+
<% } %>
1231
}

modules/schematics/src/effect/index.spec.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,4 +277,37 @@ describe('Effect Schematic', () => {
277277
/loadFoos\$ = this\.actions\$.pipe\(ofType\(FooActionTypes\.LoadFoos\)\);/
278278
);
279279
});
280+
281+
it('should create an api effect that describes a source of actions within a feature', () => {
282+
const options = { ...defaultOptions, feature: true, api: true };
283+
284+
const tree = schematicRunner.runSchematic('effect', options, appTree);
285+
const content = tree.readContent(
286+
`${projectPath}/src/app/foo/foo.effects.ts`
287+
);
288+
expect(content).toMatch(
289+
/import { Actions, Effect, ofType } from '@ngrx\/effects';/
290+
);
291+
expect(content).toMatch(
292+
/import { catchError, map, concatMap } from 'rxjs\/operators';/
293+
);
294+
expect(content).toMatch(/import { EMPTY, of } from 'rxjs';/);
295+
expect(content).toMatch(
296+
/import { LoadFoosFailure, LoadFoosSuccess, FooActionTypes, FooActions } from '\.\/foo.actions';/
297+
);
298+
299+
expect(content).toMatch(/export class FooEffects/);
300+
expect(content).toMatch(/loadFoos\$ = this\.actions\$.pipe\(/);
301+
expect(content).toMatch(/ofType\(FooActionTypes\.LoadFoos\),/);
302+
expect(content).toMatch(/concatMap\(\(\) =>/);
303+
expect(content).toMatch(/EMPTY\.pipe\(/);
304+
expect(content).toMatch(/map\(data => new LoadFoosSuccess\({ data }\)\),/);
305+
expect(content).toMatch(
306+
/catchError\(error => of\(new LoadFoosFailure\({ error }\)\)\)\)/
307+
);
308+
309+
expect(content).toMatch(
310+
/constructor\(private actions\$: Actions<FooActions>\) {}/
311+
);
312+
});
280313
});

modules/schematics/src/effect/schema.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@
5656
"default": false,
5757
"description": "Group effects file within 'effects' folder",
5858
"aliases": ["g"]
59+
},
60+
"api": {
61+
"type": "boolean",
62+
"default": false,
63+
"description":
64+
"Specifies if effect has api success and failure actions wired up",
65+
"aliases": ["a"]
5966
}
6067
},
6168
"required": []

modules/schematics/src/effect/schema.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,50 @@ export interface Schema {
22
/**
33
* The name of the component.
44
*/
5-
65
name: string;
6+
77
/**
88
* The path to create the effect.
99
*/
10-
1110
path?: string;
11+
1212
/**
1313
* The name of the project.
1414
*/
1515
project?: string;
16+
1617
/**
1718
* Flag to indicate if a dir is created.
1819
*/
1920
flat?: boolean;
21+
2022
/**
2123
* Specifies if a spec file is generated.
2224
*/
2325
spec?: boolean;
26+
2427
/**
2528
* Allows specification of the declaring module.
2629
*/
2730
module?: string;
31+
2832
/**
2933
* Specifies if this is a root-level effect
3034
*/
3135
root?: boolean;
36+
3237
/**
3338
* Specifies if this is grouped within a feature
3439
*/
3540
feature?: boolean;
41+
3642
/**
3743
* Specifies if this is grouped within an 'effects' folder
3844
*/
39-
4045
group?: boolean;
46+
47+
/**
48+
* Specifies if effect has api success and failure actions wired up
49+
*/
50+
api?: boolean;
4151
}

modules/schematics/src/feature/index.spec.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,70 @@ describe('Feature Schematic', () => {
126126
/import \* as fromFoo from '\.\/foo\/reducers\/foo.reducer';/
127127
);
128128
});
129+
130+
it('should have all three api actions in actions type union if api flag enabled', () => {
131+
const options = {
132+
...defaultOptions,
133+
api: true,
134+
};
135+
136+
const tree = schematicRunner.runSchematic('feature', options, appTree);
137+
const fileContent = tree.readContent(
138+
`${projectPath}/src/app/foo.actions.ts`
139+
);
140+
141+
expect(fileContent).toMatch(
142+
/export type FooActions = LoadFoos \| LoadFoosSuccess \| LoadFoosFailure/
143+
);
144+
});
145+
146+
it('should have all api effect if api flag enabled', () => {
147+
const options = {
148+
...defaultOptions,
149+
api: true,
150+
};
151+
152+
const tree = schematicRunner.runSchematic('feature', options, appTree);
153+
const fileContent = tree.readContent(
154+
`${projectPath}/src/app/foo.effects.ts`
155+
);
156+
157+
expect(fileContent).toMatch(
158+
/import { Actions, Effect, ofType } from '@ngrx\/effects';/
159+
);
160+
expect(fileContent).toMatch(
161+
/import { catchError, map, concatMap } from 'rxjs\/operators';/
162+
);
163+
expect(fileContent).toMatch(/import { EMPTY, of } from 'rxjs';/);
164+
expect(fileContent).toMatch(
165+
/import { LoadFoosFailure, LoadFoosSuccess, FooActionTypes, FooActions } from '\.\/foo.actions';/
166+
);
167+
168+
expect(fileContent).toMatch(/export class FooEffects/);
169+
expect(fileContent).toMatch(/loadFoos\$ = this\.actions\$.pipe\(/);
170+
expect(fileContent).toMatch(/ofType\(FooActionTypes\.LoadFoos\),/);
171+
expect(fileContent).toMatch(/concatMap\(\(\) =>/);
172+
expect(fileContent).toMatch(/EMPTY\.pipe\(/);
173+
expect(fileContent).toMatch(
174+
/map\(data => new LoadFoosSuccess\({ data }\)\),/
175+
);
176+
expect(fileContent).toMatch(
177+
/catchError\(error => of\(new LoadFoosFailure\({ error }\)\)\)\)/
178+
);
179+
});
180+
181+
it('should have all api actions in reducer if api flag enabled', () => {
182+
const options = {
183+
...defaultOptions,
184+
api: true,
185+
};
186+
187+
const tree = schematicRunner.runSchematic('feature', options, appTree);
188+
const fileContent = tree.readContent(
189+
`${projectPath}/src/app/foo.reducer.ts`
190+
);
191+
192+
expect(fileContent).toMatch(/case FooActionTypes\.LoadFoosSuccess/);
193+
expect(fileContent).toMatch(/case FooActionTypes\.LoadFoosFailure/);
194+
});
129195
});

modules/schematics/src/feature/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export default function(options: FeatureOptions): Rule {
1717
path: options.path,
1818
project: options.project,
1919
spec: false,
20+
api: options.api,
2021
}),
2122
schematic('reducer', {
2223
flat: options.flat,
@@ -28,6 +29,7 @@ export default function(options: FeatureOptions): Rule {
2829
spec: options.spec,
2930
reducers: options.reducers,
3031
feature: true,
32+
api: options.api,
3133
}),
3234
schematic('effect', {
3335
flat: options.flat,
@@ -38,6 +40,7 @@ export default function(options: FeatureOptions): Rule {
3840
project: options.project,
3941
spec: options.spec,
4042
feature: true,
43+
api: options.api,
4144
}),
4245
])(host, context);
4346
};

0 commit comments

Comments
 (0)