feat(angular): add standalone support to NgRx generator (#14141)

This commit is contained in:
Colum Ferry 2023-01-05 12:46:54 +00:00 committed by GitHub
parent 49aa44bb81
commit 5fba936254
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 834 additions and 464 deletions

View File

@ -28,13 +28,17 @@
"module": {
"type": "string",
"description": "The path to the `NgModule` where the feature state will be registered. The host directory will create/use the new state directory.",
"x-prompt": "What is the path to the module where this NgRx state should be registered?",
"x-deprecated": "This option will be removed in a future version of Nx. Please switch to using --parent instead."
},
"parent": {
"type": "string",
"description": "The path to the `NgModule` where the feature state will be registered. The host directory will create/use the new state directory.",
"x-prompt": "What is the path to the module where this NgRx state should be registered?"
"description": "The path to the `NgModule` or the `Routes` definition file (for Standalone API usage) where the feature state will be registered. The host directory will create/use the new state directory.",
"x-prompt": "What is the path to the module or Routes definition where this NgRx state should be registered?"
},
"route": {
"type": "string",
"description": "The route that the Standalone NgRx Providers should be added to.",
"default": "''"
},
"directory": {
"type": "string",

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ngrx generated unit tests should generate specs for the ngrx effects 1`] = `
exports[`ngrx NgModule Syntax generated unit tests should generate specs for the ngrx effects 1`] = `
"import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { Action } from '@ngrx/store';
@ -41,7 +41,7 @@ describe('SuperUsersEffects', () => {
"
`;
exports[`ngrx generated unit tests should generate specs for the ngrx facade 1`] = `
exports[`ngrx NgModule Syntax generated unit tests should generate specs for the ngrx facade 1`] = `
"import { NgModule } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { EffectsModule } from '@ngrx/effects';
@ -144,7 +144,7 @@ describe('SuperUsersFacade', () => {
"
`;
exports[`ngrx generated unit tests should generate specs for the ngrx reducer 1`] = `
exports[`ngrx NgModule Syntax generated unit tests should generate specs for the ngrx reducer 1`] = `
"import { Action } from '@ngrx/store';
import * as SuperUsersActions from './super-users.actions';
@ -185,7 +185,7 @@ describe('SuperUsers Reducer', () => {
"
`;
exports[`ngrx generated unit tests should generate specs for the ngrx selectors 1`] = `
exports[`ngrx NgModule Syntax generated unit tests should generate specs for the ngrx selectors 1`] = `
"import { SuperUsersEntity } from './super-users.models';
import { superUsersAdapter, SuperUsersPartialState, initialSuperUsersState } from './super-users.reducer';
import * as SuperUsersSelectors from './super-users.selectors';
@ -247,7 +247,7 @@ describe('SuperUsers Selectors', () => {
"
`;
exports[`ngrx should add a root module with feature module when minimal is set to false 1`] = `
exports[`ngrx NgModule Syntax should add a root module with feature module when minimal is set to false 1`] = `
"
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@ -273,7 +273,7 @@ import { StoreRouterConnectingModule } from '@ngrx/router-store';
"
`;
exports[`ngrx should add a root module with feature module when minimal is set to false using --module 1`] = `
exports[`ngrx NgModule Syntax should add a root module with feature module when minimal is set to false using --module 1`] = `
"
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@ -299,7 +299,7 @@ import { StoreRouterConnectingModule } from '@ngrx/router-store';
"
`;
exports[`ngrx should add an empty root module when minimal and root are set to true 1`] = `
exports[`ngrx NgModule Syntax should add an empty root module when minimal and root are set to true 1`] = `
"
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@ -323,7 +323,7 @@ import { StoreRouterConnectingModule } from '@ngrx/router-store';
"
`;
exports[`ngrx should generate a models file for the feature 1`] = `
exports[`ngrx NgModule Syntax should generate a models file for the feature 1`] = `
"/**
* Interface for the 'Users' data
*/
@ -333,7 +333,7 @@ export interface UsersEntity {
};"
`;
exports[`ngrx should generate the ngrx actions 1`] = `
exports[`ngrx NgModule Syntax should generate the ngrx actions 1`] = `
"import { createAction, props } from '@ngrx/store';
import { UsersEntity } from './users.models';
@ -353,36 +353,33 @@ export const loadUsersFailure = createAction(
"
`;
exports[`ngrx should generate the ngrx effects 1`] = `
exports[`ngrx NgModule Syntax should generate the ngrx effects 1`] = `
"import { Injectable, inject } from '@angular/core';
import { createEffect, Actions, ofType } from '@ngrx/effects';
import { fetch } from '@nrwl/angular';
import * as UsersActions from './users.actions';
import * as UsersFeature from './users.reducer';
import {switchMap, catchError, of} from 'rxjs';
@Injectable()
export class UsersEffects {
private actions$ = inject(Actions);
init$ = createEffect(() => this.actions$.pipe(
ofType(UsersActions.initUsers),
fetch({
run: (action) => {
// Your custom service 'load' logic goes here. For now just return a success action...
return UsersActions.loadUsersSuccess({ users: [] });
},
onError: (action, error) => {
switchMap(() => of(UsersActions.loadUsersSuccess({ users: [] }))),
catchError((error) => {
console.error('Error', error);
return UsersActions.loadUsersFailure({ error });
return of(UsersActions.loadUsersFailure({ error }));
}
})
)
));
}
"
`;
exports[`ngrx should generate the ngrx facade 1`] = `
exports[`ngrx NgModule Syntax should generate the ngrx facade 1`] = `
"import { Injectable, inject } from '@angular/core';
import { select, Store, Action } from '@ngrx/store';
@ -413,7 +410,7 @@ export class UsersFacade {
"
`;
exports[`ngrx should generate the ngrx reducer 1`] = `
exports[`ngrx NgModule Syntax should generate the ngrx reducer 1`] = `
"import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
import { createReducer, on, Action } from '@ngrx/store';
@ -458,7 +455,7 @@ export function usersReducer(state: UsersState | undefined, action: Action) {
"
`;
exports[`ngrx should generate the ngrx selectors 1`] = `
exports[`ngrx NgModule Syntax should generate the ngrx selectors 1`] = `
"import { createFeatureSelector, createSelector } from '@ngrx/store';
import { USERS_FEATURE_KEY, UsersState, usersAdapter } from './users.reducer';
@ -500,7 +497,7 @@ export const selectEntity = createSelector(
"
`;
exports[`ngrx should not generate imports when skipImport is true 1`] = `
exports[`ngrx NgModule Syntax should not generate imports when skipImport is true 1`] = `
"
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@ -515,7 +512,7 @@ exports[`ngrx should not generate imports when skipImport is true 1`] = `
"
`;
exports[`ngrx should update the entry point file correctly when barrels is true 1`] = `
exports[`ngrx NgModule Syntax should update the entry point file correctly when barrels is true 1`] = `
"import * as SuperUsersActions from './lib/+state/super-users.actions';
import * as SuperUsersFeature from './lib/+state/super-users.reducer';
@ -532,7 +529,7 @@ export { SuperUsersActions, SuperUsersFeature, SuperUsersSelectors };
"
`;
exports[`ngrx should update the entry point file with no facade 1`] = `
exports[`ngrx NgModule Syntax should update the entry point file with no facade 1`] = `
"export * from './lib/+state/super-users.models';
export * from './lib/+state/super-users.selectors';
export * from './lib/+state/super-users.reducer';
@ -542,7 +539,7 @@ export * from './lib/+state/super-users.actions';
"
`;
exports[`ngrx should update the entry point file with the right exports 1`] = `
exports[`ngrx NgModule Syntax should update the entry point file with the right exports 1`] = `
"export * from './lib/+state/super-users.facade';
export * from './lib/+state/super-users.models';
export * from './lib/+state/super-users.selectors';
@ -552,3 +549,58 @@ export * from './lib/+state/super-users.actions';
export * from './lib/flights.module';
"
`;
exports[`ngrx Standalone APIs should add a root module with feature module when minimal is set to false 1`] = `
"import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter, withEnabledBlockingInitialNavigation } from '@angular/router';
import { AppComponent } from './app/app.component';
import { appRoutes } from './app/app.routes';
import { provideStore, provideState } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
import * as fromUsers from './+state/users.reducer';
import { UsersEffects } from './+state/users.effects';
bootstrapApplication(AppComponent, {
providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),provideEffects(),provideStore(),provideRouter(appRoutes, withEnabledBlockingInitialNavigation())],
}).catch((err) => console.error(err));"
`;
exports[`ngrx Standalone APIs should add an empty provideStore when minimal and root are set to true 1`] = `
"import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter, withEnabledBlockingInitialNavigation } from '@angular/router';
import { AppComponent } from './app/app.component';
import { appRoutes } from './app/app.routes';
import { provideStore, provideState } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
bootstrapApplication(AppComponent, {
providers: [provideEffects(),provideStore(),provideRouter(appRoutes, withEnabledBlockingInitialNavigation())],
}).catch((err) => console.error(err));"
`;
exports[`ngrx Standalone APIs should add facade provider when facade is true 1`] = `
"import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter, withEnabledBlockingInitialNavigation } from '@angular/router';
import { AppComponent } from './app/app.component';
import { appRoutes } from './app/app.routes';
import { provideStore, provideState } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
import * as fromUsers from './+state/users.reducer';
import { UsersEffects } from './+state/users.effects';
import { UsersFacade } from './+state/users.facade';
bootstrapApplication(AppComponent, {
providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),provideEffects(),provideStore(),UsersFacade,provideRouter(appRoutes, withEnabledBlockingInitialNavigation())],
}).catch((err) => console.error(err));"
`;
exports[`ngrx Standalone APIs should add facade provider when facade is true and --root is false 1`] = `
"import { Routes } from '@angular/router';
import { NxWelcomeComponent } from './nx-welcome.component';
import { provideStore, provideState } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
import * as fromUsers from './+state/users.reducer';
import { UsersEffects } from './+state/users.effects';
import { UsersFacade } from './+state/users.facade';
export const appRoutes: Routes = [{ path: '', component: NxWelcomeComponent , providers: [UsersFacade, provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer), provideEffects(UsersEffects)]}];"
`;

View File

@ -1,25 +1,22 @@
import { Injectable, inject } from '@angular/core';
import { createEffect, Actions, ofType } from '@ngrx/effects';
import { fetch } from '@nrwl/angular';
import * as <%= className %>Actions from './<%= fileName %>.actions';
import * as <%= className %>Feature from './<%= fileName %>.reducer';
import {switchMap, catchError, of} from 'rxjs';
@Injectable()
export class <%= className %>Effects {
private actions$ = inject(Actions);
init$ = createEffect(() => this.actions$.pipe(
ofType(<%= className %>Actions.init<%= className %>),
fetch({
run: (action) => {
// Your custom service 'load' logic goes here. For now just return a success action...
return <%= className %>Actions.load<%= className %>Success({ <%= propertyName %>: [] });
},
onError: (action, error) => {
switchMap(() => of(<%= className %>Actions.load<%= className %>Success({ <%= propertyName %>: [] }))),
catchError((error) => {
console.error('Error', error);
return <%= className %>Actions.load<%= className %>Failure({ error });
return of(<%= className %>Actions.load<%= className %>Failure({ error }));
}
})
)
));
}

View File

@ -5,9 +5,139 @@ import type { SourceFile } from 'typescript';
import { createSourceFile, ScriptTarget } from 'typescript';
import {
addImportToModule,
addProviderToBootstrapApplication,
addProviderToModule,
} from '../../../utils/nx-devkit/ast-utils';
import type { NormalizedNgRxGeneratorOptions } from './normalize-options';
import { addProviderToRoute } from '../../../utils/nx-devkit/route-utils';
function addRootStoreImport(
tree: Tree,
isParentStandalone: boolean,
route: string,
sourceFile: SourceFile,
parentPath: string,
provideRootStore: string,
storeForRoot: string
) {
if (isParentStandalone) {
if (tree.read(parentPath, 'utf-8').includes('bootstrapApplication')) {
addProviderToBootstrapApplication(tree, parentPath, provideRootStore);
} else {
addProviderToRoute(tree, parentPath, route, provideRootStore);
}
} else {
sourceFile = addImportToModule(tree, sourceFile, parentPath, storeForRoot);
}
return sourceFile;
}
function addRootEffectsImport(
tree: Tree,
isParentStandalone: boolean,
route: string,
sourceFile: SourceFile,
parentPath: string,
provideRootEffects: string,
effectsForEmptyRoot: string
) {
if (isParentStandalone) {
if (tree.read(parentPath, 'utf-8').includes('bootstrapApplication')) {
addProviderToBootstrapApplication(tree, parentPath, provideRootEffects);
} else {
addProviderToRoute(tree, parentPath, route, provideRootEffects);
}
} else {
sourceFile = addImportToModule(
tree,
sourceFile,
parentPath,
effectsForEmptyRoot
);
}
return sourceFile;
}
function addRouterStoreImport(
tree: Tree,
sourceFile: SourceFile,
addImport: (
source: SourceFile,
symbolName: string,
fileName: string,
isDefault?: boolean
) => SourceFile,
parentPath: string,
storeRouterModule: string
) {
sourceFile = addImport(
sourceFile,
'StoreRouterConnectingModule',
'@ngrx/router-store'
);
return addImportToModule(tree, sourceFile, parentPath, storeRouterModule);
}
function addStoreForFeatureImport(
tree: Tree,
isParentStandalone,
route: string,
sourceFile: SourceFile,
parentPath: string,
provideStoreForFeature: string,
storeForFeature: string
) {
if (isParentStandalone) {
if (tree.read(parentPath, 'utf-8').includes('bootstrapApplication')) {
addProviderToBootstrapApplication(
tree,
parentPath,
provideStoreForFeature
);
} else {
addProviderToRoute(tree, parentPath, route, provideStoreForFeature);
}
} else {
sourceFile = addImportToModule(
tree,
sourceFile,
parentPath,
storeForFeature
);
}
return sourceFile;
}
function addEffectsForFeatureImport(
tree: Tree,
isParentStandalone,
route: string,
sourceFile: SourceFile,
parentPath: string,
provideEffectsForFeature: string,
effectsForFeature: string
) {
if (isParentStandalone) {
if (tree.read(parentPath, 'utf-8').includes('bootstrapApplication')) {
addProviderToBootstrapApplication(
tree,
parentPath,
provideEffectsForFeature
);
} else {
addProviderToRoute(tree, parentPath, route, provideEffectsForFeature);
}
} else {
sourceFile = addImportToModule(
tree,
sourceFile,
parentPath,
effectsForFeature
);
}
return sourceFile;
}
export function addImportsToModule(
tree: Tree,
@ -21,6 +151,9 @@ export function addImportsToModule(
ScriptTarget.Latest,
true
);
const isParentStandalone = !sourceText.includes('@NgModule');
const addImport = (
source: SourceFile,
symbolName: string,
@ -65,30 +198,48 @@ export function addImportsToModule(
const effectsForFeature = `EffectsModule.forFeature([${effectsName}])`;
const storeRouterModule = 'StoreRouterConnectingModule.forRoot()';
const provideRootStore = `provideStore()`;
const provideRootEffects = `provideEffects()`;
const provideEffectsForFeature = `provideEffects(${effectsName})`;
const provideStoreForFeature = `provideState(from${className}.${constantName}_FEATURE_KEY, from${className}.${propertyName}Reducer)`;
if (isParentStandalone) {
sourceFile = addImport(sourceFile, 'provideStore', '@ngrx/store');
sourceFile = addImport(sourceFile, 'provideState', '@ngrx/store');
sourceFile = addImport(sourceFile, 'provideEffects', '@ngrx/effects');
} else {
sourceFile = addImport(sourceFile, 'StoreModule', '@ngrx/store');
sourceFile = addImport(sourceFile, 'EffectsModule', '@ngrx/effects');
}
// this is just a heuristic
const hasRouter = sourceText.indexOf('RouterModule') > -1;
sourceFile = addImport(sourceFile, 'StoreModule', '@ngrx/store');
sourceFile = addImport(sourceFile, 'EffectsModule', '@ngrx/effects');
if (options.minimal && options.root) {
sourceFile = addImportToModule(tree, sourceFile, parentPath, storeForRoot);
sourceFile = addImportToModule(
sourceFile = addRootStoreImport(
tree,
isParentStandalone,
options.route,
sourceFile,
parentPath,
provideRootStore,
storeForRoot
);
sourceFile = addRootEffectsImport(
tree,
isParentStandalone,
options.route,
sourceFile,
parentPath,
provideRootEffects,
effectsForEmptyRoot
);
if (hasRouter) {
sourceFile = addImport(
sourceFile,
'StoreRouterConnectingModule',
'@ngrx/router-store'
);
sourceFile = addImportToModule(
if (hasRouter && !isParentStandalone) {
sourceFile = addRouterStoreImport(
tree,
sourceFile,
addImport,
parentPath,
storeRouterModule
);
@ -100,12 +251,20 @@ export function addImportsToModule(
if (options.facade) {
sourceFile = addImport(sourceFile, facadeName, facadePath);
sourceFile = addProviderToModule(
tree,
sourceFile,
parentPath,
facadeName
);
if (isParentStandalone) {
if (tree.read(parentPath, 'utf-8').includes('bootstrapApplication')) {
addProviderToBootstrapApplication(tree, parentPath, facadeName);
} else {
addProviderToRoute(tree, parentPath, options.route, facadeName);
}
} else {
sourceFile = addProviderToModule(
tree,
sourceFile,
parentPath,
facadeName
);
}
}
return sourceFile;
@ -114,52 +273,76 @@ export function addImportsToModule(
if (options.root) {
sourceFile = addCommonImports();
sourceFile = addImportToModule(
sourceFile = addRootStoreImport(
tree,
isParentStandalone,
options.route,
sourceFile,
parentPath,
provideRootStore,
storeForRoot
);
sourceFile = addImportToModule(
sourceFile = addRootEffectsImport(
tree,
isParentStandalone,
options.route,
sourceFile,
parentPath,
provideRootEffects,
effectsForRoot
);
if (hasRouter) {
sourceFile = addImport(
sourceFile,
'StoreRouterConnectingModule',
'@ngrx/router-store'
);
sourceFile = addImportToModule(
if (hasRouter && !isParentStandalone) {
sourceFile = addRouterStoreImport(
tree,
sourceFile,
addImport,
parentPath,
storeRouterModule
);
}
sourceFile = addImportToModule(
sourceFile = addStoreForFeatureImport(
tree,
isParentStandalone,
options.route,
sourceFile,
parentPath,
provideStoreForFeature,
storeForFeature
);
if (isParentStandalone) {
addEffectsForFeatureImport(
tree,
isParentStandalone,
options.route,
sourceFile,
parentPath,
provideEffectsForFeature,
effectsForFeature
);
}
} else {
sourceFile = addCommonImports();
sourceFile = addImportToModule(
sourceFile = addStoreForFeatureImport(
tree,
isParentStandalone,
options.route,
sourceFile,
parentPath,
provideStoreForFeature,
storeForFeature
);
sourceFile = addImportToModule(
sourceFile = addEffectsForFeatureImport(
tree,
isParentStandalone,
options.route,
sourceFile,
parentPath,
provideEffectsForFeature,
effectsForFeature
);
}

View File

@ -16,6 +16,7 @@ export function normalizeOptions(
: options.parent
? dirname(options.parent)
: undefined,
route: options.route ?? "''",
directory: names(options.directory).fileName,
};
}

View File

@ -11,6 +11,7 @@ import {
} from '../../utils/nx-devkit/testing';
import { ngrxVersion } from '../../utils/versions';
import { ngrxGenerator } from './ngrx';
import applicationGenerator from '../application/application';
import type { NgRxGeneratorOptions } from './schema';
describe('ngrx', () => {
@ -25,6 +26,13 @@ describe('ngrx', () => {
name: 'users',
};
const defaultStandaloneOptions: NgRxGeneratorOptions = {
directory: '+state',
minimal: true,
parent: 'apps/my-app/src/main.ts',
name: 'users',
};
const defaultModuleOptions: NgRxGeneratorOptions = {
directory: '+state',
minimal: true,
@ -37,442 +45,546 @@ describe('ngrx', () => {
const expectFileToNotExist = (file: string) =>
expect(tree.exists(file)).not.toBeTruthy();
beforeEach(() => {
jest.clearAllMocks();
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
createApp(tree, 'myapp');
appConfig = getAppConfig();
statePath = `${dirname(appConfig.appModule)}/+state`;
});
it('should error when the module could not be found', async () => {
const modulePath = 'not-existing.module.ts';
await expect(
ngrxGenerator(tree, {
...defaultOptions,
module: modulePath,
})
).rejects.toThrowError(`Module does not exist: ${modulePath}.`);
});
it('should error when the module could not be found using --module', async () => {
const modulePath = 'not-existing.module.ts';
await expect(
ngrxGenerator(tree, {
...defaultOptions,
module: modulePath,
})
).rejects.toThrowError(`Module does not exist: ${modulePath}.`);
});
it('should add an empty root module when minimal and root are set to true', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
root: true,
minimal: true,
describe('NgModule Syntax', () => {
beforeEach(() => {
jest.clearAllMocks();
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
createApp(tree, 'myapp');
appConfig = getAppConfig();
statePath = `${dirname(appConfig.appModule)}/+state`;
});
expect(
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
).toMatchSnapshot();
});
it('should error when the module could not be found', async () => {
const modulePath = 'not-existing.module.ts';
it('should not generate files when minimal and root are set to true', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
root: true,
minimal: true,
await expect(
ngrxGenerator(tree, {
...defaultOptions,
module: modulePath,
})
).rejects.toThrowError(`Module does not exist: ${modulePath}.`);
});
expect(tree.exists('/apps/myapp/src/app/+state/users.actions.ts')).toBe(
false
);
expect(tree.exists('/apps/myapp/src/app/+state/users.effects.ts')).toBe(
false
);
expect(
tree.exists('/apps/myapp/src/app/+state/users.effects.spec.ts')
).toBe(false);
expect(tree.exists('/apps/myapp/src/app/+state/users.reducer.ts')).toBe(
false
);
expect(tree.exists('/apps/myapp/src/app/+state/users.selectors.ts')).toBe(
false
);
expect(
tree.exists('/apps/myapp/src/app/+state/users.selectors.spec.ts')
).toBe(false);
});
it('should error when the module could not be found using --module', async () => {
const modulePath = 'not-existing.module.ts';
it('should add a root module with feature module when minimal is set to false', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
root: true,
minimal: false,
await expect(
ngrxGenerator(tree, {
...defaultOptions,
module: modulePath,
})
).rejects.toThrowError(`Module does not exist: ${modulePath}.`);
});
expect(
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
).toMatchSnapshot();
});
it('should add a root module with feature module when minimal is set to false using --module', async () => {
await ngrxGenerator(tree, {
...defaultModuleOptions,
root: true,
minimal: false,
});
expect(
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
).toMatchSnapshot();
});
it('should not add RouterStoreModule when the module does not reference the router', async () => {
createApp(tree, 'no-router-app', false);
await ngrxGenerator(tree, {
...defaultOptions,
module: 'apps/no-router-app/src/app/app.module.ts',
root: true,
});
const appModule = tree.read(
'/apps/no-router-app/src/app/app.module.ts',
'utf-8'
);
expect(appModule).not.toContain('StoreRouterConnectingModule.forRoot()');
});
it('should add facade provider when facade is true', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
root: true,
minimal: false,
facade: true,
});
expect(tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')).toContain(
'providers: [UsersFacade]'
);
});
it('should not add facade provider when facade is false', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
root: true,
minimal: false,
facade: false,
});
expect(
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
).not.toContain('providers: [UsersFacade]');
});
it('should not add facade provider when minimal is true', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
root: true,
minimal: true,
facade: true,
});
expect(
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
).not.toContain('providers: [UsersFacade]');
});
it('should not generate imports when skipImport is true', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
minimal: false,
skipImport: true,
});
expectFileToExist('/apps/myapp/src/app/+state/users.actions.ts');
expectFileToExist('/apps/myapp/src/app/+state/users.effects.ts');
expectFileToExist('/apps/myapp/src/app/+state/users.effects.spec.ts');
expectFileToExist('/apps/myapp/src/app/+state/users.reducer.ts');
expectFileToExist('/apps/myapp/src/app/+state/users.selectors.ts');
expectFileToExist('/apps/myapp/src/app/+state/users.selectors.spec.ts');
expect(
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
).toMatchSnapshot();
});
it('should update package.json', async () => {
await ngrxGenerator(tree, defaultOptions);
const packageJson = devkit.readJson(tree, 'package.json');
expect(packageJson.dependencies['@ngrx/store']).toEqual(ngrxVersion);
expect(packageJson.dependencies['@ngrx/effects']).toEqual(ngrxVersion);
expect(packageJson.dependencies['@ngrx/entity']).toEqual(ngrxVersion);
expect(packageJson.dependencies['@ngrx/router-store']).toEqual(ngrxVersion);
expect(packageJson.dependencies['@ngrx/component-store']).toEqual(
ngrxVersion
);
expect(packageJson.devDependencies['@ngrx/schematics']).toEqual(
ngrxVersion
);
expect(packageJson.devDependencies['@ngrx/store-devtools']).toEqual(
ngrxVersion
);
expect(packageJson.devDependencies['jasmine-marbles']).toBeDefined();
});
it('should not update package.json when skipPackageJson is true', async () => {
await ngrxGenerator(tree, { ...defaultOptions, skipPackageJson: true });
const packageJson = devkit.readJson(tree, 'package.json');
expect(packageJson.dependencies['@ngrx/store']).toBeUndefined();
expect(packageJson.dependencies['@ngrx/effects']).toBeUndefined();
expect(packageJson.dependencies['@ngrx/entity']).toBeUndefined();
expect(packageJson.dependencies['@ngrx/router-store']).toBeUndefined();
expect(packageJson.dependencies['@ngrx/component-store']).toBeUndefined();
expect(packageJson.devDependencies['@ngrx/schematics']).toBeUndefined();
expect(packageJson.devDependencies['@ngrx/store-devtools']).toBeUndefined();
});
it('should generate files without a facade', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
module: appConfig.appModule,
});
expectFileToExist(`${statePath}/users.actions.ts`);
expectFileToExist(`${statePath}/users.effects.ts`);
expectFileToExist(`${statePath}/users.effects.spec.ts`);
expectFileToExist(`${statePath}/users.models.ts`);
expectFileToExist(`${statePath}/users.reducer.ts`);
expectFileToExist(`${statePath}/users.reducer.spec.ts`);
expectFileToExist(`${statePath}/users.selectors.ts`);
expectFileToExist(`${statePath}/users.selectors.spec.ts`);
expectFileToNotExist(`${statePath}/users.facade.ts`);
expectFileToNotExist(`${statePath}/users.facade.spec.ts`);
});
it('should generate files with a facade', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
module: appConfig.appModule,
facade: true,
});
expectFileToExist(`${statePath}/users.actions.ts`);
expectFileToExist(`${statePath}/users.effects.ts`);
expectFileToExist(`${statePath}/users.effects.spec.ts`);
expectFileToExist(`${statePath}/users.facade.ts`);
expectFileToExist(`${statePath}/users.facade.spec.ts`);
expectFileToExist(`${statePath}/users.models.ts`);
expectFileToExist(`${statePath}/users.reducer.ts`);
expectFileToExist(`${statePath}/users.reducer.spec.ts`);
expectFileToExist(`${statePath}/users.selectors.ts`);
expectFileToExist(`${statePath}/users.selectors.spec.ts`);
});
it('should generate the ngrx actions', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
module: appConfig.appModule,
});
expect(
tree.read(`${statePath}/users.actions.ts`, 'utf-8')
).toMatchSnapshot();
});
it('should generate the ngrx effects', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
module: appConfig.appModule,
});
expect(
tree.read(`${statePath}/users.effects.ts`, 'utf-8')
).toMatchSnapshot();
});
it('should generate the ngrx facade', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
module: appConfig.appModule,
facade: true,
});
expect(
tree.read(`${statePath}/users.facade.ts`, 'utf-8')
).toMatchSnapshot();
});
it('should generate a models file for the feature', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
module: appConfig.appModule,
minimal: false,
});
expect(
tree.read(`${statePath}/users.models.ts`, 'utf-8')
).toMatchSnapshot();
});
it('should generate the ngrx reducer', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
module: appConfig.appModule,
});
expect(
tree.read(`${statePath}/users.reducer.ts`, 'utf-8')
).toMatchSnapshot();
});
it('should generate the ngrx selectors', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
module: appConfig.appModule,
});
expect(
tree.read(`${statePath}/users.selectors.ts`, 'utf-8')
).toMatchSnapshot();
});
it('should generate with custom directory', async () => {
statePath = '/apps/myapp/src/app/my-custom-directory';
await ngrxGenerator(tree, {
...defaultOptions,
directory: 'my-custom-directory',
minimal: false,
facade: true,
});
expectFileToExist(`${statePath}/users.actions.ts`);
expectFileToExist(`${statePath}/users.effects.ts`);
expectFileToExist(`${statePath}/users.effects.spec.ts`);
expectFileToExist(`${statePath}/users.facade.ts`);
expectFileToExist(`${statePath}/users.facade.spec.ts`);
expectFileToExist(`${statePath}/users.models.ts`);
expectFileToExist(`${statePath}/users.reducer.ts`);
expectFileToExist(`${statePath}/users.reducer.spec.ts`);
expectFileToExist(`${statePath}/users.selectors.ts`);
expectFileToExist(`${statePath}/users.selectors.spec.ts`);
});
it('should update the entry point file with the right exports', async () => {
createLib(tree, 'flights');
let libConfig = getLibConfig();
await ngrxGenerator(tree, {
...defaultOptions,
name: 'super-users',
module: libConfig.module,
facade: true,
});
expect(tree.read(libConfig.barrel, 'utf-8')).toMatchSnapshot();
});
it('should update the entry point file correctly when barrels is true', async () => {
createLib(tree, 'flights');
let libConfig = getLibConfig();
await ngrxGenerator(tree, {
...defaultOptions,
name: 'super-users',
module: libConfig.module,
facade: true,
barrels: true,
});
expect(tree.read(libConfig.barrel, 'utf-8')).toMatchSnapshot();
});
it('should update the entry point file with no facade', async () => {
createLib(tree, 'flights');
let libConfig = getLibConfig();
await ngrxGenerator(tree, {
...defaultOptions,
name: 'super-users',
module: libConfig.module,
facade: false,
});
expect(tree.read(libConfig.barrel, 'utf-8')).toMatchSnapshot();
});
it('should format files', async () => {
jest.spyOn(devkit, 'formatFiles');
await ngrxGenerator(tree, defaultOptions);
expect(devkit.formatFiles).toHaveBeenCalled();
});
it('should not format files when skipFormat is true', async () => {
jest.spyOn(devkit, 'formatFiles');
await ngrxGenerator(tree, { ...defaultOptions, skipFormat: true });
expect(devkit.formatFiles).not.toHaveBeenCalled();
});
describe('generated unit tests', () => {
it('should generate specs for the ngrx effects', async () => {
it('should add an empty root module when minimal and root are set to true', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
name: 'super-users',
module: appConfig.appModule,
root: true,
minimal: true,
});
expect(
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
).toMatchSnapshot();
});
it('should not generate files when minimal and root are set to true', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
root: true,
minimal: true,
});
expect(tree.exists('/apps/myapp/src/app/+state/users.actions.ts')).toBe(
false
);
expect(tree.exists('/apps/myapp/src/app/+state/users.effects.ts')).toBe(
false
);
expect(
tree.exists('/apps/myapp/src/app/+state/users.effects.spec.ts')
).toBe(false);
expect(tree.exists('/apps/myapp/src/app/+state/users.reducer.ts')).toBe(
false
);
expect(tree.exists('/apps/myapp/src/app/+state/users.selectors.ts')).toBe(
false
);
expect(
tree.exists('/apps/myapp/src/app/+state/users.selectors.spec.ts')
).toBe(false);
});
it('should add a root module with feature module when minimal is set to false', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
root: true,
minimal: false,
});
expect(
tree.read(`${statePath}/super-users.effects.spec.ts`, 'utf-8')
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
).toMatchSnapshot();
});
it('should generate specs for the ngrx facade', async () => {
it('should add a root module with feature module when minimal is set to false using --module', async () => {
await ngrxGenerator(tree, {
...defaultModuleOptions,
root: true,
minimal: false,
});
expect(
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
).toMatchSnapshot();
});
it('should not add RouterStoreModule when the module does not reference the router', async () => {
createApp(tree, 'no-router-app', false);
await ngrxGenerator(tree, {
...defaultOptions,
name: 'super-users',
module: appConfig.appModule,
module: 'apps/no-router-app/src/app/app.module.ts',
root: true,
});
const appModule = tree.read(
'/apps/no-router-app/src/app/app.module.ts',
'utf-8'
);
expect(appModule).not.toContain('StoreRouterConnectingModule.forRoot()');
});
it('should add facade provider when facade is true', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
root: true,
minimal: false,
facade: true,
});
expect(tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')).toContain(
'providers: [UsersFacade]'
);
});
it('should not add facade provider when facade is false', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
root: true,
minimal: false,
facade: false,
});
expect(
tree.read(`${statePath}/super-users.facade.spec.ts`, 'utf-8')
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
).not.toContain('providers: [UsersFacade]');
});
it('should not add facade provider when minimal is true', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
root: true,
minimal: true,
facade: true,
});
expect(
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
).not.toContain('providers: [UsersFacade]');
});
it('should not generate imports when skipImport is true', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
minimal: false,
skipImport: true,
});
expectFileToExist('/apps/myapp/src/app/+state/users.actions.ts');
expectFileToExist('/apps/myapp/src/app/+state/users.effects.ts');
expectFileToExist('/apps/myapp/src/app/+state/users.effects.spec.ts');
expectFileToExist('/apps/myapp/src/app/+state/users.reducer.ts');
expectFileToExist('/apps/myapp/src/app/+state/users.selectors.ts');
expectFileToExist('/apps/myapp/src/app/+state/users.selectors.spec.ts');
expect(
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
).toMatchSnapshot();
});
it('should generate specs for the ngrx reducer', async () => {
it('should update package.json', async () => {
await ngrxGenerator(tree, defaultOptions);
const packageJson = devkit.readJson(tree, 'package.json');
expect(packageJson.dependencies['@ngrx/store']).toEqual(ngrxVersion);
expect(packageJson.dependencies['@ngrx/effects']).toEqual(ngrxVersion);
expect(packageJson.dependencies['@ngrx/entity']).toEqual(ngrxVersion);
expect(packageJson.dependencies['@ngrx/router-store']).toEqual(
ngrxVersion
);
expect(packageJson.dependencies['@ngrx/component-store']).toEqual(
ngrxVersion
);
expect(packageJson.devDependencies['@ngrx/schematics']).toEqual(
ngrxVersion
);
expect(packageJson.devDependencies['@ngrx/store-devtools']).toEqual(
ngrxVersion
);
expect(packageJson.devDependencies['jasmine-marbles']).toBeDefined();
});
it('should not update package.json when skipPackageJson is true', async () => {
await ngrxGenerator(tree, { ...defaultOptions, skipPackageJson: true });
const packageJson = devkit.readJson(tree, 'package.json');
expect(packageJson.dependencies['@ngrx/store']).toBeUndefined();
expect(packageJson.dependencies['@ngrx/effects']).toBeUndefined();
expect(packageJson.dependencies['@ngrx/entity']).toBeUndefined();
expect(packageJson.dependencies['@ngrx/router-store']).toBeUndefined();
expect(packageJson.dependencies['@ngrx/component-store']).toBeUndefined();
expect(packageJson.devDependencies['@ngrx/schematics']).toBeUndefined();
expect(
packageJson.devDependencies['@ngrx/store-devtools']
).toBeUndefined();
});
it('should generate files without a facade', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
module: appConfig.appModule,
});
expectFileToExist(`${statePath}/users.actions.ts`);
expectFileToExist(`${statePath}/users.effects.ts`);
expectFileToExist(`${statePath}/users.effects.spec.ts`);
expectFileToExist(`${statePath}/users.models.ts`);
expectFileToExist(`${statePath}/users.reducer.ts`);
expectFileToExist(`${statePath}/users.reducer.spec.ts`);
expectFileToExist(`${statePath}/users.selectors.ts`);
expectFileToExist(`${statePath}/users.selectors.spec.ts`);
expectFileToNotExist(`${statePath}/users.facade.ts`);
expectFileToNotExist(`${statePath}/users.facade.spec.ts`);
});
it('should generate files with a facade', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
module: appConfig.appModule,
facade: true,
});
expectFileToExist(`${statePath}/users.actions.ts`);
expectFileToExist(`${statePath}/users.effects.ts`);
expectFileToExist(`${statePath}/users.effects.spec.ts`);
expectFileToExist(`${statePath}/users.facade.ts`);
expectFileToExist(`${statePath}/users.facade.spec.ts`);
expectFileToExist(`${statePath}/users.models.ts`);
expectFileToExist(`${statePath}/users.reducer.ts`);
expectFileToExist(`${statePath}/users.reducer.spec.ts`);
expectFileToExist(`${statePath}/users.selectors.ts`);
expectFileToExist(`${statePath}/users.selectors.spec.ts`);
});
it('should generate the ngrx actions', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
module: appConfig.appModule,
});
expect(
tree.read(`${statePath}/users.actions.ts`, 'utf-8')
).toMatchSnapshot();
});
it('should generate the ngrx effects', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
module: appConfig.appModule,
});
expect(
tree.read(`${statePath}/users.effects.ts`, 'utf-8')
).toMatchSnapshot();
});
it('should generate the ngrx facade', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
module: appConfig.appModule,
facade: true,
});
expect(
tree.read(`${statePath}/users.facade.ts`, 'utf-8')
).toMatchSnapshot();
});
it('should generate a models file for the feature', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
name: 'super-users',
module: appConfig.appModule,
minimal: false,
});
expect(
tree.read(`${statePath}/super-users.reducer.spec.ts`, 'utf-8')
tree.read(`${statePath}/users.models.ts`, 'utf-8')
).toMatchSnapshot();
});
it('should generate specs for the ngrx selectors', async () => {
it('should generate the ngrx reducer', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
name: 'super-users',
module: appConfig.appModule,
minimal: false,
});
expect(
tree.read(`${statePath}/super-users.selectors.spec.ts`, 'utf-8')
tree.read(`${statePath}/users.reducer.ts`, 'utf-8')
).toMatchSnapshot();
});
it('should generate the ngrx selectors', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
module: appConfig.appModule,
});
expect(
tree.read(`${statePath}/users.selectors.ts`, 'utf-8')
).toMatchSnapshot();
});
it('should generate with custom directory', async () => {
statePath = '/apps/myapp/src/app/my-custom-directory';
await ngrxGenerator(tree, {
...defaultOptions,
directory: 'my-custom-directory',
minimal: false,
facade: true,
});
expectFileToExist(`${statePath}/users.actions.ts`);
expectFileToExist(`${statePath}/users.effects.ts`);
expectFileToExist(`${statePath}/users.effects.spec.ts`);
expectFileToExist(`${statePath}/users.facade.ts`);
expectFileToExist(`${statePath}/users.facade.spec.ts`);
expectFileToExist(`${statePath}/users.models.ts`);
expectFileToExist(`${statePath}/users.reducer.ts`);
expectFileToExist(`${statePath}/users.reducer.spec.ts`);
expectFileToExist(`${statePath}/users.selectors.ts`);
expectFileToExist(`${statePath}/users.selectors.spec.ts`);
});
it('should update the entry point file with the right exports', async () => {
createLib(tree, 'flights');
let libConfig = getLibConfig();
await ngrxGenerator(tree, {
...defaultOptions,
name: 'super-users',
module: libConfig.module,
facade: true,
});
expect(tree.read(libConfig.barrel, 'utf-8')).toMatchSnapshot();
});
it('should update the entry point file correctly when barrels is true', async () => {
createLib(tree, 'flights');
let libConfig = getLibConfig();
await ngrxGenerator(tree, {
...defaultOptions,
name: 'super-users',
module: libConfig.module,
facade: true,
barrels: true,
});
expect(tree.read(libConfig.barrel, 'utf-8')).toMatchSnapshot();
});
it('should update the entry point file with no facade', async () => {
createLib(tree, 'flights');
let libConfig = getLibConfig();
await ngrxGenerator(tree, {
...defaultOptions,
name: 'super-users',
module: libConfig.module,
facade: false,
});
expect(tree.read(libConfig.barrel, 'utf-8')).toMatchSnapshot();
});
it('should format files', async () => {
jest.spyOn(devkit, 'formatFiles');
await ngrxGenerator(tree, defaultOptions);
expect(devkit.formatFiles).toHaveBeenCalled();
});
it('should not format files when skipFormat is true', async () => {
jest.spyOn(devkit, 'formatFiles');
await ngrxGenerator(tree, { ...defaultOptions, skipFormat: true });
expect(devkit.formatFiles).not.toHaveBeenCalled();
});
describe('generated unit tests', () => {
it('should generate specs for the ngrx effects', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
name: 'super-users',
module: appConfig.appModule,
minimal: false,
});
expect(
tree.read(`${statePath}/super-users.effects.spec.ts`, 'utf-8')
).toMatchSnapshot();
});
it('should generate specs for the ngrx facade', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
name: 'super-users',
module: appConfig.appModule,
minimal: false,
facade: true,
});
expect(
tree.read(`${statePath}/super-users.facade.spec.ts`, 'utf-8')
).toMatchSnapshot();
});
it('should generate specs for the ngrx reducer', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
name: 'super-users',
module: appConfig.appModule,
minimal: false,
});
expect(
tree.read(`${statePath}/super-users.reducer.spec.ts`, 'utf-8')
).toMatchSnapshot();
});
it('should generate specs for the ngrx selectors', async () => {
await ngrxGenerator(tree, {
...defaultOptions,
name: 'super-users',
module: appConfig.appModule,
minimal: false,
});
expect(
tree.read(`${statePath}/super-users.selectors.spec.ts`, 'utf-8')
).toMatchSnapshot();
});
});
});
describe('Standalone APIs', () => {
beforeEach(async () => {
jest.clearAllMocks();
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await applicationGenerator(tree, {
name: 'my-app',
standalone: true,
routing: true,
});
tree.write(
'apps/my-app/src/app/app.component.html',
'<router-outlet></router-outlet>'
);
tree.write(
'apps/my-app/src/app/app.routes.ts',
`import { Routes } from '@angular/router';
import { NxWelcomeComponent } from './nx-welcome.component';
export const appRoutes: Routes = [{ path: '', component: NxWelcomeComponent }];`
);
});
it('should throw when the parent cannot be found', async () => {
// ARRANGE
const parentPath = 'apps/my-app/src/app/non-existent.routes.ts';
// ACT & ASSERT
await expect(
ngrxGenerator(tree, {
...defaultStandaloneOptions,
parent: parentPath,
})
).rejects.toThrowError(`Parent does not exist: ${parentPath}.`);
});
it('should add an empty provideStore when minimal and root are set to true', async () => {
await ngrxGenerator(tree, {
...defaultStandaloneOptions,
root: true,
minimal: true,
});
expect(tree.read('/apps/my-app/src/main.ts', 'utf-8')).toMatchSnapshot();
expect(tree.exists('/apps/my-app/src/app/+state/users.actions.ts')).toBe(
false
);
expect(tree.exists('/apps/my-app/src/app/+state/users.effects.ts')).toBe(
false
);
expect(
tree.exists('/apps/my-app/src/app/+state/users.effects.spec.ts')
).toBe(false);
expect(tree.exists('/apps/my-app/src/app/+state/users.reducer.ts')).toBe(
false
);
expect(
tree.exists('/apps/my-app/src/app/+state/users.selectors.ts')
).toBe(false);
expect(
tree.exists('/apps/my-app/src/app/+state/users.selectors.spec.ts')
).toBe(false);
});
it('should add a root module with feature module when minimal is set to false', async () => {
await ngrxGenerator(tree, {
...defaultStandaloneOptions,
root: true,
minimal: false,
});
expect(tree.read('/apps/my-app/src/main.ts', 'utf-8')).toMatchSnapshot();
});
it('should add facade provider when facade is true', async () => {
await ngrxGenerator(tree, {
...defaultStandaloneOptions,
root: true,
minimal: false,
facade: true,
});
expect(tree.read('/apps/my-app/src/main.ts', 'utf-8')).toMatchSnapshot();
});
it('should add facade provider when facade is true and --root is false', async () => {
await ngrxGenerator(tree, {
...defaultStandaloneOptions,
root: false,
minimal: false,
facade: true,
parent: 'apps/my-app/src/app/app.routes.ts',
});
expect(
tree.read('/apps/my-app/src/app/app.routes.ts', 'utf-8')
).toMatchSnapshot();
});
});

View File

@ -3,6 +3,7 @@ export interface NgRxGeneratorOptions {
minimal: boolean;
module?: string;
parent?: string;
route?: string;
name: string;
barrels?: boolean;
facade?: boolean;

View File

@ -28,13 +28,17 @@
"module": {
"type": "string",
"description": "The path to the `NgModule` where the feature state will be registered. The host directory will create/use the new state directory.",
"x-prompt": "What is the path to the module where this NgRx state should be registered?",
"x-deprecated": "This option will be removed in a future version of Nx. Please switch to using --parent instead."
},
"parent": {
"type": "string",
"description": "The path to the `NgModule` where the feature state will be registered. The host directory will create/use the new state directory.",
"x-prompt": "What is the path to the module where this NgRx state should be registered?"
"description": "The path to the `NgModule` or the `Routes` definition file (for Standalone API usage) where the feature state will be registered. The host directory will create/use the new state directory.",
"x-prompt": "What is the path to the module or Routes definition where this NgRx state should be registered?"
},
"route": {
"type": "string",
"description": "The route that the Standalone NgRx Providers should be added to.",
"default": "''"
},
"directory": {
"type": "string",

View File

@ -653,6 +653,22 @@ export function addProviderToModule(
);
}
export function addProviderToComponent(
host: Tree,
source: ts.SourceFile,
componentPath: string,
symbolName: string
): ts.SourceFile {
return _addSymbolToDecoratorMetadata(
host,
source,
componentPath,
'providers',
symbolName,
'Component'
);
}
export function addDeclarationToModule(
host: Tree,
source: ts.SourceFile,