diff --git a/modules/schematics/src/action/files/__name@dasherize@if-flat__/__name@dasherize__.actions.ts b/modules/schematics/src/action/files/__name@dasherize@if-flat__/__name@dasherize__.actions.ts index c633e3eb3a..98cd8c8a93 100644 --- a/modules/schematics/src/action/files/__name@dasherize@if-flat__/__name@dasherize__.actions.ts +++ b/modules/schematics/src/action/files/__name@dasherize@if-flat__/__name@dasherize__.actions.ts @@ -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;<% } %> diff --git a/modules/schematics/src/action/index.spec.ts b/modules/schematics/src/action/index.spec.ts index f44c364741..9a780d257b 100644 --- a/modules/schematics/src/action/index.spec.ts +++ b/modules/schematics/src/action/index.spec.ts @@ -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/ + ); + }); }); diff --git a/modules/schematics/src/action/schema.json b/modules/schematics/src/action/schema.json index 1912889439..5ea9693891 100644 --- a/modules/schematics/src/action/schema.json +++ b/modules/schematics/src/action/schema.json @@ -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": [] diff --git a/modules/schematics/src/action/schema.ts b/modules/schematics/src/action/schema.ts index fe74a3706b..735e90d43e 100644 --- a/modules/schematics/src/action/schema.ts +++ b/modules/schematics/src/action/schema.ts @@ -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; } diff --git a/modules/schematics/src/effect/files/__name@dasherize@if-flat__/__name@dasherize__.effects.ts b/modules/schematics/src/effect/files/__name@dasherize@if-flat__/__name@dasherize__.effects.ts index e6f6ecdb98..5d9d9e84e2 100644 --- a/modules/schematics/src/effect/files/__name@dasherize@if-flat__/__name@dasherize__.effects.ts +++ b/modules/schematics/src/effect/files/__name@dasherize@if-flat__/__name@dasherize__.effects.ts @@ -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) {} +<% } %> } diff --git a/modules/schematics/src/effect/index.spec.ts b/modules/schematics/src/effect/index.spec.ts index 16a2276c9f..76364a6fd2 100644 --- a/modules/schematics/src/effect/index.spec.ts +++ b/modules/schematics/src/effect/index.spec.ts @@ -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\) {}/ + ); + }); }); diff --git a/modules/schematics/src/effect/schema.json b/modules/schematics/src/effect/schema.json index 217de2f781..7d29ab270d 100644 --- a/modules/schematics/src/effect/schema.json +++ b/modules/schematics/src/effect/schema.json @@ -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": [] diff --git a/modules/schematics/src/effect/schema.ts b/modules/schematics/src/effect/schema.ts index 611f357079..96b82a8e1b 100644 --- a/modules/schematics/src/effect/schema.ts +++ b/modules/schematics/src/effect/schema.ts @@ -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; } diff --git a/modules/schematics/src/feature/index.spec.ts b/modules/schematics/src/feature/index.spec.ts index 24d0caf52d..718db9e25a 100644 --- a/modules/schematics/src/feature/index.spec.ts +++ b/modules/schematics/src/feature/index.spec.ts @@ -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/); + }); }); diff --git a/modules/schematics/src/feature/index.ts b/modules/schematics/src/feature/index.ts index a9b5abe1ac..cbde98c368 100644 --- a/modules/schematics/src/feature/index.ts +++ b/modules/schematics/src/feature/index.ts @@ -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, @@ -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, @@ -38,6 +40,7 @@ export default function(options: FeatureOptions): Rule { project: options.project, spec: options.spec, feature: true, + api: options.api, }), ])(host, context); }; diff --git a/modules/schematics/src/feature/schema.json b/modules/schematics/src/feature/schema.json index 454398c537..f39099fbec 100644 --- a/modules/schematics/src/feature/schema.json +++ b/modules/schematics/src/feature/schema.json @@ -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": [] diff --git a/modules/schematics/src/feature/schema.ts b/modules/schematics/src/feature/schema.ts index e88bc1adf9..08ac475cb2 100644 --- a/modules/schematics/src/feature/schema.ts +++ b/modules/schematics/src/feature/schema.ts @@ -3,38 +3,45 @@ export interface Schema { * The name of the feature. */ name: string; + /** * The path to create the feature. */ 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; + /** * Allows specification of the declaring reducers. */ - reducers?: string; + /** * Specifies if this is grouped within sub folders */ - group?: boolean; + /** - * Specifies if this is grouped within a feature + * Specifies if api success and failure actions, reducer, and effects + * should be generated as part of this feature. */ + api?: boolean; } diff --git a/modules/schematics/src/reducer/files/__name@dasherize@if-flat__/__name@dasherize__.reducer.ts b/modules/schematics/src/reducer/files/__name@dasherize@if-flat__/__name@dasherize__.reducer.ts index d04b51cb80..7901446675 100644 --- a/modules/schematics/src/reducer/files/__name@dasherize@if-flat__/__name@dasherize__.reducer.ts +++ b/modules/schematics/src/reducer/files/__name@dasherize@if-flat__/__name@dasherize__.reducer.ts @@ -1,4 +1,4 @@ -import { Action } from '@ngrx/store'; +<% if(!feature) { %>import { Action } from '@ngrx/store';<% } %> <% if(feature) { %>import { <%= classify(name) %>Actions, <%= classify(name) %>ActionTypes } from '<%= featurePath(group, flat, "actions", dasherize(name)) %><%= dasherize(name) %>.actions';<% } %> export interface State { @@ -14,8 +14,13 @@ export function reducer(state = initialState, action: <% if(feature) { %><%= cla <% if(feature) { %> case <%= classify(name) %>ActionTypes.Load<%= classify(name) %>s: return state; +<% if(api) { %> + case <%= classify(name) %>ActionTypes.Load<%= classify(name) %>sSuccess: + return state; -<% } %> + case <%= classify(name) %>ActionTypes.Load<%= classify(name) %>sFailure: + return state; +<% } %><% } %> default: return state; } diff --git a/modules/schematics/src/reducer/index.spec.ts b/modules/schematics/src/reducer/index.spec.ts index f97c1aff88..f060c579d3 100644 --- a/modules/schematics/src/reducer/index.spec.ts +++ b/modules/schematics/src/reducer/index.spec.ts @@ -169,4 +169,23 @@ describe('Reducer Schematic', () => { /import\ \{\ FooActions,\ FooActionTypes\ }\ from\ \'\.\.\/\.\.\/actions\/foo\/foo\.actions';/ ); }); + + it('should create an reducer function with api success and failure, given feature and api', () => { + const tree = schematicRunner.runSchematic( + 'reducer', + { + ...defaultOptions, + feature: true, + api: true, + }, + appTree + ); + const fileContent = tree.readContent( + `${projectPath}/src/app/foo.reducer.ts` + ); + + expect(fileContent).toMatch(/case FooActionTypes\.LoadFoosSuccess/); + expect(fileContent).toMatch(/case FooActionTypes\.LoadFoosFailure/); + expect(fileContent).not.toMatch(/import { Action } from '@ngrx\/store'/); + }); }); diff --git a/modules/schematics/src/reducer/schema.json b/modules/schematics/src/reducer/schema.json index 78acb8bd0d..57a11e229e 100644 --- a/modules/schematics/src/reducer/schema.json +++ b/modules/schematics/src/reducer/schema.json @@ -53,6 +53,13 @@ "default": false, "description": "Group reducer file within 'reducers' folder", "aliases": ["g"] + }, + "api": { + "type": "boolean", + "default": false, + "description": + "Specifies if api success and failure actions should be added to the reducer", + "aliases": ["a"] } }, "required": [] diff --git a/modules/schematics/src/reducer/schema.ts b/modules/schematics/src/reducer/schema.ts index f3d3087f3e..32b1be51e7 100644 --- a/modules/schematics/src/reducer/schema.ts +++ b/modules/schematics/src/reducer/schema.ts @@ -3,41 +3,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; + /** * Allows specification of the declaring reducers. */ - reducers?: string; + /** * Specifies if this is grouped within sub folders */ - group?: boolean; + /** * Specifies if this is grouped within a feature */ - feature?: boolean; + + /** + * Specifies if api success and failure actions + * should be added to the reducer. + */ + api?: boolean; }