feat(angular): add ngrx root store generator (#16811)

This commit is contained in:
Colum Ferry 2023-05-11 13:38:21 +01:00 committed by GitHub
parent 279154436a
commit e59c930ff9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1669 additions and 0 deletions

View File

@ -4246,6 +4246,14 @@
"isExternal": false, "isExternal": false,
"disableCollapsible": false "disableCollapsible": false
}, },
{
"id": "ngrx-root-store",
"path": "/packages/angular/generators/ngrx-root-store",
"name": "ngrx-root-store",
"children": [],
"isExternal": false,
"disableCollapsible": false
},
{ {
"id": "pipe", "id": "pipe",
"path": "/packages/angular/generators/pipe", "path": "/packages/angular/generators/pipe",

View File

@ -267,6 +267,15 @@
"path": "/packages/angular/generators/ngrx-feature-store", "path": "/packages/angular/generators/ngrx-feature-store",
"type": "generator" "type": "generator"
}, },
"/packages/angular/generators/ngrx-root-store": {
"description": "Adds an NgRx Root Store to an application.",
"file": "generated/packages/angular/generators/ngrx-root-store.json",
"hidden": false,
"name": "ngrx-root-store",
"originalFilePath": "/packages/angular/src/generators/ngrx-root-store/schema.json",
"path": "/packages/angular/generators/ngrx-root-store",
"type": "generator"
},
"/packages/angular/generators/pipe": { "/packages/angular/generators/pipe": {
"description": "Generate an Angular Pipe", "description": "Generate an Angular Pipe",
"file": "generated/packages/angular/generators/pipe.json", "file": "generated/packages/angular/generators/pipe.json",

View File

@ -262,6 +262,15 @@
"path": "angular/generators/ngrx-feature-store", "path": "angular/generators/ngrx-feature-store",
"type": "generator" "type": "generator"
}, },
{
"description": "Adds an NgRx Root Store to an application.",
"file": "generated/packages/angular/generators/ngrx-root-store.json",
"hidden": false,
"name": "ngrx-root-store",
"originalFilePath": "/packages/angular/src/generators/ngrx-root-store/schema.json",
"path": "angular/generators/ngrx-root-store",
"type": "generator"
},
{ {
"description": "Generate an Angular Pipe", "description": "Generate an Angular Pipe",
"file": "generated/packages/angular/generators/pipe.json", "file": "generated/packages/angular/generators/pipe.json",

View File

@ -0,0 +1,74 @@
{
"name": "ngrx-root-store",
"factory": "./src/generators/ngrx-root-store/ngrx-root-store",
"schema": {
"$schema": "http://json-schema.org/schema",
"$id": "NxNgrxRootStoreGenerator",
"title": "Add NgRx support to an application.",
"description": "Adds NgRx support to an application.",
"cli": "nx",
"type": "object",
"properties": {
"project": {
"type": "string",
"description": "The name of the application to generate the NgRx configuration for.",
"$default": { "$source": "argv", "index": 0 },
"x-prompt": "What app would you like to generate a NgRx configuration for?",
"x-dropdown": "projects"
},
"minimal": {
"type": "boolean",
"default": true,
"description": "Only register the root state management setup or also generate a global feature state.",
"x-priority": "important"
},
"name": {
"type": "string",
"description": "Name of the NgRx state, such as `products` or `users`. Recommended to use the plural form of the name.",
"x-priority": "important"
},
"route": {
"type": "string",
"description": "The route that the Standalone NgRx Providers should be added to. _Note: This is only supported in Angular versions >= 14.1.0_.",
"default": "''"
},
"directory": {
"type": "string",
"default": "+state",
"description": "The name of the folder used to contain/group the generated NgRx files."
},
"facade": {
"type": "boolean",
"default": false,
"description": "Create a Facade class for the the feature.",
"x-prompt": "Would you like to use a Facade with your NgRx state?"
},
"skipImport": {
"type": "boolean",
"default": false,
"description": "Generate NgRx feature files without registering the feature in the NgModule."
},
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
"default": false,
"x-priority": "internal"
},
"skipPackageJson": {
"type": "boolean",
"default": false,
"description": "Do not update the `package.json` with NgRx dependencies.",
"x-priority": "internal"
}
},
"additionalProperties": false,
"required": ["project"],
"presets": []
},
"description": "Adds an NgRx Root Store to an application.",
"implementation": "/packages/angular/src/generators/ngrx-root-store/ngrx-root-store.ts",
"aliases": [],
"hidden": false,
"path": "/packages/angular/src/generators/ngrx-root-store/schema.json",
"type": "generator"
}

View File

@ -259,6 +259,11 @@
"schema": "./src/generators/ngrx-feature-store/schema.json", "schema": "./src/generators/ngrx-feature-store/schema.json",
"description": "Adds an NgRx Feature Store to an application or library." "description": "Adds an NgRx Feature Store to an application or library."
}, },
"ngrx-root-store": {
"factory": "./src/generators/ngrx-root-store/ngrx-root-store",
"schema": "./src/generators/ngrx-root-store/schema.json",
"description": "Adds an NgRx Root Store to an application."
},
"pipe": { "pipe": {
"factory": "./src/generators/pipe/pipe", "factory": "./src/generators/pipe/pipe",
"schema": "./src/generators/pipe/schema.json", "schema": "./src/generators/pipe/schema.json",

View File

@ -12,6 +12,7 @@ export * from './src/generators/library/library';
export * from './src/generators/move/move'; export * from './src/generators/move/move';
export * from './src/generators/ngrx/ngrx'; export * from './src/generators/ngrx/ngrx';
export * from './src/generators/ngrx-feature-store/ngrx-feature-store'; export * from './src/generators/ngrx-feature-store/ngrx-feature-store';
export * from './src/generators/ngrx-root-store/ngrx-root-store';
export * from './src/generators/pipe/pipe'; export * from './src/generators/pipe/pipe';
export * from './src/generators/remote/remote'; export * from './src/generators/remote/remote';
export * from './src/generators/scam-directive/scam-directive'; export * from './src/generators/scam-directive/scam-directive';

View File

@ -0,0 +1,740 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`NgRxRootStoreGenerator NgModule should add a facade when --facade=true 1`] = `
"import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { appRoutes } from './app.routes';
import { NxWelcomeComponent } from './nx-welcome.component';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreRouterConnectingModule } from '@ngrx/router-store';
import * as fromUsers from './+state/users.reducer';
import { UsersEffects } from './+state/users.effects';
import { UsersFacade } from './+state/users.facade';
@NgModule({
declarations: [AppComponent, NxWelcomeComponent],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes, { initialNavigation: 'enabledBlocking' }),
StoreModule.forRoot(
{},
{
metaReducers: [],
runtimeChecks: {
strictActionImmutability: true,
strictStateImmutability: true,
},
}
),
EffectsModule.forRoot([]),
StoreRouterConnectingModule.forRoot(),
StoreModule.forFeature(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),
EffectsModule.forFeature([UsersEffects]),
],
providers: [UsersFacade],
bootstrap: [AppComponent],
})
export class AppModule {}
"
`;
exports[`NgRxRootStoreGenerator NgModule should add a facade when --facade=true 2`] = `
"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[`NgRxRootStoreGenerator NgModule should add a root module and root state when --minimal=false 1`] = `
"import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { appRoutes } from './app.routes';
import { NxWelcomeComponent } from './nx-welcome.component';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreRouterConnectingModule } from '@ngrx/router-store';
import * as fromUsers from './+state/users.reducer';
import { UsersEffects } from './+state/users.effects';
@NgModule({
declarations: [AppComponent, NxWelcomeComponent],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes, { initialNavigation: 'enabledBlocking' }),
StoreModule.forRoot(
{},
{
metaReducers: [],
runtimeChecks: {
strictActionImmutability: true,
strictStateImmutability: true,
},
}
),
EffectsModule.forRoot([]),
StoreRouterConnectingModule.forRoot(),
StoreModule.forFeature(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),
EffectsModule.forFeature([UsersEffects]),
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
"
`;
exports[`NgRxRootStoreGenerator NgModule should add a root module and root state when --minimal=false 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[`NgRxRootStoreGenerator NgModule should add a root module and root state when --minimal=false 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[`NgRxRootStoreGenerator NgModule should add a root module and root state when --minimal=false 4`] = `
"import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { Action } from '@ngrx/store';
import { provideMockStore } from '@ngrx/store/testing';
import { hot } from 'jasmine-marbles';
import { Observable } from 'rxjs';
import * as UsersActions from './users.actions';
import { UsersEffects } from './users.effects';
describe('UsersEffects', () => {
let actions: Observable<Action>;
let effects: UsersEffects;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [],
providers: [
UsersEffects,
provideMockActions(() => actions),
provideMockStore(),
],
});
effects = TestBed.inject(UsersEffects);
});
describe('init$', () => {
it('should work', () => {
actions = hot('-a-|', { a: UsersActions.initUsers() });
const expected = hot('-a-|', {
a: UsersActions.loadUsersSuccess({ users: [] }),
});
expect(effects.init$).toBeObservable(expected);
});
});
});
"
`;
exports[`NgRxRootStoreGenerator NgModule should add a root module and root state when --minimal=false 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<UsersEntity> {
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<UsersEntity> =
createEntityAdapter<UsersEntity>();
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[`NgRxRootStoreGenerator NgModule should add a root module and root state when --minimal=false 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<UsersState>(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[`NgRxRootStoreGenerator NgModule should add a root module and root state when --minimal=false 7`] = `
"import { UsersEntity } from './users.models';
import {
usersAdapter,
UsersPartialState,
initialUsersState,
} from './users.reducer';
import * as UsersSelectors from './users.selectors';
describe('Users Selectors', () => {
const ERROR_MSG = 'No Error Available';
const getUsersId = (it: UsersEntity) => it.id;
const createUsersEntity = (id: string, name = '') =>
({
id,
name: name || \`name-\${id}\`,
} as UsersEntity);
let state: UsersPartialState;
beforeEach(() => {
state = {
users: usersAdapter.setAll(
[
createUsersEntity('PRODUCT-AAA'),
createUsersEntity('PRODUCT-BBB'),
createUsersEntity('PRODUCT-CCC'),
],
{
...initialUsersState,
selectedId: 'PRODUCT-BBB',
error: ERROR_MSG,
loaded: true,
}
),
};
});
describe('Users Selectors', () => {
it('selectAllUsers() should return the list of Users', () => {
const results = UsersSelectors.selectAllUsers(state);
const selId = getUsersId(results[1]);
expect(results.length).toBe(3);
expect(selId).toBe('PRODUCT-BBB');
});
it('selectEntity() should return the selected Entity', () => {
const result = UsersSelectors.selectEntity(state) as UsersEntity;
const selId = getUsersId(result);
expect(selId).toBe('PRODUCT-BBB');
});
it('selectUsersLoaded() should return the current "loaded" status', () => {
const result = UsersSelectors.selectUsersLoaded(state);
expect(result).toBe(true);
});
it('selectUsersError() should return the current "error" state', () => {
const result = UsersSelectors.selectUsersError(state);
expect(result).toBe(ERROR_MSG);
});
});
});
"
`;
exports[`NgRxRootStoreGenerator NgModule should add an empty root module when --minimal=true 1`] = `
"import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { appRoutes } from './app.routes';
import { NxWelcomeComponent } from './nx-welcome.component';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreRouterConnectingModule } from '@ngrx/router-store';
@NgModule({
declarations: [AppComponent, NxWelcomeComponent],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes, { initialNavigation: 'enabledBlocking' }),
StoreModule.forRoot(
{},
{
metaReducers: [],
runtimeChecks: {
strictActionImmutability: true,
strictStateImmutability: true,
},
}
),
EffectsModule.forRoot([]),
StoreRouterConnectingModule.forRoot(),
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
"
`;
exports[`NgRxRootStoreGenerator Standalone APIs should add a facade when --facade=true 1`] = `
"import { ApplicationConfig } from '@angular/core';
import {
provideRouter,
withEnabledBlockingInitialNavigation,
} from '@angular/router';
import { appRoutes } from './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';
export const appConfig: ApplicationConfig = {
providers: [
provideEffects(UsersEffects),
provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),
UsersFacade,
provideEffects(),
provideStore(),
provideRouter(appRoutes, withEnabledBlockingInitialNavigation()),
],
};
"
`;
exports[`NgRxRootStoreGenerator Standalone APIs should add a facade when --facade=true 2`] = `
"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[`NgRxRootStoreGenerator Standalone APIs should add a root module and root state when --minimal=false 1`] = `
"import { ApplicationConfig } from '@angular/core';
import {
provideRouter,
withEnabledBlockingInitialNavigation,
} from '@angular/router';
import { appRoutes } from './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';
export const appConfig: ApplicationConfig = {
providers: [
provideEffects(UsersEffects),
provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),
provideEffects(),
provideStore(),
provideRouter(appRoutes, withEnabledBlockingInitialNavigation()),
],
};
"
`;
exports[`NgRxRootStoreGenerator Standalone APIs should add a root module and root state when --minimal=false 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[`NgRxRootStoreGenerator Standalone APIs should add a root module and root state when --minimal=false 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[`NgRxRootStoreGenerator Standalone APIs should add a root module and root state when --minimal=false 4`] = `
"import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { Action } from '@ngrx/store';
import { provideMockStore } from '@ngrx/store/testing';
import { hot } from 'jasmine-marbles';
import { Observable } from 'rxjs';
import * as UsersActions from './users.actions';
import { UsersEffects } from './users.effects';
describe('UsersEffects', () => {
let actions: Observable<Action>;
let effects: UsersEffects;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [],
providers: [
UsersEffects,
provideMockActions(() => actions),
provideMockStore(),
],
});
effects = TestBed.inject(UsersEffects);
});
describe('init$', () => {
it('should work', () => {
actions = hot('-a-|', { a: UsersActions.initUsers() });
const expected = hot('-a-|', {
a: UsersActions.loadUsersSuccess({ users: [] }),
});
expect(effects.init$).toBeObservable(expected);
});
});
});
"
`;
exports[`NgRxRootStoreGenerator Standalone APIs should add a root module and root state when --minimal=false 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<UsersEntity> {
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<UsersEntity> =
createEntityAdapter<UsersEntity>();
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[`NgRxRootStoreGenerator Standalone APIs should add a root module and root state when --minimal=false 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<UsersState>(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[`NgRxRootStoreGenerator Standalone APIs should add a root module and root state when --minimal=false 7`] = `
"import { UsersEntity } from './users.models';
import {
usersAdapter,
UsersPartialState,
initialUsersState,
} from './users.reducer';
import * as UsersSelectors from './users.selectors';
describe('Users Selectors', () => {
const ERROR_MSG = 'No Error Available';
const getUsersId = (it: UsersEntity) => it.id;
const createUsersEntity = (id: string, name = '') =>
({
id,
name: name || \`name-\${id}\`,
} as UsersEntity);
let state: UsersPartialState;
beforeEach(() => {
state = {
users: usersAdapter.setAll(
[
createUsersEntity('PRODUCT-AAA'),
createUsersEntity('PRODUCT-BBB'),
createUsersEntity('PRODUCT-CCC'),
],
{
...initialUsersState,
selectedId: 'PRODUCT-BBB',
error: ERROR_MSG,
loaded: true,
}
),
};
});
describe('Users Selectors', () => {
it('selectAllUsers() should return the list of Users', () => {
const results = UsersSelectors.selectAllUsers(state);
const selId = getUsersId(results[1]);
expect(results.length).toBe(3);
expect(selId).toBe('PRODUCT-BBB');
});
it('selectEntity() should return the selected Entity', () => {
const result = UsersSelectors.selectEntity(state) as UsersEntity;
const selId = getUsersId(result);
expect(selId).toBe('PRODUCT-BBB');
});
it('selectUsersLoaded() should return the current "loaded" status', () => {
const result = UsersSelectors.selectUsersLoaded(state);
expect(result).toBe(true);
});
it('selectUsersError() should return the current "error" state', () => {
const result = UsersSelectors.selectUsersError(state);
expect(result).toBe(ERROR_MSG);
});
});
});
"
`;
exports[`NgRxRootStoreGenerator Standalone APIs should add an empty root module when --minimal=true 1`] = `
"import { ApplicationConfig } from '@angular/core';
import {
provideRouter,
withEnabledBlockingInitialNavigation,
} from '@angular/router';
import { appRoutes } from './app.routes';
import { provideStore } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
export const appConfig: ApplicationConfig = {
providers: [
provideEffects(),
provideStore(),
provideRouter(appRoutes, withEnabledBlockingInitialNavigation()),
],
};
"
`;

View File

@ -0,0 +1,167 @@
import type { Tree } from '@nx/devkit';
import type { SourceFile } from 'typescript';
import {
addImportToModule,
addProviderToAppConfig,
addProviderToBootstrapApplication,
} from '../../../utils/nx-devkit/ast-utils';
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
import { insertImport } from '@nx/js';
import { NormalizedNgRxRootStoreGeneratorOptions } from './normalize-options';
let tsModule: typeof import('typescript');
function addRootStoreImport(
tree: Tree,
isParentStandalone: boolean,
sourceFile: SourceFile,
parentPath: string,
provideRootStore: string,
storeForRoot: string
) {
if (isParentStandalone) {
if (tree.read(parentPath, 'utf-8').includes('ApplicationConfig')) {
addProviderToAppConfig(tree, parentPath, provideRootStore);
} else {
addProviderToBootstrapApplication(tree, parentPath, provideRootStore);
}
} else {
sourceFile = addImportToModule(tree, sourceFile, parentPath, storeForRoot);
}
return sourceFile;
}
function addRootEffectsImport(
tree: Tree,
isParentStandalone: boolean,
sourceFile: SourceFile,
parentPath: string,
provideRootEffects: string,
effectsForEmptyRoot: string
) {
if (isParentStandalone) {
if (tree.read(parentPath, 'utf-8').includes('ApplicationConfig')) {
addProviderToAppConfig(tree, parentPath, provideRootEffects);
} else {
addProviderToBootstrapApplication(tree, parentPath, 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);
}
export function addImportsToModule(
tree: Tree,
options: NormalizedNgRxRootStoreGeneratorOptions
): void {
if (!tsModule) {
tsModule = ensureTypescript();
}
const parentPath = options.parent;
const sourceText = tree.read(parentPath, 'utf-8');
let sourceFile = tsModule.createSourceFile(
parentPath,
sourceText,
tsModule.ScriptTarget.Latest,
true
);
const isParentStandalone = !sourceText.includes('@NgModule');
const addImport = (
source: SourceFile,
symbolName: string,
fileName: string,
isDefault = false
): SourceFile => {
return insertImport(
tree,
source,
parentPath,
symbolName,
fileName,
isDefault
);
};
const storeMetaReducers = `metaReducers: []`;
const storeForRoot = `StoreModule.forRoot({}, {
${storeMetaReducers},
runtimeChecks: {
strictActionImmutability: true,
strictStateImmutability: true
}
})`;
const effectsForEmptyRoot = `EffectsModule.forRoot([])`;
const storeRouterModule = 'StoreRouterConnectingModule.forRoot()';
const provideRootStore = `provideStore()`;
const provideRootEffects = `provideEffects()`;
if (isParentStandalone) {
sourceFile = addImport(sourceFile, 'provideStore', '@ngrx/store');
sourceFile = addImport(sourceFile, 'provideEffects', '@ngrx/effects');
} else {
sourceFile = addImport(sourceFile, 'StoreModule', '@ngrx/store');
sourceFile = addImport(sourceFile, 'EffectsModule', '@ngrx/effects');
}
sourceFile = addRootStoreImport(
tree,
isParentStandalone,
sourceFile,
parentPath,
provideRootStore,
storeForRoot
);
sourceFile = addRootEffectsImport(
tree,
isParentStandalone,
sourceFile,
parentPath,
provideRootEffects,
effectsForEmptyRoot
);
// this is just a heuristic
const hasRouter = sourceText.indexOf('RouterModule') > -1;
if (hasRouter && !isParentStandalone) {
sourceFile = addRouterStoreImport(
tree,
sourceFile,
addImport,
parentPath,
storeRouterModule
);
}
}

View File

@ -0,0 +1,33 @@
import type { GeneratorCallback, Tree } from '@nx/devkit';
import { addDependenciesToPackageJson } from '@nx/devkit';
import { gte } from 'semver';
import { versions } from '../../utils/version-utils';
import { NormalizedNgRxRootStoreGeneratorOptions } from './normalize-options';
export function addNgRxToPackageJson(
tree: Tree,
options: NormalizedNgRxRootStoreGeneratorOptions
): GeneratorCallback {
const jasmineMarblesVersion = gte(options.rxjsVersion, '7.0.0')
? '~0.9.1'
: '~0.8.3';
const ngrxVersion = versions(tree).ngrxVersion;
process.env.npm_config_legacy_peer_deps ??= 'true';
return addDependenciesToPackageJson(
tree,
{
'@ngrx/store': ngrxVersion,
'@ngrx/effects': ngrxVersion,
'@ngrx/entity': ngrxVersion,
'@ngrx/router-store': ngrxVersion,
'@ngrx/component-store': ngrxVersion,
},
{
'@ngrx/schematics': ngrxVersion,
'@ngrx/store-devtools': ngrxVersion,
'jasmine-marbles': jasmineMarblesVersion,
}
);
}

View File

@ -0,0 +1,4 @@
export * from './add-imports';
export * from './add-ngrx-to-package-json';
export * from './normalize-options';
export * from './validate-options';

View File

@ -0,0 +1,61 @@
import type { Tree } from '@nx/devkit';
import {
joinPathFragments,
names,
readJson,
readProjectConfiguration,
} from '@nx/devkit';
import { checkAndCleanWithSemver } from '@nx/devkit/src/utils/semver';
import { rxjsVersion as defaultRxjsVersion } from '../../../utils/versions';
import type { Schema } from '../schema';
import { isNgStandaloneApp } from '../../../utils/nx-devkit/ast-utils';
export type NormalizedNgRxRootStoreGeneratorOptions = Schema & {
parent: string;
rxjsVersion: string;
};
export function normalizeOptions(
tree: Tree,
options: Schema
): NormalizedNgRxRootStoreGeneratorOptions {
let rxjsVersion: string;
try {
rxjsVersion = checkAndCleanWithSemver(
'rxjs',
readJson(tree, 'package.json').dependencies['rxjs']
);
} catch {
rxjsVersion = checkAndCleanWithSemver('rxjs', defaultRxjsVersion);
}
const project = readProjectConfiguration(tree, options.project);
const isStandalone = isNgStandaloneApp(tree, options.project);
const appConfigPath = joinPathFragments(
project.sourceRoot,
'app/app.config.ts'
);
const appMainPath = project.targets.build.options.main;
/** If NgModule App
* -> Use App Module
* If Standalone
* -> Check Config File exists (v16+)
* --> If so, use that
* --> If not, use main.ts
*/
const parent = !isStandalone
? joinPathFragments(project.sourceRoot, 'app/app.module.ts')
: tree.exists(appConfigPath)
? appConfigPath
: appMainPath;
options.directory = options.directory ?? '+state';
return {
...options,
parent,
directory: names(options.directory).fileName,
rxjsVersion,
};
}

View File

@ -0,0 +1,53 @@
import type { Tree } from '@nx/devkit';
import { getProjects, readProjectConfiguration } from '@nx/devkit';
import { Schema } from '../schema';
import {
getInstalledAngularVersionInfo,
getInstalledPackageVersionInfo,
} from '../../utils/version-utils';
import { getPkgVersionForAngularMajorVersion } from '../../../utils/version-utils';
import { coerce, lt, major } from 'semver';
import { isNgStandaloneApp } from '../../../utils/nx-devkit/ast-utils';
export function validateOptions(tree: Tree, options: Schema): void {
if (!getProjects(tree).has(options.project)) {
throw new Error(
`Could not find project '${options.project}'. Please ensure the project name is correct and exists.`
);
}
const project = readProjectConfiguration(tree, options.project);
if (project.projectType !== 'application') {
throw new Error(
`NgRx Root Stores can only be added to applications, please ensure the project you use is an application.`
);
}
if (!options.minimal && !options.name) {
throw new Error(
`If generating a global feature state with your root store, you must provide a name for it with '--name'.`
);
}
const angularVersionInfo = getInstalledAngularVersionInfo(tree);
const intendedNgRxVersionForAngularMajor =
getPkgVersionForAngularMajorVersion(
'ngrxVersion',
angularVersionInfo.major
);
const ngrxMajorVersion =
getInstalledPackageVersionInfo(tree, '@ngrx/store')?.major ??
major(coerce(intendedNgRxVersionForAngularMajor));
const isStandalone = isNgStandaloneApp(tree, options.project);
if (lt(angularVersionInfo.version, '14.1.0') || ngrxMajorVersion < 15) {
if (isStandalone) {
throw new Error(
`The provided project '${options.project}' is set up to use Standalone APIs, however your workspace is not configured to support Standalone APIs. ` +
'Please make sure to provide a path to an "NgModule" where the state will be registered. '
);
}
}
}

View File

@ -0,0 +1,381 @@
import type { Tree } from '@nx/devkit';
import { readJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import applicationGenerator from '../application/application';
import ngrxRootStoreGenerator from './ngrx-root-store';
import { ngrxVersion } from '../../utils/versions';
describe('NgRxRootStoreGenerator', () => {
describe('NgModule', () => {
it('should error when project does not exist', async () => {
const tree = createTreeWithEmptyWorkspace();
await expect(
ngrxRootStoreGenerator(tree, {
project: 'non-exist',
minimal: true,
name: '',
})
).rejects.toThrowError();
});
it('should error when minimal false, but name is undefined or falsy', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
await createNgModuleApp(tree);
// ACT & ASSERT
await expect(
ngrxRootStoreGenerator(tree, {
project: 'my-app',
minimal: false,
name: undefined,
})
).rejects.toThrowError();
});
it('should add an empty root module when --minimal=true', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
await createNgModuleApp(tree);
// ACT
await ngrxRootStoreGenerator(tree, {
project: 'my-app',
minimal: true,
});
// ASSERT
expect(
tree.read('my-app/src/app/app.module.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.exists('/my-app/src/app/+state/users.actions.ts')).toBe(
false
);
expect(tree.exists('/my-app/src/app/+state/users.effects.ts')).toBe(
false
);
expect(tree.exists('/my-app/src/app/+state/users.effects.spec.ts')).toBe(
false
);
expect(tree.exists('/my-app/src/app/+state/users.reducer.ts')).toBe(
false
);
expect(tree.exists('/my-app/src/app/+state/users.selectors.ts')).toBe(
false
);
expect(
tree.exists('/my-app/src/app/+state/users.selectors.spec.ts')
).toBe(false);
});
it('should add a root module and root state when --minimal=false', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
await createNgModuleApp(tree);
// ACT
await ngrxRootStoreGenerator(tree, {
project: 'my-app',
minimal: false,
name: 'users',
});
// ASSERT
expect(
tree.read('my-app/src/app/app.module.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('/my-app/src/app/+state/users.actions.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('/my-app/src/app/+state/users.effects.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('/my-app/src/app/+state/users.effects.spec.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('/my-app/src/app/+state/users.reducer.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('/my-app/src/app/+state/users.selectors.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('/my-app/src/app/+state/users.selectors.spec.ts', 'utf-8')
).toMatchSnapshot();
});
it('should add a facade when --facade=true', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
await createNgModuleApp(tree);
// ACT
await ngrxRootStoreGenerator(tree, {
project: 'my-app',
minimal: false,
name: 'users',
facade: true,
});
// ASSERT
expect(
tree.read('my-app/src/app/app.module.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('/my-app/src/app/+state/users.facade.ts', 'utf-8')
).toMatchSnapshot();
});
it('should update package.json', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
await createNgModuleApp(tree);
// ACT
await ngrxRootStoreGenerator(tree, {
project: 'my-app',
minimal: true,
});
// ASSERT
const packageJson = 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=true', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
await createNgModuleApp(tree);
// ACT
await ngrxRootStoreGenerator(tree, {
project: 'my-app',
minimal: true,
skipPackageJson: true,
});
// ASSERT
const packageJson = 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();
expect(packageJson.devDependencies['jasmine-marbles']).toBeUndefined();
});
});
describe('Standalone APIs', () => {
it('should error when project does not exist', async () => {
const tree = createTreeWithEmptyWorkspace();
await expect(
ngrxRootStoreGenerator(tree, {
project: 'non-exist',
minimal: true,
name: '',
})
).rejects.toThrowError();
});
it('should error when minimal false, but name is undefined or falsy', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
await createStandaloneApp(tree);
// ACT & ASSERT
await expect(
ngrxRootStoreGenerator(tree, {
project: 'my-app',
minimal: false,
name: undefined,
})
).rejects.toThrowError();
});
it('should add an empty root module when --minimal=true', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
await createStandaloneApp(tree);
// ACT
await ngrxRootStoreGenerator(tree, {
project: 'my-app',
minimal: true,
});
// ASSERT
expect(
tree.read('my-app/src/app/app.config.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.exists('/my-app/src/app/+state/users.actions.ts')).toBe(
false
);
expect(tree.exists('/my-app/src/app/+state/users.effects.ts')).toBe(
false
);
expect(tree.exists('/my-app/src/app/+state/users.effects.spec.ts')).toBe(
false
);
expect(tree.exists('/my-app/src/app/+state/users.reducer.ts')).toBe(
false
);
expect(tree.exists('/my-app/src/app/+state/users.selectors.ts')).toBe(
false
);
expect(
tree.exists('/my-app/src/app/+state/users.selectors.spec.ts')
).toBe(false);
});
it('should add a root module and root state when --minimal=false', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
await createStandaloneApp(tree);
// ACT
await ngrxRootStoreGenerator(tree, {
project: 'my-app',
minimal: false,
name: 'users',
});
// ASSERT
expect(
tree.read('my-app/src/app/app.config.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('/my-app/src/app/+state/users.actions.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('/my-app/src/app/+state/users.effects.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('/my-app/src/app/+state/users.effects.spec.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('/my-app/src/app/+state/users.reducer.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('/my-app/src/app/+state/users.selectors.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('/my-app/src/app/+state/users.selectors.spec.ts', 'utf-8')
).toMatchSnapshot();
});
it('should add a facade when --facade=true', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
await createStandaloneApp(tree);
// ACT
await ngrxRootStoreGenerator(tree, {
project: 'my-app',
minimal: false,
name: 'users',
facade: true,
});
// ASSERT
expect(
tree.read('my-app/src/app/app.config.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('/my-app/src/app/+state/users.facade.ts', 'utf-8')
).toMatchSnapshot();
});
it('should update package.json', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
await createStandaloneApp(tree);
// ACT
await ngrxRootStoreGenerator(tree, {
project: 'my-app',
minimal: true,
});
// ASSERT
const packageJson = 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=true', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
await createStandaloneApp(tree);
// ACT
await ngrxRootStoreGenerator(tree, {
project: 'my-app',
minimal: true,
skipPackageJson: true,
});
// ASSERT
const packageJson = 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();
expect(packageJson.devDependencies['jasmine-marbles']).toBeUndefined();
});
});
});
async function createNgModuleApp(tree: Tree, name = 'my-app') {
await applicationGenerator(tree, {
name,
standalone: false,
routing: true,
});
}
async function createStandaloneApp(tree: Tree, name = 'my-app') {
await applicationGenerator(tree, {
name,
standalone: true,
routing: true,
});
}

View File

@ -0,0 +1,48 @@
import type { Tree } from '@nx/devkit';
import { formatFiles, GeneratorCallback } from '@nx/devkit';
import type { Schema } from './schema';
import {
addImportsToModule,
addNgRxToPackageJson,
normalizeOptions,
validateOptions,
} from './lib';
import ngrxFeatureStoreGenerator from '../ngrx-feature-store/ngrx-feature-store';
export async function ngrxRootStoreGenerator(tree: Tree, schema: Schema) {
validateOptions(tree, schema);
const options = normalizeOptions(tree, schema);
if (!options.skipImport) {
addImportsToModule(tree, options);
}
if (!options.minimal) {
await ngrxFeatureStoreGenerator(tree, {
name: options.name,
parent: options.parent,
directory: options.directory,
minimal: false,
facade: options.facade,
barrels: false,
skipImport: false,
skipPackageJson: true,
skipFormat: true,
});
}
let packageInstallationTask: GeneratorCallback = () => {};
if (!options.skipPackageJson) {
packageInstallationTask = addNgRxToPackageJson(tree, options);
}
if (!options.skipFormat) {
await formatFiles(tree);
}
return packageInstallationTask;
}
export default ngrxRootStoreGenerator;

View File

@ -0,0 +1,10 @@
export interface Schema {
project: string;
minimal: boolean;
name?: string;
directory?: string;
facade?: boolean;
skipFormat?: boolean;
skipImport?: boolean;
skipPackageJson?: boolean;
}

View File

@ -0,0 +1,66 @@
{
"$schema": "http://json-schema.org/schema",
"$id": "NxNgrxRootStoreGenerator",
"title": "Add NgRx support to an application.",
"description": "Adds NgRx support to an application.",
"cli": "nx",
"type": "object",
"properties": {
"project": {
"type": "string",
"description": "The name of the application to generate the NgRx configuration for.",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "What app would you like to generate a NgRx configuration for?",
"x-dropdown": "projects"
},
"minimal": {
"type": "boolean",
"default": true,
"description": "Only register the root state management setup or also generate a global feature state.",
"x-priority": "important"
},
"name": {
"type": "string",
"description": "Name of the NgRx state, such as `products` or `users`. Recommended to use the plural form of the name.",
"x-priority": "important"
},
"route": {
"type": "string",
"description": "The route that the Standalone NgRx Providers should be added to. _Note: This is only supported in Angular versions >= 14.1.0_.",
"default": "''"
},
"directory": {
"type": "string",
"default": "+state",
"description": "The name of the folder used to contain/group the generated NgRx files."
},
"facade": {
"type": "boolean",
"default": false,
"description": "Create a Facade class for the the feature.",
"x-prompt": "Would you like to use a Facade with your NgRx state?"
},
"skipImport": {
"type": "boolean",
"default": false,
"description": "Generate NgRx feature files without registering the feature in the NgModule."
},
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
"default": false,
"x-priority": "internal"
},
"skipPackageJson": {
"type": "boolean",
"default": false,
"description": "Do not update the `package.json` with NgRx dependencies.",
"x-priority": "internal"
}
},
"additionalProperties": false,
"required": ["project"]
}