From 818d04cb9a4eb773957d297c787505307142068c Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 31 Aug 2023 12:02:15 +0100 Subject: [PATCH] fix(angular): ngrx-feature-store should respect paths in names #18905 (#18913) --- .../ngrx-feature-store.spec.ts.snap | 194 ++++++++++++++++++ .../__fileName__.actions.ts__tmpl__ | 2 +- .../__fileName__.effects.spec.ts__tmpl__ | 4 +- .../__fileName__.effects.ts__tmpl__ | 4 +- .../__fileName__.facade.spec.ts__tmpl__ | 12 +- .../__fileName__.facade.ts__tmpl__ | 6 +- .../__fileName__.reducer.spec.ts__tmpl__ | 6 +- .../__fileName__.reducer.ts__tmpl__ | 4 +- .../__fileName__.selectors.spec.ts__tmpl__ | 6 +- .../__fileName__.selectors.ts__tmpl__ | 2 +- .../__fileName__.effects.ts__tmpl__ | 4 +- .../__fileName__.facade.ts__tmpl__ | 6 +- .../lib/add-exports-barrel.ts | 5 +- .../ngrx-feature-store/lib/add-imports.ts | 4 +- .../ngrx-feature-store/lib/generate-files.ts | 15 +- .../lib/normalize-options.ts | 18 +- .../ngrx-feature-store.spec.ts | 51 +++++ 17 files changed, 310 insertions(+), 33 deletions(-) diff --git a/packages/angular/src/generators/ngrx-feature-store/__snapshots__/ngrx-feature-store.spec.ts.snap b/packages/angular/src/generators/ngrx-feature-store/__snapshots__/ngrx-feature-store.spec.ts.snap index 5909e0f5aa892..02982cad7d078 100644 --- a/packages/angular/src/generators/ngrx-feature-store/__snapshots__/ngrx-feature-store.spec.ts.snap +++ b/packages/angular/src/generators/ngrx-feature-store/__snapshots__/ngrx-feature-store.spec.ts.snap @@ -1,5 +1,199 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`ngrx-feature-store NgModule should generate into a subdirectory correctly when a path is passed as the name 1`] = ` +"export * from './lib/+state/users/users.facade'; +export * from './lib/+state/users/users.models'; +export * from './lib/+state/users/users.selectors'; +export * from './lib/+state/users/users.reducer'; +export * from './lib/+state/users/users.actions'; +export * from './lib/feature-module.module'; +" +`; + +exports[`ngrx-feature-store NgModule should generate into a subdirectory correctly when a path is passed as the name 2`] = ` +"import { createAction, props } from '@ngrx/store'; +import { UsersEntity } from './users.models'; + +export const initUsers = createAction('[Users Page] Init'); + +export const loadUsersSuccess = createAction( + '[Users/API] Load Users Success', + props<{ users: UsersEntity[] }>() +); + +export const loadUsersFailure = createAction( + '[Users/API] Load Users Failure', + props<{ error: any }>() +); +" +`; + +exports[`ngrx-feature-store NgModule should generate into a subdirectory correctly when a path is passed as the name 3`] = ` +"import { Injectable, inject } from '@angular/core'; +import { createEffect, Actions, ofType } from '@ngrx/effects'; +import { switchMap, catchError, of } from 'rxjs'; +import * as UsersActions from './users.actions'; +import * as UsersFeature from './users.reducer'; + +@Injectable() +export class UsersEffects { + private actions$ = inject(Actions); + + init$ = createEffect(() => + this.actions$.pipe( + ofType(UsersActions.initUsers), + switchMap(() => of(UsersActions.loadUsersSuccess({ users: [] }))), + catchError((error) => { + console.error('Error', error); + return of(UsersActions.loadUsersFailure({ error })); + }) + ) + ); +} +" +`; + +exports[`ngrx-feature-store NgModule should generate into a subdirectory correctly when a path is passed as the name 4`] = ` +"import { Injectable, inject } from '@angular/core'; +import { select, Store, Action } from '@ngrx/store'; + +import * as UsersActions from './users.actions'; +import * as UsersFeature from './users.reducer'; +import * as UsersSelectors from './users.selectors'; + +@Injectable() +export class UsersFacade { + private readonly store = inject(Store); + + /** + * Combine pieces of state using createSelector, + * and expose them as observables through the facade. + */ + loaded$ = this.store.pipe(select(UsersSelectors.selectUsersLoaded)); + allUsers$ = this.store.pipe(select(UsersSelectors.selectAllUsers)); + selectedUsers$ = this.store.pipe(select(UsersSelectors.selectEntity)); + + /** + * Use the initialization action to perform one + * or more tasks in your Effects. + */ + init() { + this.store.dispatch(UsersActions.initUsers()); + } +} +" +`; + +exports[`ngrx-feature-store NgModule should generate into a subdirectory correctly when a path is passed as the name 5`] = ` +"import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; +import { createReducer, on, Action } from '@ngrx/store'; + +import * as UsersActions from './users.actions'; +import { UsersEntity } from './users.models'; + +export const USERS_FEATURE_KEY = 'users'; + +export interface UsersState extends EntityState { + selectedId?: string | number; // which Users record has been selected + loaded: boolean; // has the Users list been loaded + error?: string | null; // last known error (if any) +} + +export interface UsersPartialState { + readonly [USERS_FEATURE_KEY]: UsersState; +} + +export const usersAdapter: EntityAdapter = + createEntityAdapter(); + +export const initialUsersState: UsersState = usersAdapter.getInitialState({ + // set initial required properties + loaded: false, +}); + +const reducer = createReducer( + initialUsersState, + on(UsersActions.initUsers, (state) => ({ + ...state, + loaded: false, + error: null, + })), + on(UsersActions.loadUsersSuccess, (state, { users }) => + usersAdapter.setAll(users, { ...state, loaded: true }) + ), + on(UsersActions.loadUsersFailure, (state, { error }) => ({ ...state, error })) +); + +export function usersReducer(state: UsersState | undefined, action: Action) { + return reducer(state, action); +} +" +`; + +exports[`ngrx-feature-store NgModule should generate into a subdirectory correctly when a path is passed as the name 6`] = ` +"import { createFeatureSelector, createSelector } from '@ngrx/store'; +import { USERS_FEATURE_KEY, UsersState, usersAdapter } from './users.reducer'; + +// Lookup the 'Users' feature state managed by NgRx +export const selectUsersState = + createFeatureSelector(USERS_FEATURE_KEY); + +const { selectAll, selectEntities } = usersAdapter.getSelectors(); + +export const selectUsersLoaded = createSelector( + selectUsersState, + (state: UsersState) => state.loaded +); + +export const selectUsersError = createSelector( + selectUsersState, + (state: UsersState) => state.error +); + +export const selectAllUsers = createSelector( + selectUsersState, + (state: UsersState) => selectAll(state) +); + +export const selectUsersEntities = createSelector( + selectUsersState, + (state: UsersState) => selectEntities(state) +); + +export const selectSelectedId = createSelector( + selectUsersState, + (state: UsersState) => state.selectedId +); + +export const selectEntity = createSelector( + selectUsersEntities, + selectSelectedId, + (entities, selectedId) => (selectedId ? entities[selectedId] : undefined) +); +" +`; + +exports[`ngrx-feature-store NgModule should generate into a subdirectory correctly when a path is passed as the name 7`] = ` +"import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; +import * as fromUsers from './+state/users/users.reducer'; +import { UsersEffects } from './+state/users/users.effects'; +import { UsersFacade } from './+state/users/users.facade'; + +@NgModule({ + imports: [ + CommonModule, + StoreModule.forFeature(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer), + EffectsModule.forFeature([UsersEffects]), + ], + providers: [UsersFacade], +}) +export class FeatureModuleModule {} +" +`; + exports[`ngrx-feature-store NgModule should generate the files with the correct content 1`] = ` "import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; diff --git a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.actions.ts__tmpl__ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.actions.ts__tmpl__ index 6b626b0dc2a52..d54550810bafa 100644 --- a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.actions.ts__tmpl__ +++ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.actions.ts__tmpl__ @@ -1,5 +1,5 @@ import { createAction, props } from '@ngrx/store'; -import { <%= className %>Entity } from './<%= fileName %>.models'; +import { <%= className %>Entity } from './<%= relativeFileName %>.models'; export const init<%= className %> = createAction( '[<%= className %> Page] Init' diff --git a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.effects.spec.ts__tmpl__ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.effects.spec.ts__tmpl__ index 827d18f04c626..d2e6a58686e4d 100644 --- a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.effects.spec.ts__tmpl__ +++ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.effects.spec.ts__tmpl__ @@ -5,8 +5,8 @@ import { provideMockStore } from '@ngrx/store/testing'; import { hot } from 'jasmine-marbles'; import { Observable } from 'rxjs'; -import * as <%= className %>Actions from './<%= fileName %>.actions'; -import { <%= className %>Effects } from './<%= fileName %>.effects'; +import * as <%= className %>Actions from './<%= relativeFileName %>.actions'; +import { <%= className %>Effects } from './<%= relativeFileName %>.effects'; describe('<%= className %>Effects', () => { let actions: Observable; diff --git a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.effects.ts__tmpl__ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.effects.ts__tmpl__ index f443e2c172de1..0187c5171c8f2 100644 --- a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.effects.ts__tmpl__ +++ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.effects.ts__tmpl__ @@ -3,8 +3,8 @@ import { createEffect, Actions, ofType } from '@ngrx/effects';<% if (!importFrom import { switchMap, catchError, of } from 'rxjs';<% } else { %> import { of } from 'rxjs'; import { switchMap, catchError } from 'rxjs/operators';<% } %> -import * as <%= className %>Actions from './<%= fileName %>.actions'; -import * as <%= className %>Feature from './<%= fileName %>.reducer'; +import * as <%= className %>Actions from './<%= relativeFileName %>.actions'; +import * as <%= className %>Feature from './<%= relativeFileName %>.reducer'; @Injectable() export class <%= className %>Effects { diff --git a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.facade.spec.ts__tmpl__ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.facade.spec.ts__tmpl__ index ccd1b5263a73b..c801156d54232 100644 --- a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.facade.spec.ts__tmpl__ +++ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.facade.spec.ts__tmpl__ @@ -4,17 +4,17 @@ import { EffectsModule } from '@ngrx/effects'; import { StoreModule, Store } from '@ngrx/store'; import { readFirst } from '@nx/angular/testing'; -import * as <%= className %>Actions from './<%= fileName %>.actions'; -import { <%= className %>Effects } from './<%= fileName %>.effects'; -import { <%= className %>Facade } from './<%= fileName %>.facade'; -import { <%= className %>Entity } from './<%= fileName %>.models'; +import * as <%= className %>Actions from './<%= relativeFileName %>.actions'; +import { <%= className %>Effects } from './<%= relativeFileName %>.effects'; +import { <%= className %>Facade } from './<%= relativeFileName %>.facade'; +import { <%= className %>Entity } from './<%= relativeFileName %>.models'; import { <%= constantName %>_FEATURE_KEY, <%= className %>State, initial<%= className %>State, <%= propertyName %>Reducer -} from './<%= fileName %>.reducer'; -import * as <%= className %>Selectors from './<%= fileName %>.selectors'; +} from './<%= relativeFileName %>.reducer'; +import * as <%= className %>Selectors from './<%= relativeFileName %>.selectors'; interface TestSchema { <%= propertyName %>: <%= className %>State; diff --git a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.facade.ts__tmpl__ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.facade.ts__tmpl__ index 5e358f4f4f6b5..2a22ea7aae328 100644 --- a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.facade.ts__tmpl__ +++ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.facade.ts__tmpl__ @@ -1,9 +1,9 @@ import { Injectable, inject } from '@angular/core'; import { select, Store, Action } from '@ngrx/store'; -import * as <%= className %>Actions from './<%= fileName %>.actions'; -import * as <%= className %>Feature from './<%= fileName %>.reducer'; -import * as <%= className %>Selectors from './<%= fileName %>.selectors'; +import * as <%= className %>Actions from './<%= relativeFileName %>.actions'; +import * as <%= className %>Feature from './<%= relativeFileName %>.reducer'; +import * as <%= className %>Selectors from './<%= relativeFileName %>.selectors'; @Injectable() export class <%= className %>Facade { diff --git a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.reducer.spec.ts__tmpl__ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.reducer.spec.ts__tmpl__ index edf70509efb12..5a11f4a1f1194 100644 --- a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.reducer.spec.ts__tmpl__ +++ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.reducer.spec.ts__tmpl__ @@ -1,8 +1,8 @@ import { Action } from '@ngrx/store'; -import * as <%= className %>Actions from './<%= fileName %>.actions'; -import { <%= className %>Entity } from './<%= fileName %>.models'; -import { <%= className %>State, initial<%= className %>State, <%= propertyName %>Reducer } from './<%= fileName %>.reducer'; +import * as <%= className %>Actions from './<%= relativeFileName %>.actions'; +import { <%= className %>Entity } from './<%= relativeFileName %>.models'; +import { <%= className %>State, initial<%= className %>State, <%= propertyName %>Reducer } from './<%= relativeFileName %>.reducer'; describe('<%= className %> Reducer', () => { const create<%= className %>Entity = (id: string, name = ''): <%= className %>Entity => ({ diff --git a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.reducer.ts__tmpl__ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.reducer.ts__tmpl__ index 6e925716a7d66..3d9d31d3bf913 100644 --- a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.reducer.ts__tmpl__ +++ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.reducer.ts__tmpl__ @@ -1,8 +1,8 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; import { createReducer, on, Action } from '@ngrx/store'; -import * as <%= className %>Actions from './<%= fileName %>.actions'; -import { <%= className %>Entity } from './<%= fileName %>.models'; +import * as <%= className %>Actions from './<%= relativeFileName %>.actions'; +import { <%= className %>Entity } from './<%= relativeFileName %>.models'; export const <%= constantName %>_FEATURE_KEY = '<%= propertyName %>'; diff --git a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.selectors.spec.ts__tmpl__ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.selectors.spec.ts__tmpl__ index 6d75b21e89928..937153d188462 100644 --- a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.selectors.spec.ts__tmpl__ +++ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.selectors.spec.ts__tmpl__ @@ -1,6 +1,6 @@ -import { <%= className %>Entity } from './<%= fileName %>.models'; -import { <%= propertyName %>Adapter, <%= className %>PartialState, initial<%= className %>State } from './<%= fileName %>.reducer'; -import * as <%= className %>Selectors from './<%= fileName %>.selectors'; +import { <%= className %>Entity } from './<%= relativeFileName %>.models'; +import { <%= propertyName %>Adapter, <%= className %>PartialState, initial<%= className %>State } from './<%= relativeFileName %>.reducer'; +import * as <%= className %>Selectors from './<%= relativeFileName %>.selectors'; describe('<%= className %> Selectors', () => { const ERROR_MSG = 'No Error Available'; diff --git a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.selectors.ts__tmpl__ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.selectors.ts__tmpl__ index 25e5f81884bdb..e125b0437fc94 100644 --- a/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.selectors.ts__tmpl__ +++ b/packages/angular/src/generators/ngrx-feature-store/files/base/__directory__/__fileName__.selectors.ts__tmpl__ @@ -1,5 +1,5 @@ import { createFeatureSelector, createSelector } from '@ngrx/store'; -import { <%= constantName %>_FEATURE_KEY, <%= className %>State, <%= propertyName %>Adapter } from './<%= fileName %>.reducer'; +import { <%= constantName %>_FEATURE_KEY, <%= className %>State, <%= propertyName %>Adapter } from './<%= relativeFileName %>.reducer'; // Lookup the '<%= className %>' feature state managed by NgRx export const select<%= className %>State = createFeatureSelector<<%= className %>State>(<%= constantName %>_FEATURE_KEY); diff --git a/packages/angular/src/generators/ngrx-feature-store/files/no-inject/__directory__/__fileName__.effects.ts__tmpl__ b/packages/angular/src/generators/ngrx-feature-store/files/no-inject/__directory__/__fileName__.effects.ts__tmpl__ index 508c619520756..c4c787a91cb64 100644 --- a/packages/angular/src/generators/ngrx-feature-store/files/no-inject/__directory__/__fileName__.effects.ts__tmpl__ +++ b/packages/angular/src/generators/ngrx-feature-store/files/no-inject/__directory__/__fileName__.effects.ts__tmpl__ @@ -1,8 +1,8 @@ import { Injectable } from '@angular/core'; import { createEffect, Actions, ofType } from '@ngrx/effects'; -import * as <%= className %>Actions from './<%= fileName %>.actions'; -import * as <%= className %>Feature from './<%= fileName %>.reducer'; +import * as <%= className %>Actions from './<%= relativeFileName %>.actions'; +import * as <%= className %>Feature from './<%= relativeFileName %>.reducer'; import {switchMap, catchError, of} from 'rxjs'; diff --git a/packages/angular/src/generators/ngrx-feature-store/files/no-inject/__directory__/__fileName__.facade.ts__tmpl__ b/packages/angular/src/generators/ngrx-feature-store/files/no-inject/__directory__/__fileName__.facade.ts__tmpl__ index b5bd3607baa4f..61e66dcd8c1ac 100644 --- a/packages/angular/src/generators/ngrx-feature-store/files/no-inject/__directory__/__fileName__.facade.ts__tmpl__ +++ b/packages/angular/src/generators/ngrx-feature-store/files/no-inject/__directory__/__fileName__.facade.ts__tmpl__ @@ -1,9 +1,9 @@ import { Injectable } from '@angular/core'; import { select, Store, Action } from '@ngrx/store'; -import * as <%= className %>Actions from './<%= fileName %>.actions'; -import * as <%= className %>Feature from './<%= fileName %>.reducer'; -import * as <%= className %>Selectors from './<%= fileName %>.selectors'; +import * as <%= className %>Actions from './<%= relativeFileName %>.actions'; +import * as <%= className %>Feature from './<%= relativeFileName %>.reducer'; +import * as <%= className %>Selectors from './<%= relativeFileName %>.selectors'; @Injectable() export class <%= className %>Facade { diff --git a/packages/angular/src/generators/ngrx-feature-store/lib/add-exports-barrel.ts b/packages/angular/src/generators/ngrx-feature-store/lib/add-exports-barrel.ts index 633d6905e420c..cf8d392f27ea2 100644 --- a/packages/angular/src/generators/ngrx-feature-store/lib/add-exports-barrel.ts +++ b/packages/angular/src/generators/ngrx-feature-store/lib/add-exports-barrel.ts @@ -33,7 +33,10 @@ export function addExportsToBarrel( // Public API for the feature interfaces, selectors, and facade const { className, fileName } = names(options.name); - const statePath = `./lib/${options.directory}/${fileName}`; + const fileNameWithSubdir = options.subdirectory + ? joinPathFragments(options.subdirectory, fileName) + : fileName; + const statePath = `./lib/${options.directory}/${fileNameWithSubdir}`; sourceFile = addGlobal( tree, diff --git a/packages/angular/src/generators/ngrx-feature-store/lib/add-imports.ts b/packages/angular/src/generators/ngrx-feature-store/lib/add-imports.ts index 752b3ec046e28..20f88116bedb4 100644 --- a/packages/angular/src/generators/ngrx-feature-store/lib/add-imports.ts +++ b/packages/angular/src/generators/ngrx-feature-store/lib/add-imports.ts @@ -114,7 +114,9 @@ export function addImportsToModule( ); }; - const dir = `./${names(options.directory).fileName}`; + const dir = `./${names(options.directory).fileName}${ + options.subdirectory ? `/${options.subdirectory}` : '' + }`; const pathPrefix = `${dir}/${names(options.name).fileName}`; const reducerPath = `${pathPrefix}.reducer`; const effectsPath = `${pathPrefix}.effects`; diff --git a/packages/angular/src/generators/ngrx-feature-store/lib/generate-files.ts b/packages/angular/src/generators/ngrx-feature-store/lib/generate-files.ts index dd5c7dce91829..caee51b914021 100644 --- a/packages/angular/src/generators/ngrx-feature-store/lib/generate-files.ts +++ b/packages/angular/src/generators/ngrx-feature-store/lib/generate-files.ts @@ -9,6 +9,9 @@ export function generateFilesFromTemplates( options: NormalizedNgRxFeatureStoreGeneratorOptions ) { const projectNames = names(options.name); + const fileName = options.subdirectory + ? joinPathFragments(options.subdirectory, projectNames.fileName) + : projectNames.fileName; generateFiles( tree, @@ -17,6 +20,8 @@ export function generateFilesFromTemplates( { ...options, ...projectNames, + fileName, + relativeFileName: projectNames.fileName, importFromOperators: lt(options.rxjsVersion, '7.2.0'), tmpl: '', } @@ -31,6 +36,8 @@ export function generateFilesFromTemplates( { ...options, ...projectNames, + fileName, + relativeFileName: projectNames.fileName, tmpl: '', } ); @@ -41,14 +48,18 @@ export function generateFilesFromTemplates( joinPathFragments( options.parentDirectory, options.directory, - `${projectNames.fileName}.facade.ts` + `${options.subdirectory ? `${options.subdirectory}/` : ''}${ + projectNames.fileName + }.facade.ts` ) ); tree.delete( joinPathFragments( options.parentDirectory, options.directory, - `${projectNames.fileName}.facade.spec.ts` + `${options.subdirectory ? `${options.subdirectory}/` : ''}${ + projectNames.fileName + }.facade.spec.ts` ) ); } diff --git a/packages/angular/src/generators/ngrx-feature-store/lib/normalize-options.ts b/packages/angular/src/generators/ngrx-feature-store/lib/normalize-options.ts index 1a06b431628d3..7e92b117923e7 100644 --- a/packages/angular/src/generators/ngrx-feature-store/lib/normalize-options.ts +++ b/packages/angular/src/generators/ngrx-feature-store/lib/normalize-options.ts @@ -1,5 +1,5 @@ import type { Tree } from '@nx/devkit'; -import { names, readJson } from '@nx/devkit'; +import { joinPathFragments, names, readJson } from '@nx/devkit'; import { checkAndCleanWithSemver } from '@nx/devkit/src/utils/semver'; import { dirname } from 'path'; import { rxjsVersion as defaultRxjsVersion } from '../../../utils/versions'; @@ -7,6 +7,7 @@ import type { Schema } from '../schema'; export type NormalizedNgRxFeatureStoreGeneratorOptions = Schema & { parentDirectory: string; + subdirectory: string; rxjsVersion: string; }; @@ -24,11 +25,26 @@ export function normalizeOptions( rxjsVersion = checkAndCleanWithSemver('rxjs', defaultRxjsVersion); } + const { subdirectory, name } = determineSubdirectoryAndName(options.name); + return { ...options, + name, + subdirectory, parentDirectory: options.parent ? dirname(options.parent) : undefined, route: options.route === '' ? `''` : options.route ?? `''`, directory: names(options.directory).fileName, rxjsVersion, }; } + +function determineSubdirectoryAndName(name: string) { + if (name.includes('/')) { + const parts = name.split('/'); + const storeName = parts.pop(); + const subdirectory = joinPathFragments(...parts); + return { subdirectory, name: storeName }; + } else { + return { name }; + } +} diff --git a/packages/angular/src/generators/ngrx-feature-store/ngrx-feature-store.spec.ts b/packages/angular/src/generators/ngrx-feature-store/ngrx-feature-store.spec.ts index 19a8b20ea2416..151dae9336752 100644 --- a/packages/angular/src/generators/ngrx-feature-store/ngrx-feature-store.spec.ts +++ b/packages/angular/src/generators/ngrx-feature-store/ngrx-feature-store.spec.ts @@ -254,6 +254,57 @@ describe('ngrx-feature-store', () => { tree.read(`feature-module/src/index.ts`, 'utf-8') ).toMatchSnapshot(); }); + + it('should generate into a subdirectory correctly when a path is passed as the name', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + await addNgModuleLib(tree); + const statePath = 'feature-module/src/lib/+state'; + // ACT + await ngrxFeatureStoreGenerator(tree, { + name: 'users/users', + minimal: false, + directory: '+state', + facade: true, + parent, + }); + + // ASSERT + expect( + tree.read(`feature-module/src/index.ts`, 'utf-8') + ).toMatchSnapshot(); + expect( + tree.read( + `feature-module/src/lib/+state/users/users.actions.ts`, + 'utf-8' + ) + ).toMatchSnapshot(); + expect( + tree.read( + `feature-module/src/lib/+state/users/users.effects.ts`, + 'utf-8' + ) + ).toMatchSnapshot(); + expect( + tree.read( + `feature-module/src/lib/+state/users/users.facade.ts`, + 'utf-8' + ) + ).toMatchSnapshot(); + expect( + tree.read( + `feature-module/src/lib/+state/users/users.reducer.ts`, + 'utf-8' + ) + ).toMatchSnapshot(); + expect( + tree.read( + `feature-module/src/lib/+state/users/users.selectors.ts`, + 'utf-8' + ) + ).toMatchSnapshot(); + expect(tree.read(parent, 'utf-8')).toMatchSnapshot(); + }); }); describe('Standalone APIs', () => {