feat(angular): add parent flag to ngrx generator (#14105)

This commit is contained in:
Colum Ferry 2023-01-03 13:35:53 +00:00 committed by GitHub
parent 53cffacf6b
commit 16a0891d30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 172 additions and 50 deletions

View File

@ -26,6 +26,12 @@
"x-prompt": "What name would you like to use for the NgRx feature state? An example would be `users`."
},
"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?"
@ -74,7 +80,7 @@
}
},
"additionalProperties": false,
"required": ["module", "name"],
"required": ["name"],
"presets": []
},
"description": "Adds NgRx support to an application or library.",

View File

@ -27,7 +27,7 @@ describe('Angular Package', () => {
// Generate root ngrx state management
runCLI(
`generate @nrwl/angular:ngrx users --module=apps/${myapp}/src/app/app.module.ts --root --minimal=false`
`generate @nrwl/angular:ngrx users --parent=apps/${myapp}/src/app/app.module.ts --root --minimal=false`
);
const packageJson = readJson('package.json');
expect(packageJson.dependencies['@ngrx/store']).toBeDefined();
@ -39,7 +39,7 @@ describe('Angular Package', () => {
// Generate feature library and ngrx state within that library
runCLI(`g @nrwl/angular:lib ${mylib} --prefix=fl`);
runCLI(
`generate @nrwl/angular:ngrx flights --module=libs/${mylib}/src/lib/${mylib}.module.ts --facade`
`generate @nrwl/angular:ngrx flights --parent=libs/${mylib}/src/lib/${mylib}.module.ts --facade`
);
expect(runCLI(`build ${myapp}`)).toMatch(/main\.[a-z0-9]+\.js/);
@ -56,7 +56,40 @@ describe('Angular Package', () => {
// Generate root ngrx state management
runCLI(
`generate @nrwl/angular:ngrx users --module=apps/${myapp}/src/app/app.module.ts --root`
`generate @nrwl/angular:ngrx users --parent=apps/${myapp}/src/app/app.module.ts --root`
);
const packageJson = readJson('package.json');
expect(packageJson.dependencies['@ngrx/entity']).toBeDefined();
expect(packageJson.dependencies['@ngrx/store']).toBeDefined();
expect(packageJson.dependencies['@ngrx/effects']).toBeDefined();
expect(packageJson.dependencies['@ngrx/router-store']).toBeDefined();
expect(packageJson.devDependencies['@ngrx/schematics']).toBeDefined();
expect(packageJson.devDependencies['@ngrx/store-devtools']).toBeDefined();
const mylib = uniq('mylib');
// Generate feature library and ngrx state within that library
runCLI(`g @nrwl/angular:lib ${mylib} --prefix=fl`);
const flags = `--facade --barrels`;
runCLI(
`generate @nrwl/angular:ngrx flights --parent=libs/${mylib}/src/lib/${mylib}.module.ts ${flags}`
);
expect(runCLI(`build ${myapp}`)).toMatch(/main\.[a-z0-9]+\.js/);
expectTestsPass(await runCLIAsync(`test ${myapp} --no-watch`));
// TODO: remove this condition
if (getSelectedPackageManager() !== 'pnpm') {
expectTestsPass(await runCLIAsync(`test ${mylib} --no-watch`));
}
}, 1000000);
it('should work with creators using --module', async () => {
const myapp = uniq('myapp');
runCLI(`generate @nrwl/angular:app ${myapp} --routing --no-interactive`);
// Generate root ngrx state management
runCLI(
`generate @nrwl/angular:ngrx users --parent=apps/${myapp}/src/app/app.module.ts --root`
);
const packageJson = readJson('package.json');
expect(packageJson.dependencies['@ngrx/entity']).toBeDefined();

View File

@ -273,6 +273,32 @@ 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`] = `
"
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import * as fromUsers from './+state/users.reducer';
import { UsersEffects } from './+state/users.effects';
import { StoreRouterConnectingModule } from '@ngrx/router-store';
@NgModule({
imports: [BrowserModule, RouterModule.forRoot([]), StoreModule.forRoot({}, {
metaReducers: [],
runtimeChecks: {
strictActionImmutability: true,
strictStateImmutability: true
}
}), EffectsModule.forRoot([UsersEffects]), StoreRouterConnectingModule.forRoot(), StoreModule.forFeature(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer)],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {}
"
`;
exports[`ngrx should add an empty root module when minimal and root are set to true 1`] = `
"
import { NgModule } from '@angular/core';

View File

@ -1,16 +1,15 @@
import type { Tree } from '@nrwl/devkit';
import { joinPathFragments, names } from '@nrwl/devkit';
import { addGlobal } from '@nrwl/workspace/src/utilities/ast-utils';
import { dirname } from 'path';
import { createSourceFile, ScriptTarget } from 'typescript';
import type { NgRxGeneratorOptions } from '../schema';
import type { NormalizedNgRxGeneratorOptions } from './normalize-options';
/**
* Add ngrx feature exports to the public barrel in the feature library
*/
export function addExportsToBarrel(
tree: Tree,
options: NgRxGeneratorOptions
options: NormalizedNgRxGeneratorOptions
): void {
// Don't update the public barrel for the root state, only for feature states
if (options.root) {
@ -18,7 +17,7 @@ export function addExportsToBarrel(
}
const indexFilePath = joinPathFragments(
dirname(options.module),
options.parentDirectory,
'..',
'index.ts'
);

View File

@ -7,16 +7,16 @@ import {
addImportToModule,
addProviderToModule,
} from '../../../utils/nx-devkit/ast-utils';
import type { NgRxGeneratorOptions } from '../schema';
import type { NormalizedNgRxGeneratorOptions } from './normalize-options';
export function addImportsToModule(
tree: Tree,
options: NgRxGeneratorOptions
options: NormalizedNgRxGeneratorOptions
): void {
const modulePath = options.module;
const sourceText = tree.read(modulePath, 'utf-8');
const parentPath = options.module ?? options.parent;
const sourceText = tree.read(parentPath, 'utf-8');
let sourceFile = createSourceFile(
modulePath,
parentPath,
sourceText,
ScriptTarget.Latest,
true
@ -30,7 +30,7 @@ export function addImportsToModule(
return insertImport(
tree,
source,
modulePath,
parentPath,
symbolName,
fileName,
isDefault
@ -72,11 +72,11 @@ export function addImportsToModule(
sourceFile = addImport(sourceFile, 'EffectsModule', '@ngrx/effects');
if (options.minimal && options.root) {
sourceFile = addImportToModule(tree, sourceFile, modulePath, storeForRoot);
sourceFile = addImportToModule(tree, sourceFile, parentPath, storeForRoot);
sourceFile = addImportToModule(
tree,
sourceFile,
modulePath,
parentPath,
effectsForEmptyRoot
);
@ -89,7 +89,7 @@ export function addImportsToModule(
sourceFile = addImportToModule(
tree,
sourceFile,
modulePath,
parentPath,
storeRouterModule
);
}
@ -103,7 +103,7 @@ export function addImportsToModule(
sourceFile = addProviderToModule(
tree,
sourceFile,
modulePath,
parentPath,
facadeName
);
}
@ -117,13 +117,13 @@ export function addImportsToModule(
sourceFile = addImportToModule(
tree,
sourceFile,
modulePath,
parentPath,
storeForRoot
);
sourceFile = addImportToModule(
tree,
sourceFile,
modulePath,
parentPath,
effectsForRoot
);
@ -136,7 +136,7 @@ export function addImportsToModule(
sourceFile = addImportToModule(
tree,
sourceFile,
modulePath,
parentPath,
storeRouterModule
);
}
@ -144,7 +144,7 @@ export function addImportsToModule(
sourceFile = addImportToModule(
tree,
sourceFile,
modulePath,
parentPath,
storeForFeature
);
} else {
@ -153,13 +153,13 @@ export function addImportsToModule(
sourceFile = addImportToModule(
tree,
sourceFile,
modulePath,
parentPath,
storeForFeature
);
sourceFile = addImportToModule(
tree,
sourceFile,
modulePath,
parentPath,
effectsForFeature
);
}

View File

@ -1,36 +1,39 @@
import type { Tree } from '@nrwl/devkit';
import { generateFiles, joinPathFragments, names } from '@nrwl/devkit';
import { dirname } from 'path';
import type { NgRxGeneratorOptions } from '../schema';
import { NormalizedNgRxGeneratorOptions } from './normalize-options';
/**
* Generate 'feature' scaffolding: actions, reducer, effects, interfaces, selectors, facade
*/
export function generateNgrxFilesFromTemplates(
tree: Tree,
options: NgRxGeneratorOptions
options: NormalizedNgRxGeneratorOptions
): void {
const name = options.name;
const moduleDir = dirname(options.module);
const projectNames = names(name);
generateFiles(tree, joinPathFragments(__dirname, '..', 'files'), moduleDir, {
...options,
...projectNames,
tmpl: '',
});
generateFiles(
tree,
joinPathFragments(__dirname, '..', 'files'),
options.parentDirectory,
{
...options,
...projectNames,
tmpl: '',
}
);
if (!options.facade) {
tree.delete(
joinPathFragments(
moduleDir,
options.parentDirectory,
options.directory,
`${projectNames.fileName}.facade.ts`
)
);
tree.delete(
joinPathFragments(
moduleDir,
options.parentDirectory,
options.directory,
`${projectNames.fileName}.facade.spec.ts`
)

View File

@ -1,11 +1,21 @@
import { names } from '@nrwl/devkit';
import type { NgRxGeneratorOptions } from '../schema';
import { dirname } from 'path';
export type NormalizedNgRxGeneratorOptions = NgRxGeneratorOptions & {
parentDirectory: string;
};
export function normalizeOptions(
options: NgRxGeneratorOptions
): NgRxGeneratorOptions {
): NormalizedNgRxGeneratorOptions {
return {
...options,
parentDirectory: options.module
? dirname(options.module)
: options.parent
? dirname(options.parent)
: undefined,
directory: names(options.directory).fileName,
};
}

View File

@ -19,6 +19,13 @@ describe('ngrx', () => {
let tree: Tree;
const defaultOptions: NgRxGeneratorOptions = {
directory: '+state',
minimal: true,
parent: 'apps/myapp/src/app/app.module.ts',
name: 'users',
};
const defaultModuleOptions: NgRxGeneratorOptions = {
directory: '+state',
minimal: true,
module: 'apps/myapp/src/app/app.module.ts',
@ -49,6 +56,17 @@ describe('ngrx', () => {
).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,
@ -100,6 +118,18 @@ describe('ngrx', () => {
).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);

View File

@ -11,29 +11,37 @@ import type { NgRxGeneratorOptions } from './schema';
export async function ngrxGenerator(
tree: Tree,
options: NgRxGeneratorOptions
schema: NgRxGeneratorOptions
): Promise<GeneratorCallback> {
const normalizedOptions = normalizeOptions(options);
if (!tree.exists(normalizedOptions.module)) {
throw new Error(`Module does not exist: ${normalizedOptions.module}.`);
if (!schema.module && !schema.parent) {
throw new Error('Please provide a value for `--parent`!');
}
if (!normalizedOptions.minimal || !normalizedOptions.root) {
generateNgrxFilesFromTemplates(tree, normalizedOptions);
if (schema.module && !tree.exists(schema.module)) {
throw new Error(`Module does not exist: ${schema.module}.`);
}
if (!normalizedOptions.skipImport) {
addImportsToModule(tree, normalizedOptions);
addExportsToBarrel(tree, normalizedOptions);
if (schema.parent && !tree.exists(schema.parent)) {
throw new Error(`Parent does not exist: ${schema.parent}.`);
}
const options = normalizeOptions(schema);
if (!options.minimal || !options.root) {
generateNgrxFilesFromTemplates(tree, options);
}
if (!options.skipImport) {
addImportsToModule(tree, options);
addExportsToBarrel(tree, options);
}
let packageInstallationTask: GeneratorCallback = () => {};
if (!normalizedOptions.skipPackageJson) {
if (!options.skipPackageJson) {
packageInstallationTask = addNgRxToPackageJson(tree);
}
if (!normalizedOptions.skipFormat) {
if (!options.skipFormat) {
await formatFiles(tree);
}

View File

@ -1,7 +1,8 @@
export interface NgRxGeneratorOptions {
directory: string;
minimal: boolean;
module: string;
module?: string;
parent?: string;
name: string;
barrels?: boolean;
facade?: boolean;

View File

@ -26,6 +26,12 @@
"x-prompt": "What name would you like to use for the NgRx feature state? An example would be `users`."
},
"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?"
@ -74,5 +80,5 @@
}
},
"additionalProperties": false,
"required": ["module", "name"]
"required": ["name"]
}