diff --git a/modules/schematics/BUILD b/modules/schematics/BUILD index 05625f59f0..f8a25e75a8 100644 --- a/modules/schematics/BUILD +++ b/modules/schematics/BUILD @@ -30,6 +30,8 @@ npm_package( ":README.md", ] + glob([ "**/src/*/files/**/*", + "**/src/*/common-files/**/*", + "**/src/*/creator-files/**/*", "**/schema.json", "**/migration.json", ]), @@ -51,6 +53,8 @@ ts_test_library( ), data = glob([ "**/src/*/files/**/*", + "**/src/*/common-files/**/*", + "**/src/*/creator-files/**/*", "**/*.json", ]), deps = [ diff --git a/modules/schematics/src/action/creator-files/__name@dasherize@if-flat__/__name@dasherize__.actions.spec.ts.template b/modules/schematics/src/action/creator-files/__name@dasherize@if-flat__/__name@dasherize__.actions.spec.ts.template new file mode 100644 index 0000000000..54ee181146 --- /dev/null +++ b/modules/schematics/src/action/creator-files/__name@dasherize@if-flat__/__name@dasherize__.actions.spec.ts.template @@ -0,0 +1,7 @@ +import * as from<%= classify(name) %> from './<%= dasherize(name) %>.actions'; + +describe('load<%= classify(name) %>s', () => { + it('should return an action', () => { + expect(from<%= classify(name) %>.load<%= classify(name) %>s().type).toBe('[<%= classify(name) %>] Load <%= classify(name) %>s'); + }); +}); diff --git a/modules/schematics/src/action/creator-files/__name@dasherize@if-flat__/__name@dasherize__.actions.ts.template b/modules/schematics/src/action/creator-files/__name@dasherize@if-flat__/__name@dasherize__.actions.ts.template new file mode 100644 index 0000000000..399ba7a7bd --- /dev/null +++ b/modules/schematics/src/action/creator-files/__name@dasherize@if-flat__/__name@dasherize__.actions.ts.template @@ -0,0 +1,14 @@ +import { createAction, union } from '@ngrx/store'; + +export const load<%= classify(name) %>s = createAction('[<%= classify(name) %>] Load <%= classify(name) %>s'); +<% if (api) { %>export const load<%= classify(name) %>sSuccess = createAction('[<%= classify(name) %>] Load <%= classify(name) %>s Success');<% } %> +<% if (api) { %>export const load<%= classify(name) %>sFailure = createAction('[<%= classify(name) %>] Load <%= classify(name) %>s Failure');<% } %> + +const all = union({ + load<%= classify(name) %>s, +<% if (api) { %> + load<%= classify(name) %>sSuccess, + load<%= classify(name) %>sFailure +<% } %> +}); +export Union = typeof all; diff --git a/modules/schematics/src/action/index.spec.ts b/modules/schematics/src/action/index.spec.ts index 9a780d257b..bb7e0f736a 100644 --- a/modules/schematics/src/action/index.spec.ts +++ b/modules/schematics/src/action/index.spec.ts @@ -75,43 +75,64 @@ describe('Action Schematic', () => { ).toBeGreaterThanOrEqual(0); }); - it('should create an enum named "Foo"', () => { - const tree = schematicRunner.runSchematic( - 'action', - defaultOptions, - appTree - ); - const fileContent = tree.readContent( - `${projectPath}/src/app/foo.actions.ts` - ); - - expect(fileContent).toMatch(/export enum FooActionTypes/); - }); + describe('action classes', () => { + it('should create an enum named "Foo"', () => { + const tree = schematicRunner.runSchematic( + 'action', + defaultOptions, + appTree + ); + const fileContent = tree.readContent( + `${projectPath}/src/app/foo.actions.ts` + ); + + expect(fileContent).toMatch(/export enum FooActionTypes/); + }); - it('should create a class based on the provided name', () => { - const tree = schematicRunner.runSchematic( - 'action', - defaultOptions, - appTree - ); - const fileContent = tree.readContent( - `${projectPath}/src/app/foo.actions.ts` - ); + it('should create a class based on the provided name', () => { + const tree = schematicRunner.runSchematic( + 'action', + defaultOptions, + appTree + ); + const fileContent = tree.readContent( + `${projectPath}/src/app/foo.actions.ts` + ); + + expect(fileContent).toMatch(/export class LoadFoos implements Action/); + }); - expect(fileContent).toMatch(/export class LoadFoos implements Action/); + it('should create the union type based on the provided name', () => { + const tree = schematicRunner.runSchematic( + 'action', + defaultOptions, + appTree + ); + const fileContent = tree.readContent( + `${projectPath}/src/app/foo.actions.ts` + ); + + expect(fileContent).toMatch(/export type FooActions = LoadFoos/); + }); }); - it('should create the union type based on the provided name', () => { - const tree = schematicRunner.runSchematic( - 'action', - defaultOptions, - appTree - ); - const fileContent = tree.readContent( - `${projectPath}/src/app/foo.actions.ts` - ); - - expect(fileContent).toMatch(/export type FooActions = LoadFoos/); + describe('action creators', () => { + const creatorOptions = { ...defaultOptions, actionCreators: true }; + + it('should create a const for the action creator', () => { + const tree = schematicRunner.runSchematic( + 'action', + creatorOptions, + appTree + ); + const fileContent = tree.readContent( + `${projectPath}/src/app/foo.actions.ts` + ); + + expect(fileContent).toMatch( + /export const loadFoos = createAction\('\[Foo\] Load Foos'\);/ + ); + }); }); it('should group within an "actions" folder if group is set', () => { diff --git a/modules/schematics/src/action/index.ts b/modules/schematics/src/action/index.ts index 58b45ad5eb..2dc24fe074 100644 --- a/modules/schematics/src/action/index.ts +++ b/modules/schematics/src/action/index.ts @@ -29,21 +29,24 @@ export default function(options: ActionOptions): Rule { options.name = parsedPath.name; options.path = parsedPath.path; - const templateSource = apply(url('./files'), [ - options.spec - ? noop() - : filter(path => !path.endsWith('.spec.ts.template')), - applyTemplates({ - ...stringUtils, - 'if-flat': (s: string) => - stringUtils.group( - options.flat ? '' : s, - options.group ? 'actions' : '' - ), - ...options, - }), - move(parsedPath.path), - ]); + const templateSource = apply( + url(options.actionCreators ? './creator-files' : './files'), + [ + options.spec + ? noop() + : filter(path => !path.endsWith('.spec.ts.template')), + applyTemplates({ + ...stringUtils, + 'if-flat': (s: string) => + stringUtils.group( + options.flat ? '' : s, + options.group ? 'actions' : '' + ), + ...options, + }), + move(parsedPath.path), + ] + ); return chain([branchAndMerge(chain([mergeWith(templateSource)]))])( host, diff --git a/modules/schematics/src/action/schema.json b/modules/schematics/src/action/schema.json index 5ea9693891..645105ad10 100644 --- a/modules/schematics/src/action/schema.json +++ b/modules/schematics/src/action/schema.json @@ -45,6 +45,13 @@ "description": "Specifies if api success and failure actions should be generated.", "aliases": ["a"] + }, + "actionCreators": { + "type": "boolean", + "default": false, + "description": + "Specifies whether to generate action creators instead of action classes.", + "aliases": ["ac"] } }, "required": [] diff --git a/modules/schematics/src/action/schema.ts b/modules/schematics/src/action/schema.ts index 735e90d43e..44aed0ec40 100644 --- a/modules/schematics/src/action/schema.ts +++ b/modules/schematics/src/action/schema.ts @@ -35,4 +35,10 @@ export interface Schema { * should be generated. */ api?: boolean; + + /** + * Specifies whether to generate action creators + * instead of action classes. + */ + actionCreators?: boolean; } diff --git a/modules/schematics/src/effect/files/__name@dasherize@if-flat__/__name@dasherize__.effects.spec.ts.template b/modules/schematics/src/effect/files/__name@dasherize@if-flat__/__name@dasherize__.effects.spec.ts.template index 881d8e3276..0bebf51008 100644 --- a/modules/schematics/src/effect/files/__name@dasherize@if-flat__/__name@dasherize__.effects.spec.ts.template +++ b/modules/schematics/src/effect/files/__name@dasherize@if-flat__/__name@dasherize__.effects.spec.ts.template @@ -1,4 +1,4 @@ -import { TestBed, inject } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; import { provideMockActions } from '@ngrx/effects/testing'; import { Observable } from 'rxjs'; diff --git a/modules/schematics/src/effect/files/__name@dasherize@if-flat__/__name@dasherize__.effects.ts.template b/modules/schematics/src/effect/files/__name@dasherize@if-flat__/__name@dasherize__.effects.ts.template index 3214983e36..d0ecf1376c 100644 --- a/modules/schematics/src/effect/files/__name@dasherize@if-flat__/__name@dasherize__.effects.ts.template +++ b/modules/schematics/src/effect/files/__name@dasherize@if-flat__/__name@dasherize__.effects.ts.template @@ -2,16 +2,18 @@ import { Injectable } from '@angular/core'; import { Actions, <%= effectMethod %><% if (feature) { %>, ofType<% } %> } from '@ngrx/effects'; <% if (feature && api) { %>import { catchError, map, concatMap } from 'rxjs/operators'; import { EMPTY, of } from 'rxjs'; -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 (!effectCreators) {%>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 (effectCreators) {%>import * as <%= classify(name) %>Actions from '<%= featurePath(group, flat, "actions", dasherize(name)) %><%= dasherize(name) %>.actions';<% } %> <% } %> <% if (feature && !api) { %>import { concatMap } from 'rxjs/operators'; import { EMPTY } from 'rxjs'; -import { <%= classify(name) %>ActionTypes, <%= classify(name) %>Actions } from '<%= featurePath(group, flat, "actions", dasherize(name)) %><%= dasherize(name) %>.actions'; +<% if (!effectCreators) {%>import { <%= classify(name) %>ActionTypes, <%= classify(name) %>Actions } from '<%= featurePath(group, flat, "actions", dasherize(name)) %><%= dasherize(name) %>.actions';<% } %> +<% if (effectCreators) {%>import * as <%= classify(name) %>Actions from '<%= featurePath(group, flat, "actions", dasherize(name)) %><%= dasherize(name) %>.actions';<% } %> <% } %> @Injectable() export class <%= classify(name) %>Effects { -<% if (feature && api) { %> +<% if (feature && api && !effectCreators) { %> <%= effectStart %> ofType(<%= classify(name) %>ActionTypes.Load<%= classify(name) %>s), concatMap(() => @@ -21,18 +23,34 @@ export class <%= classify(name) %>Effects { catchError(error => of(new Load<%= classify(name) %>sFailure({ error })))) ) <%= effectEnd %> +<% } else if (feature && api && effectCreators) { %> + <%= effectStart %> + ofType(<%= classify(name) %>Actions.load<%= classify(name) %>s), + concatMap(() => + /** An EMPTY observable only emits completion. Replace with your own observable API request */ + EMPTY.pipe( + map(data => <%= classify(name) %>Actions.load<%= classify(name) %>sSuccess({ data })), + catchError(error => of(<%= classify(name) %>Actions.load<%= classify(name) %>sFailure({ error })))) + ) + <%= effectEnd %> <% } %> - -<% if (feature && !api) { %> +<% if (feature && !api && !effectCreators) { %> <%= effectStart %> ofType(<%= classify(name) %>ActionTypes.Load<%= classify(name) %>s), /** An EMPTY observable only emits completion. Replace with your own observable API request */ concatMap(() => EMPTY) <%= effectEnd %> +<% } else if (feature && !api && effectCreators) { %> + <%= effectStart %> + ofType(<%= classify(name) %>Actions.load<%= classify(name) %>s), + /** An EMPTY observable only emits completion. Replace with your own observable API request */ + concatMap(() => EMPTY) + <%= effectEnd %> <% } %> - -<% if (feature) { %> +<% if (feature && !effectCreators) { %> constructor(private actions$: Actions<<%= classify(name) %>Actions>) {} +<% } else if (feature && effectCreators) { %> + constructor(private actions$: 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 b851bda01d..cbdda37ce7 100644 --- a/modules/schematics/src/effect/index.spec.ts +++ b/modules/schematics/src/effect/index.spec.ts @@ -334,6 +334,21 @@ describe('Effect Schematic', () => { ); }); + it('should use action creators when effectCreators is enabled in a feature', () => { + const options = { ...defaultOptions, effectCreators: true, feature: true }; + + const tree = schematicRunner.runSchematic('effect', options, appTree); + const content = tree.readContent( + `${projectPath}/src/app/foo/foo.effects.ts` + ); + + expect(content).toMatch( + /import { Actions, createEffect, ofType } from '@ngrx\/effects';/ + ); + expect(content).toMatch(/import \* as FooActions from '\.\/foo\.actions';/); + expect(content).toMatch(/ofType\(FooActions\.loadFoos\),/); + }); + it('should create an api effect using creator function', () => { const options = { ...defaultOptions, diff --git a/modules/schematics/src/entity/files/__name@dasherize@if-flat__/__name@dasherize@group-models__.model.ts.template b/modules/schematics/src/entity/common-files/__name@dasherize@if-flat__/__name@dasherize@group-models__.model.ts.template similarity index 100% rename from modules/schematics/src/entity/files/__name@dasherize@if-flat__/__name@dasherize@group-models__.model.ts.template rename to modules/schematics/src/entity/common-files/__name@dasherize@if-flat__/__name@dasherize@group-models__.model.ts.template diff --git a/modules/schematics/src/entity/files/__name@dasherize@if-flat__/__name@dasherize@group-reducers__.reducer.spec.ts.template b/modules/schematics/src/entity/common-files/__name@dasherize@if-flat__/__name@dasherize@group-reducers__.reducer.spec.ts.template similarity index 87% rename from modules/schematics/src/entity/files/__name@dasherize@if-flat__/__name@dasherize@group-reducers__.reducer.spec.ts.template rename to modules/schematics/src/entity/common-files/__name@dasherize@if-flat__/__name@dasherize@group-reducers__.reducer.spec.ts.template index e61caab1a3..02b4b3816c 100644 --- a/modules/schematics/src/entity/files/__name@dasherize@if-flat__/__name@dasherize@group-reducers__.reducer.spec.ts.template +++ b/modules/schematics/src/entity/common-files/__name@dasherize@if-flat__/__name@dasherize@group-reducers__.reducer.spec.ts.template @@ -2,7 +2,7 @@ import { reducer, initialState } from '<%= featurePath(group, flat, "reducers", describe('<%= classify(name) %> Reducer', () => { describe('unknown action', () => { - it('should return the initial state', () => { + it('should return the previous state', () => { const action = {} as any; const result = reducer(initialState, action); diff --git a/modules/schematics/src/entity/creator-files/__name@dasherize@if-flat__/__name@dasherize@group-actions__.actions.ts.template b/modules/schematics/src/entity/creator-files/__name@dasherize@if-flat__/__name@dasherize@group-actions__.actions.ts.template new file mode 100644 index 0000000000..37e114d0dd --- /dev/null +++ b/modules/schematics/src/entity/creator-files/__name@dasherize@if-flat__/__name@dasherize@group-actions__.actions.ts.template @@ -0,0 +1,29 @@ +import { createAction, props, union } from '@ngrx/store'; +import { Update } from '@ngrx/entity'; + +import { <%= classify(name) %> } from '<%= featurePath(group, flat, "models", dasherize(name)) %><%= dasherize(name) %>.model'; + +export const load<%= classify(name) %>s = createAction('[<%= classify(name) %>/API] Load <%= classify(name) %>s', props<{ <%= camelize(name) %>s: <%= classify(name) %>[] }>()); +export const add<%= classify(name) %> = createAction('[<%= classify(name) %>/API] Add <%= classify(name) %>', props<{ <%= camelize(name) %>: <%= classify(name) %> }>()); +export const upsert<%= classify(name) %> = createAction('[<%= classify(name) %>/API] Upsert <%= classify(name) %>', props<{ <%= camelize(name) %>: <%= classify(name) %> }>()); +export const add<%= classify(name) %>s = createAction('[<%= classify(name) %>/API] Add <%= classify(name) %>s', props<{ <%= camelize(name) %>: <%= classify(name) %> }>()); +export const upsert<%= classify(name) %>s = createAction('[<%= classify(name) %>/API] Upsert <%= classify(name) %>s', props<{ <%= camelize(name) %>s: <%= classify(name) %>[] }>()); +export const update<%= classify(name) %> = createAction('[<%= classify(name) %>/API] Update <%= classify(name) %>', props<{ <%= camelize(name) %>: Update<<%= classify(name) %>> }>()); +export const update<%= classify(name) %>s = createAction('[<%= classify(name) %>/API] Update <%= classify(name) %>s', props<{ <%= camelize(name) %>s: Update<<%= classify(name) %>>[] }>()); +export const delete<%= classify(name) %> = createAction('[<%= classify(name) %>/API] Delete <%= classify(name) %>', props<{ id: string }>()); +export const delete<%= classify(name) %>s = createAction('[<%= classify(name) %>/API] Delete <%= classify(name) %>s', props<{ id: string[] }>()); +export const clear<%= classify(name) %>s = createAction('[<%= classify(name) %>/API] Clear <%= classify(name) %>s'); + +const all = union({ + load<%= classify(name) %>s, + add<%= classify(name) %>, + upsert<%= classify(name) %>, + add<%= classify(name) %>s, + upsert<%= classify(name) %>s, + update<%= classify(name) %>, + update<%= classify(name) %>s, + delete<%= classify(name) %>, + delete<%= classify(name) %>s, + clear<%= classify(name) %>s +}); +export type Union = typeof all; \ No newline at end of file diff --git a/modules/schematics/src/entity/creator-files/__name@dasherize@if-flat__/__name@dasherize@group-reducers__.reducer.ts.template b/modules/schematics/src/entity/creator-files/__name@dasherize@if-flat__/__name@dasherize@group-reducers__.reducer.ts.template new file mode 100644 index 0000000000..56805cdfb7 --- /dev/null +++ b/modules/schematics/src/entity/creator-files/__name@dasherize@if-flat__/__name@dasherize@group-reducers__.reducer.ts.template @@ -0,0 +1,71 @@ +import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; +import { <%= classify(name) %> } from '<%= featurePath(group, flat, "models", dasherize(name)) %><%= dasherize(name) %>.model'; +import * as <%= classify(name) %>Actions from '<%= featurePath(group, flat, "actions", dasherize(name)) %><%= dasherize(name) %>.actions'; + +export interface State extends EntityState<<%= classify(name) %>> { + // additional entities state properties +} + +export const adapter: EntityAdapter<<%= classify(name) %>> = createEntityAdapter<<%= classify(name) %>>(); + +export const initialState: State = adapter.getInitialState({ + // additional entity state properties +}); + +export function reducer( + state = initialState, + action: <%= classify(name) %>Actions.Union +): State { + switch (action.type) { + case <%= classify(name) %>Actions.add<%= classify(name) %>.type: { + return adapter.addOne(action.<%= camelize(name) %>, state); + } + + case <%= classify(name) %>Actions.upsert<%= classify(name) %>.type: { + return adapter.upsertOne(action.<%= camelize(name) %>, state); + } + + case <%= classify(name) %>Actions.add<%= classify(name) %>s.type: { + return adapter.addMany(action.<%= camelize(name) %>s, state); + } + + case <%= classify(name) %>Actions.upsert<%= classify(name) %>s.type: { + return adapter.upsertMany(action.<%= camelize(name) %>s, state); + } + + case <%= classify(name) %>Actions.update<%= classify(name) %>.type: { + return adapter.updateOne(action.<%= camelize(name) %>, state); + } + + case <%= classify(name) %>Actions.update<%= classify(name) %>s.type: { + return adapter.updateMany(action.<%= camelize(name) %>s, state); + } + + case <%= classify(name) %>Actions.delete<%= classify(name) %>.type: { + return adapter.removeOne(action.id, state); + } + + case <%= classify(name) %>Actions.delete<%= classify(name) %>s.type: { + return adapter.removeMany(action.ids, state); + } + + case <%= classify(name) %>Actions.load<%= classify(name) %>s.type: { + return adapter.addAll(action.<%= camelize(name) %>s, state); + } + + case <%= classify(name) %>Actions.clear<%= classify(name) %>s.type: { + return adapter.removeAll(state); + } + + default: { + return state; + } + } +} + +export const { + selectIds, + selectEntities, + selectAll, + selectTotal, +} = adapter.getSelectors(); diff --git a/modules/schematics/src/entity/index.spec.ts b/modules/schematics/src/entity/index.spec.ts index 3933d604b6..b7d99f18fb 100644 --- a/modules/schematics/src/entity/index.spec.ts +++ b/modules/schematics/src/entity/index.spec.ts @@ -191,4 +191,37 @@ describe('Entity Schematic', () => { expect(content).toMatch(/users\: fromUser.State/); expect(content).toMatch(/users\: fromUser.reducer/); }); + + describe('action creators', () => { + const creatorOptions = { ...defaultOptions, actionCreators: true }; + + it('should create a const for the action creator', () => { + const tree = schematicRunner.runSchematic( + 'entity', + creatorOptions, + appTree + ); + const fileContent = tree.readContent( + `${projectPath}/src/app/foo.actions.ts` + ); + expect(fileContent).toMatch( + /export const loadFoos = createAction\(\'\[Foo\/API\] Load Foos\', props<{ foos: Foo\[\] }>\(\)\);/ + ); + }); + + it('should use action creator types in the reducer', () => { + const tree = schematicRunner.runSchematic( + 'entity', + creatorOptions, + appTree + ); + const fileContent = tree.readContent( + `${projectPath}/src/app/foo.reducer.ts` + ); + expect(fileContent).toMatch( + /import \* as FooActions from \'\.\/foo.actions\';/ + ); + expect(fileContent).toMatch(/case FooActions\.addFoo\.type: \{/); + }); + }); }); diff --git a/modules/schematics/src/entity/index.ts b/modules/schematics/src/entity/index.ts index 9cf5572a09..c6dc81ed86 100644 --- a/modules/schematics/src/entity/index.ts +++ b/modules/schematics/src/entity/index.ts @@ -36,28 +36,37 @@ export default function(options: EntityOptions): Rule { options.module = findModuleFromOptions(host, options); } - const templateSource = apply(url('./files'), [ + const templateOptions = { + ...stringUtils, + 'if-flat': (s: string) => (options.flat ? '' : s), + 'group-actions': (name: string) => + stringUtils.group(name, options.group ? 'actions' : ''), + 'group-models': (name: string) => + stringUtils.group(name, options.group ? 'models' : ''), + 'group-reducers': (s: string) => + stringUtils.group(s, options.group ? 'reducers' : ''), + ...(options as object), + }; + + const commonTemplates = apply(url('./common-files'), [ options.spec ? noop() : filter(path => !path.endsWith('.spec.ts.template')), - applyTemplates({ - ...stringUtils, - 'if-flat': (s: string) => (options.flat ? '' : s), - 'group-actions': (name: string) => - stringUtils.group(name, options.group ? 'actions' : ''), - 'group-models': (name: string) => - stringUtils.group(name, options.group ? 'models' : ''), - 'group-reducers': (s: string) => - stringUtils.group(s, options.group ? 'reducers' : ''), - ...(options as object), - } as any), + applyTemplates(templateOptions), move(parsedPath.path), ]); + const templateSource = apply( + url(options.actionCreators ? './creator-files' : './files'), + [applyTemplates(templateOptions), move(parsedPath.path)] + ); + return chain([ addReducerToState({ ...options, plural: true }), addReducerImportToNgModule({ ...options }), - branchAndMerge(chain([mergeWith(templateSource)])), + branchAndMerge( + chain([mergeWith(commonTemplates), mergeWith(templateSource)]) + ), ])(host, context); }; } diff --git a/modules/schematics/src/entity/schema.json b/modules/schematics/src/entity/schema.json index 0bd229520c..ed372355cc 100644 --- a/modules/schematics/src/entity/schema.json +++ b/modules/schematics/src/entity/schema.json @@ -49,6 +49,13 @@ "description": "Group actions, reducers and effects within relative subfolders", "aliases": ["g"] + }, + "actionCreators": { + "type": "boolean", + "default": false, + "description": + "Specifies whether to generate action creators instead of action classes.", + "aliases": ["ac"] } }, "required": [] diff --git a/modules/schematics/src/entity/schema.ts b/modules/schematics/src/entity/schema.ts index 82b0f13ffe..af6462d24a 100644 --- a/modules/schematics/src/entity/schema.ts +++ b/modules/schematics/src/entity/schema.ts @@ -36,4 +36,10 @@ export interface Schema { */ group?: boolean; + + /** + * Specifies whether to generate action creators + * instead of action classes. + */ + actionCreators?: boolean; } diff --git a/modules/schematics/src/feature/index.ts b/modules/schematics/src/feature/index.ts index cbde98c368..6dee068d99 100644 --- a/modules/schematics/src/feature/index.ts +++ b/modules/schematics/src/feature/index.ts @@ -18,6 +18,7 @@ export default function(options: FeatureOptions): Rule { project: options.project, spec: false, api: options.api, + actionCreators: options.creators, }), schematic('reducer', { flat: options.flat, @@ -30,6 +31,7 @@ export default function(options: FeatureOptions): Rule { reducers: options.reducers, feature: true, api: options.api, + actionCreators: options.creators, }), schematic('effect', { flat: options.flat, @@ -41,6 +43,7 @@ export default function(options: FeatureOptions): Rule { spec: options.spec, feature: true, api: options.api, + effectCreators: options.creators, }), ])(host, context); }; diff --git a/modules/schematics/src/feature/schema.ts b/modules/schematics/src/feature/schema.ts index c01e13378f..a6f6ceecb6 100644 --- a/modules/schematics/src/feature/schema.ts +++ b/modules/schematics/src/feature/schema.ts @@ -46,7 +46,7 @@ export interface Schema { api?: boolean; /** - * Specifies if the effect creation uses 'createEffect' + * Specifies whether to use creator functions for actions and effects. */ - effectCreators?: boolean; + creators?: boolean; } diff --git a/modules/schematics/src/reducer/files/__name@dasherize@if-flat__/__name@dasherize__.reducer.spec.ts.template b/modules/schematics/src/reducer/common-files/__name@dasherize@if-flat__/__name@dasherize__.reducer.spec.ts.template similarity index 100% rename from modules/schematics/src/reducer/files/__name@dasherize@if-flat__/__name@dasherize__.reducer.spec.ts.template rename to modules/schematics/src/reducer/common-files/__name@dasherize@if-flat__/__name@dasherize__.reducer.spec.ts.template diff --git a/modules/schematics/src/reducer/creator-files/__name@dasherize@if-flat__/__name@dasherize__.reducer.ts.template b/modules/schematics/src/reducer/creator-files/__name@dasherize@if-flat__/__name@dasherize__.reducer.ts.template new file mode 100644 index 0000000000..e70d1dd688 --- /dev/null +++ b/modules/schematics/src/reducer/creator-files/__name@dasherize@if-flat__/__name@dasherize__.reducer.ts.template @@ -0,0 +1,27 @@ +<% if(!feature) { %>import { Action } from '@ngrx/store';<% } %> +<% if(feature) { %>import * as <%= classify(name) %>Actions from '<%= featurePath(group, flat, "actions", dasherize(name)) %><%= dasherize(name) %>.actions';<% } %> + +export interface State { + +} + +export const initialState: State = { + +}; + +export function reducer(state = initialState, action: <% if(feature) { %><%= classify(name) %>Actions.Union<% } else { %>Action<% } %>): State { + switch (action.type) { +<% if(feature) { %> + case <%= classify(name) %>Actions.load<%= classify(name) %>s.type: + return state; +<% if(api) { %> + case <%= classify(name) %>Actions.load<%= classify(name) %>sSuccess.type: + return state; + + case <%= classify(name) %>Actions.load<%= classify(name) %>sFailure.type: + return state; +<% } %><% } %> + default: + return state; + } +} diff --git a/modules/schematics/src/reducer/index.ts b/modules/schematics/src/reducer/index.ts index 0b9e9679f2..81d9f12081 100644 --- a/modules/schematics/src/reducer/index.ts +++ b/modules/schematics/src/reducer/index.ts @@ -37,26 +37,37 @@ export default function(options: ReducerOptions): Rule { options.name = parsedPath.name; options.path = parsedPath.path; - const templateSource = apply(url('./files'), [ + const templateOptions = { + ...stringUtils, + 'if-flat': (s: string) => + stringUtils.group( + options.flat ? '' : s, + options.group ? 'reducers' : '' + ), + ...(options as object), + }; + + const commonTemplate = apply(url('./common-files'), [ options.spec ? noop() : filter(path => !path.endsWith('.spec.ts.template')), - applyTemplates({ - ...stringUtils, - 'if-flat': (s: string) => - stringUtils.group( - options.flat ? '' : s, - options.group ? 'reducers' : '' - ), - ...(options as object), - } as any), + applyTemplates(templateOptions), move(parsedPath.path), ]); + const templateSource = apply( + url(options.actionCreators ? './creator-files' : './files'), + [applyTemplates(templateOptions), move(parsedPath.path)] + ); + return chain([ branchAndMerge(chain([addReducerToState(options)])), branchAndMerge( - chain([addReducerImportToNgModule(options), mergeWith(templateSource)]) + chain([ + addReducerImportToNgModule(options), + mergeWith(commonTemplate), + mergeWith(templateSource), + ]) ), ])(host, context); }; diff --git a/modules/schematics/src/reducer/schema.json b/modules/schematics/src/reducer/schema.json index 57a11e229e..3a1ab03e87 100644 --- a/modules/schematics/src/reducer/schema.json +++ b/modules/schematics/src/reducer/schema.json @@ -60,6 +60,13 @@ "description": "Specifies if api success and failure actions should be added to the reducer", "aliases": ["a"] + }, + "actionCreators": { + "type": "boolean", + "default": false, + "description": + "Specifies whether to generate action creators instead of action classes.", + "aliases": ["ac"] } }, "required": [] diff --git a/modules/schematics/src/reducer/schema.ts b/modules/schematics/src/reducer/schema.ts index 32b1be51e7..735daf27b9 100644 --- a/modules/schematics/src/reducer/schema.ts +++ b/modules/schematics/src/reducer/schema.ts @@ -49,4 +49,10 @@ export interface Schema { * should be added to the reducer. */ api?: boolean; + + /** + * Specifies whether to generate action creators + * instead of action classes. + */ + actionCreators?: boolean; }