From 5fa88909eae70b492a0342e3c79050699e7fefa0 Mon Sep 17 00:00:00 2001 From: Hemang Kumar Date: Thu, 23 Sep 2021 23:45:18 +0530 Subject: [PATCH] fix(schematics): use prefix option in feature schematic (#3139) Closes #3116 --- modules/schematics/schematics-core/index.ts | 1 + .../schematics-core/utility/ngrx-utils.ts | 6 ++ modules/schematics/src/action/index.ts | 12 ++-- .../__name@dasherize__.effects.ts.template | 18 +++--- modules/schematics/src/effect/index.spec.ts | 63 +++++++++++++++++++ modules/schematics/src/effect/index.ts | 22 +++++-- modules/schematics/src/effect/schema.json | 6 ++ modules/schematics/src/effect/schema.ts | 5 ++ modules/schematics/src/feature/index.spec.ts | 48 ++++++++++++++ modules/schematics/src/feature/index.ts | 3 + modules/schematics/src/feature/schema.json | 6 ++ modules/schematics/src/feature/schema.ts | 2 + .../__name@dasherize__.reducer.ts.template | 12 ++-- .../__name@dasherize__.reducer.ts.template | 6 +- modules/schematics/src/reducer/index.spec.ts | 21 +++++++ modules/schematics/src/reducer/index.ts | 3 + modules/schematics/src/reducer/schema.json | 6 ++ modules/schematics/src/reducer/schema.ts | 5 ++ 18 files changed, 218 insertions(+), 27 deletions(-) diff --git a/modules/schematics/schematics-core/index.ts b/modules/schematics/schematics-core/index.ts index b5b4e76b25..f073b12bd0 100644 --- a/modules/schematics/schematics-core/index.ts +++ b/modules/schematics/schematics-core/index.ts @@ -60,6 +60,7 @@ export { addReducerImportToNgModule, addReducerToActionReducerMap, omit, + getPrefix, } from './utility/ngrx-utils'; export { getProjectPath, getProject, isLib } from './utility/project'; diff --git a/modules/schematics/schematics-core/utility/ngrx-utils.ts b/modules/schematics/schematics-core/utility/ngrx-utils.ts index affb353a44..a59f59cdb2 100644 --- a/modules/schematics/schematics-core/utility/ngrx-utils.ts +++ b/modules/schematics/schematics-core/utility/ngrx-utils.ts @@ -268,3 +268,9 @@ export function omit( .filter((key) => key !== keyToRemove) .reduce((result, key) => Object.assign(result, { [key]: object[key] }), {}); } + +export function getPrefix(options: any) { + return options.creators + ? stringUtils.camelize(options.prefix || 'load') + : stringUtils.capitalize(options.prefix || 'load'); +} diff --git a/modules/schematics/src/action/index.ts b/modules/schematics/src/action/index.ts index 2d59ba3493..6a44ed543a 100644 --- a/modules/schematics/src/action/index.ts +++ b/modules/schematics/src/action/index.ts @@ -13,16 +13,18 @@ import { SchematicContext, } from '@angular-devkit/schematics'; import { Schema as ActionOptions } from './schema'; -import { getProjectPath, stringUtils, parseName } from '../../schematics-core'; -import { capitalize, camelize } from '../../schematics-core/utility/strings'; +import { + getProjectPath, + stringUtils, + parseName, + getPrefix, +} from '../../schematics-core'; export default function (options: ActionOptions): Rule { return (host: Tree, context: SchematicContext) => { options.path = getProjectPath(host, options); - options.prefix = options.creators - ? camelize(options.prefix || 'load') - : capitalize(options.prefix || 'load'); + options.prefix = getPrefix(options); const parsedPath = parseName(options.path, options.name); options.name = parsedPath.name; 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 45efe8f9f5..18cba22b43 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,7 +2,7 @@ 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 { Observable, EMPTY, of } from 'rxjs'; -<% if (!creators) {%>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 (!creators) {%>import { <%= prefix %><%= classify(name) %>sFailure, <%= prefix %><%= classify(name) %>sSuccess, <%= classify(name) %>ActionTypes, <%= classify(name) %>Actions } from '<%= featurePath(group, flat, "actions", dasherize(name)) %><%= dasherize(name) %>.actions';<% } %> <% if (creators) {%>import * as <%= classify(name) %>Actions from '<%= featurePath(group, flat, "actions", dasherize(name)) %><%= dasherize(name) %>.actions';<% } %> <% } %> <% if (feature && !api) { %>import { concatMap } from 'rxjs/operators'; @@ -15,34 +15,34 @@ import { Observable, EMPTY } from 'rxjs'; export class <%= classify(name) %>Effects { <% if (feature && api && !creators) { %> <%= effectStart %> - ofType(<%= classify(name) %>ActionTypes.Load<%= classify(name) %>s), + ofType(<%= classify(name) %>ActionTypes.<%= prefix %><%= 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 })))) + map(data => new <%= prefix %><%= classify(name) %>sSuccess({ data })), + catchError(error => of(new <%= prefix %><%= classify(name) %>sFailure({ error })))) ) <%= effectEnd %> <% } else if (feature && api && creators) { %> <%= effectStart %> - ofType(<%= classify(name) %>Actions.load<%= classify(name) %>s), + ofType(<%= classify(name) %>Actions.<%= prefix %><%= 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 })))) + map(data => <%= classify(name) %>Actions.<%= prefix %><%= classify(name) %>sSuccess({ data })), + catchError(error => of(<%= classify(name) %>Actions.<%= prefix %><%= classify(name) %>sFailure({ error })))) ) <%= effectEnd %> <% } %> <% if (feature && !api && !creators) { %> <%= effectStart %> - ofType(<%= classify(name) %>ActionTypes.Load<%= classify(name) %>s), + ofType(<%= classify(name) %>ActionTypes.<%= prefix %><%= classify(name) %>s), /** An EMPTY observable only emits completion. Replace with your own observable API request */ concatMap(() => EMPTY as Observable<{ type: string }>) <%= effectEnd %> <% } else if (feature && !api && creators) { %> <%= effectStart %> - ofType(<%= classify(name) %>Actions.load<%= classify(name) %>s), + ofType(<%= classify(name) %>Actions.<%= prefix %><%= classify(name) %>s), /** An EMPTY observable only emits completion. Replace with your own observable API request */ concatMap(() => EMPTY as Observable<{ type: string }>) <%= effectEnd %> diff --git a/modules/schematics/src/effect/index.spec.ts b/modules/schematics/src/effect/index.spec.ts index 46b45e3c18..0201aac873 100644 --- a/modules/schematics/src/effect/index.spec.ts +++ b/modules/schematics/src/effect/index.spec.ts @@ -27,6 +27,7 @@ describe('Effect Schematic', () => { root: false, group: false, creators: false, + prefix: 'load', }; const projectPath = getTestProjectPath(); @@ -463,4 +464,66 @@ describe('Effect Schematic', () => { expect(content).toMatch(/effects = TestBed\.inject\(FooEffects\);/); }); + + it('should add prefix to the effect', async () => { + const options = { + ...defaultOptions, + prefix: 'custom', + feature: true, + api: true, + }; + + const tree = await schematicRunner + .runSchematicAsync('effect', options, appTree) + .toPromise(); + const content = tree.readContent( + `${projectPath}/src/app/foo/foo.effects.ts` + ); + + expect(content).toMatch( + /import { CustomFoosFailure, CustomFoosSuccess, FooActionTypes, FooActions } from '\.\/foo.actions';/ + ); + + expect(content).toMatch(/customFoos\$ = this\.actions\$.pipe\(/); + expect(content).toMatch(/ofType\(FooActionTypes\.CustomFoos\),/); + + expect(content).toMatch( + /map\(data => new CustomFoosSuccess\({ data }\)\),/ + ); + + expect(content).toMatch( + /catchError\(error => of\(new CustomFoosFailure\({ error }\)\)\)\)/ + ); + }); + + it('should add prefix to the effect using creator function', async () => { + const options = { + ...defaultOptions, + creators: true, + api: true, + feature: true, + prefix: 'custom', + }; + + const tree = await schematicRunner + .runSchematicAsync('effect', options, appTree) + .toPromise(); + const content = tree.readContent( + `${projectPath}/src/app/foo/foo.effects.ts` + ); + + expect(content).toMatch( + /customFoos\$ = createEffect\(\(\) => {\s* return this.actions\$.pipe\(/ + ); + + expect(content).toMatch(/ofType\(FooActions.customFoos\),/); + + expect(content).toMatch( + /map\(data => FooActions.customFoosSuccess\({ data }\)\),/ + ); + + expect(content).toMatch( + /catchError\(error => of\(FooActions.customFoosFailure\({ error }\)\)\)\)/ + ); + }); }); diff --git a/modules/schematics/src/effect/index.ts b/modules/schematics/src/effect/index.ts index d520096cae..cfc3f19771 100644 --- a/modules/schematics/src/effect/index.ts +++ b/modules/schematics/src/effect/index.ts @@ -23,6 +23,7 @@ import { insertImport, parseName, stringUtils, + getPrefix, } from '../../schematics-core'; import { Schema as EffectOptions } from './schema'; @@ -105,12 +106,19 @@ function getEffectMethod(creators?: boolean) { return creators ? 'createEffect' : 'Effect'; } -function getEffectStart(name: string, creators?: boolean): string { +function getEffectStart( + name: string, + effectPrefix: string, + creators?: boolean +): string { const effectName = stringUtils.classify(name); + const effectMethodPrefix = stringUtils.camelize(effectPrefix); + return creators - ? `load${effectName}s$ = createEffect(() => {` + + ? `${effectMethodPrefix}${effectName}s$ = createEffect(() => {` + '\n return this.actions$.pipe( \n' - : '@Effect()\n' + ` load${effectName}s$ = this.actions$.pipe(`; + : '@Effect()\n' + + ` ${effectMethodPrefix}${effectName}s$ = this.actions$.pipe(`; } function getEffectEnd(creators?: boolean) { @@ -121,6 +129,8 @@ export default function (options: EffectOptions): Rule { return (host: Tree, context: SchematicContext) => { options.path = getProjectPath(host, options); + options.prefix = getPrefix(options); + if (options.module) { options.module = findModuleFromOptions(host, options); } @@ -142,7 +152,11 @@ export default function (options: EffectOptions): Rule { options.group ? 'effects' : '' ), effectMethod: getEffectMethod(options.creators), - effectStart: getEffectStart(options.name, options.creators), + effectStart: getEffectStart( + options.name, + options.prefix, + options.creators + ), effectEnd: getEffectEnd(options.creators), ...(options as object), } as any), diff --git a/modules/schematics/src/effect/schema.json b/modules/schematics/src/effect/schema.json index 94b9c8b8ce..a865a6ffac 100644 --- a/modules/schematics/src/effect/schema.json +++ b/modules/schematics/src/effect/schema.json @@ -76,6 +76,12 @@ "type": "boolean", "default": false, "description": "Setup root effects module without registering initial effects." + }, + "prefix": { + "description": "The prefix of the effect.", + "type": "string", + "default": "load", + "x-prompt": "What should be the prefix of the effect?" } }, "required": [] diff --git a/modules/schematics/src/effect/schema.ts b/modules/schematics/src/effect/schema.ts index cbee7c67e0..0a9aea618e 100644 --- a/modules/schematics/src/effect/schema.ts +++ b/modules/schematics/src/effect/schema.ts @@ -59,4 +59,9 @@ export interface Schema { * Setup root effects module without registering initial effects. */ minimal?: boolean; + + /** + * The prefix for the effects. + */ + prefix?: string; } diff --git a/modules/schematics/src/feature/index.spec.ts b/modules/schematics/src/feature/index.spec.ts index 33c3380290..92b702af08 100644 --- a/modules/schematics/src/feature/index.spec.ts +++ b/modules/schematics/src/feature/index.spec.ts @@ -271,4 +271,52 @@ describe('Feature Schematic', () => { /on\(FooActions.loadFoosFailure, \(state, action\) => state\),/ ); }); + + it('should have all api effect with prefix if api flag enabled', async () => { + const options = { + ...defaultOptions, + api: true, + prefix: 'custom', + }; + + const tree = await schematicRunner + .runSchematicAsync('feature', options, appTree) + .toPromise(); + const fileContent = tree.readContent( + `${projectPath}/src/app/foo.effects.ts` + ); + + expect(fileContent).toMatch(/customFoos\$ = createEffect\(\(\) => {/); + expect(fileContent).toMatch(/ofType\(FooActions.customFoos\),/); + + expect(fileContent).toMatch( + /map\(data => FooActions.customFoosSuccess\({ data }\)\),/ + ); + expect(fileContent).toMatch( + /catchError\(error => of\(FooActions.customFoosFailure\({ error }\)\)\)\)/ + ); + }); + + it('should have all api actions with prefix in reducer if api flag enabled', async () => { + const options = { + ...defaultOptions, + api: true, + prefix: 'custom', + }; + + const tree = await schematicRunner + .runSchematicAsync('feature', options, appTree) + .toPromise(); + const fileContent = tree.readContent( + `${projectPath}/src/app/foo.reducer.ts` + ); + + expect(fileContent).toMatch(/on\(FooActions.customFoos, state => state\),/); + expect(fileContent).toMatch( + /on\(FooActions.customFoosSuccess, \(state, action\) => state\),/ + ); + expect(fileContent).toMatch( + /on\(FooActions.customFoosFailure, \(state, action\) => state\),/ + ); + }); }); diff --git a/modules/schematics/src/feature/index.ts b/modules/schematics/src/feature/index.ts index 1588905767..f2ef904ff9 100644 --- a/modules/schematics/src/feature/index.ts +++ b/modules/schematics/src/feature/index.ts @@ -19,6 +19,7 @@ export default function (options: FeatureOptions): Rule { skipTests: options.skipTests, api: options.api, creators: options.creators, + prefix: options.prefix, }), schematic('reducer', { flat: options.flat, @@ -32,6 +33,7 @@ export default function (options: FeatureOptions): Rule { feature: true, api: options.api, creators: options.creators, + prefix: options.prefix, }), schematic('effect', { flat: options.flat, @@ -44,6 +46,7 @@ export default function (options: FeatureOptions): Rule { feature: true, api: options.api, creators: options.creators, + prefix: options.prefix, }), schematic('selector', { flat: options.flat, diff --git a/modules/schematics/src/feature/schema.json b/modules/schematics/src/feature/schema.json index 50112b304c..b0270311d1 100644 --- a/modules/schematics/src/feature/schema.json +++ b/modules/schematics/src/feature/schema.json @@ -63,6 +63,12 @@ "description": "Specifies if the actions, reducers, and effects should be created using creator functions", "aliases": ["c"], "x-prompt": "Do you want to use the create functions?" + }, + "prefix": { + "description": "The prefix of the action, effect and reducer.", + "type": "string", + "default": "load", + "x-prompt": "What should be the prefix of the action, effect and reducer?" } }, "required": [] diff --git a/modules/schematics/src/feature/schema.ts b/modules/schematics/src/feature/schema.ts index 372acc6ba4..1c0491a7b0 100644 --- a/modules/schematics/src/feature/schema.ts +++ b/modules/schematics/src/feature/schema.ts @@ -49,4 +49,6 @@ export interface Schema { * Specifies whether to use creator functions for actions, reducers, and effects. */ creators?: boolean; + + prefix?: string; } 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 index 9da90f2c6e..a6a3ab86b8 100644 --- 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 @@ -15,18 +15,18 @@ export const initialState: State = { export const reducer = createReducer( initialState, <% if(feature) { %> - on(<%= classify(name) %>Actions.load<%= classify(name) %>s, state => state), -<% if(api) { %> on(<%= classify(name) %>Actions.load<%= classify(name) %>sSuccess, (state, action) => state), - on(<%= classify(name) %>Actions.load<%= classify(name) %>sFailure, (state, action) => state), + on(<%= classify(name) %>Actions.<%= prefix %><%= classify(name) %>s, state => state), +<% if(api) { %> on(<%= classify(name) %>Actions.<%= prefix %><%= classify(name) %>sSuccess, (state, action) => state), + on(<%= classify(name) %>Actions.<%= prefix %><%= classify(name) %>sFailure, (state, action) => state), <% } %><% } %> ); <% }else { %> const <%= camelize(name) %>Reducer = createReducer( initialState, <% if(feature) { %> - on(<%= classify(name) %>Actions.load<%= classify(name) %>s, state => state), -<% if(api) { %> on(<%= classify(name) %>Actions.load<%= classify(name) %>sSuccess, (state, action) => state), - on(<%= classify(name) %>Actions.load<%= classify(name) %>sFailure, (state, action) => state), + on(<%= classify(name) %>Actions.<%= prefix %><%= classify(name) %>s, state => state), +<% if(api) { %> on(<%= classify(name) %>Actions.<%= prefix %><%= classify(name) %>sSuccess, (state, action) => state), + on(<%= classify(name) %>Actions.<%= prefix %><%= classify(name) %>sFailure, (state, action) => state), <% } %><% } %> ); diff --git a/modules/schematics/src/reducer/files/__name@dasherize@if-flat__/__name@dasherize__.reducer.ts.template b/modules/schematics/src/reducer/files/__name@dasherize@if-flat__/__name@dasherize__.reducer.ts.template index 81114cb46b..0fcbc4633d 100644 --- a/modules/schematics/src/reducer/files/__name@dasherize@if-flat__/__name@dasherize__.reducer.ts.template +++ b/modules/schematics/src/reducer/files/__name@dasherize@if-flat__/__name@dasherize__.reducer.ts.template @@ -14,13 +14,13 @@ export const initialState: State = { export function reducer(state = initialState, action: <% if(feature) { %><%= classify(name) %>Actions<% } else { %>Action<% } %>): State { switch (action.type) { <% if(feature) { %> - case <%= classify(name) %>ActionTypes.Load<%= classify(name) %>s: + case <%= classify(name) %>ActionTypes.<%= prefix %><%= classify(name) %>s: return state; <% if(api) { %> - case <%= classify(name) %>ActionTypes.Load<%= classify(name) %>sSuccess: + case <%= classify(name) %>ActionTypes.<%= prefix %><%= classify(name) %>sSuccess: return state; - case <%= classify(name) %>ActionTypes.Load<%= classify(name) %>sFailure: + case <%= classify(name) %>ActionTypes.<%= prefix %><%= classify(name) %>sFailure: return state; <% } %><% } %> default: diff --git a/modules/schematics/src/reducer/index.spec.ts b/modules/schematics/src/reducer/index.spec.ts index 64931c7e00..124deb29b2 100644 --- a/modules/schematics/src/reducer/index.spec.ts +++ b/modules/schematics/src/reducer/index.spec.ts @@ -339,4 +339,25 @@ describe('Reducer Schematic', () => { expect(fileContent).toMatch(/case FooActionTypes\.LoadFoosFailure/); expect(fileContent).not.toMatch(/import { Action } from '@ngrx\/store'/); }); + + it('should create a reducer with prefix in an api feature', async () => { + const tree = await schematicRunner + .runSchematicAsync( + 'reducer', + { ...defaultOptions, feature: true, api: true, prefix: 'custom' }, + appTree + ) + .toPromise(); + const fileContent = tree.readContent( + `${projectPath}/src/app/foo.reducer.ts` + ); + + expect(fileContent).toMatch(/on\(FooActions.customFoos, state => state\)/); + expect(fileContent).toMatch( + /on\(FooActions.customFoosSuccess, \(state, action\) => state\)/ + ); + expect(fileContent).toMatch( + /on\(FooActions.customFoosFailure, \(state, action\) => state\)/ + ); + }); }); diff --git a/modules/schematics/src/reducer/index.ts b/modules/schematics/src/reducer/index.ts index 8e91a9b3d0..71e1e9bc27 100644 --- a/modules/schematics/src/reducer/index.ts +++ b/modules/schematics/src/reducer/index.ts @@ -23,6 +23,7 @@ import { parseName, isIvyEnabled, getProject, + getPrefix, } from '../../schematics-core'; import { Schema as ReducerOptions } from './schema'; @@ -31,6 +32,8 @@ export default function (options: ReducerOptions): Rule { const projectConfig = getProject(host, options); options.path = getProjectPath(host, options); + options.prefix = getPrefix(options); + if (options.module) { options.module = findModuleFromOptions(host, options); } diff --git a/modules/schematics/src/reducer/schema.json b/modules/schematics/src/reducer/schema.json index 0c10d58235..1b6ca93c75 100644 --- a/modules/schematics/src/reducer/schema.json +++ b/modules/schematics/src/reducer/schema.json @@ -70,5 +70,11 @@ "x-prompt": "Do you want to use the create function?" } }, + "prefix": { + "description": "The prefix of the reducer.", + "type": "string", + "default": "load", + "x-prompt": "What should be the prefix of the reducer?" + }, "required": [] } diff --git a/modules/schematics/src/reducer/schema.ts b/modules/schematics/src/reducer/schema.ts index 70d6acfde8..4d1be8679e 100644 --- a/modules/schematics/src/reducer/schema.ts +++ b/modules/schematics/src/reducer/schema.ts @@ -55,4 +55,9 @@ export interface Schema { * handling actions and reducers. */ creators?: boolean; + + /** + * The prefix for the reducers. + */ + prefix?: string; }