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

View File

@ -27,7 +27,7 @@ describe('Angular Package', () => {
// Generate root ngrx state management // Generate root ngrx state management
runCLI( 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'); const packageJson = readJson('package.json');
expect(packageJson.dependencies['@ngrx/store']).toBeDefined(); expect(packageJson.dependencies['@ngrx/store']).toBeDefined();
@ -39,7 +39,7 @@ describe('Angular Package', () => {
// Generate feature library and ngrx state within that library // Generate feature library and ngrx state within that library
runCLI(`g @nrwl/angular:lib ${mylib} --prefix=fl`); runCLI(`g @nrwl/angular:lib ${mylib} --prefix=fl`);
runCLI( 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/); expect(runCLI(`build ${myapp}`)).toMatch(/main\.[a-z0-9]+\.js/);
@ -56,7 +56,40 @@ describe('Angular Package', () => {
// Generate root ngrx state management // Generate root ngrx state management
runCLI( 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'); const packageJson = readJson('package.json');
expect(packageJson.dependencies['@ngrx/entity']).toBeDefined(); 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`] = ` exports[`ngrx should add an empty root module when minimal and root are set to true 1`] = `
" "
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';

View File

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

View File

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

View File

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

View File

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

View File

@ -19,6 +19,13 @@ describe('ngrx', () => {
let tree: Tree; let tree: Tree;
const defaultOptions: NgRxGeneratorOptions = { const defaultOptions: NgRxGeneratorOptions = {
directory: '+state',
minimal: true,
parent: 'apps/myapp/src/app/app.module.ts',
name: 'users',
};
const defaultModuleOptions: NgRxGeneratorOptions = {
directory: '+state', directory: '+state',
minimal: true, minimal: true,
module: 'apps/myapp/src/app/app.module.ts', module: 'apps/myapp/src/app/app.module.ts',
@ -49,6 +56,17 @@ describe('ngrx', () => {
).rejects.toThrowError(`Module does not exist: ${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 () => { it('should add an empty root module when minimal and root are set to true', async () => {
await ngrxGenerator(tree, { await ngrxGenerator(tree, {
...defaultOptions, ...defaultOptions,
@ -100,6 +118,18 @@ describe('ngrx', () => {
).toMatchSnapshot(); ).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 () => { it('should not add RouterStoreModule when the module does not reference the router', async () => {
createApp(tree, 'no-router-app', false); createApp(tree, 'no-router-app', false);

View File

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

View File

@ -1,7 +1,8 @@
export interface NgRxGeneratorOptions { export interface NgRxGeneratorOptions {
directory: string; directory: string;
minimal: boolean; minimal: boolean;
module: string; module?: string;
parent?: string;
name: string; name: string;
barrels?: boolean; barrels?: boolean;
facade?: 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`." "x-prompt": "What name would you like to use for the NgRx feature state? An example would be `users`."
}, },
"module": { "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", "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.", "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-prompt": "What is the path to the module where this NgRx state should be registered?"
@ -74,5 +80,5 @@
} }
}, },
"additionalProperties": false, "additionalProperties": false,
"required": ["module", "name"] "required": ["name"]
} }