Skip to content

Commit

Permalink
fix(schematics): use prefix option in feature schematic (#3139)
Browse files Browse the repository at this point in the history
Closes #3116
  • Loading branch information
hemangsk authored Sep 23, 2021
1 parent 9611415 commit 5fa8890
Show file tree
Hide file tree
Showing 18 changed files with 218 additions and 27 deletions.
1 change: 1 addition & 0 deletions modules/schematics/schematics-core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export {
addReducerImportToNgModule,
addReducerToActionReducerMap,
omit,
getPrefix,
} from './utility/ngrx-utils';

export { getProjectPath, getProject, isLib } from './utility/project';
Expand Down
6 changes: 6 additions & 0 deletions modules/schematics/schematics-core/utility/ngrx-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,9 @@ export function omit<T extends { [key: string]: any }>(
.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');
}
12 changes: 7 additions & 5 deletions modules/schematics/src/action/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 %>
Expand Down
63 changes: 63 additions & 0 deletions modules/schematics/src/effect/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ describe('Effect Schematic', () => {
root: false,
group: false,
creators: false,
prefix: 'load',
};

const projectPath = getTestProjectPath();
Expand Down Expand Up @@ -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 }\)\)\)\)/
);
});
});
22 changes: 18 additions & 4 deletions modules/schematics/src/effect/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
insertImport,
parseName,
stringUtils,
getPrefix,
} from '../../schematics-core';
import { Schema as EffectOptions } from './schema';

Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
Expand All @@ -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),
Expand Down
6 changes: 6 additions & 0 deletions modules/schematics/src/effect/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": []
Expand Down
5 changes: 5 additions & 0 deletions modules/schematics/src/effect/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,9 @@ export interface Schema {
* Setup root effects module without registering initial effects.
*/
minimal?: boolean;

/**
* The prefix for the effects.
*/
prefix?: string;
}
48 changes: 48 additions & 0 deletions modules/schematics/src/feature/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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\),/
);
});
});
3 changes: 3 additions & 0 deletions modules/schematics/src/feature/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions modules/schematics/src/feature/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": []
Expand Down
2 changes: 2 additions & 0 deletions modules/schematics/src/feature/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,6 @@ export interface Schema {
* Specifies whether to use creator functions for actions, reducers, and effects.
*/
creators?: boolean;

prefix?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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),
<% } %><% } %>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading

0 comments on commit 5fa8890

Please sign in to comment.