Skip to content

Commit

Permalink
feat(angular): add backwards compat support for the ngrx generator (#…
Browse files Browse the repository at this point in the history
…14348)

(cherry picked from commit 295547b)
  • Loading branch information
leosvelperez authored and FrozenPandaz committed Jan 17, 2023
1 parent 7bedb39 commit 1c21e0c
Show file tree
Hide file tree
Showing 23 changed files with 373 additions and 24 deletions.
4 changes: 2 additions & 2 deletions docs/generated/packages/angular/generators/ngrx.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@
},
"parent": {
"type": "string",
"description": "The path to the `NgModule` or the `Routes` definition file (for Standalone API usage) where the feature state will be registered. The host directory will create/use the new state directory.",
"description": "The path to the `NgModule` or the `Routes` definition file (for Standalone API usage) where the feature state will be registered. _Note: The Standalone API usage is only supported in Angular versions >= 14.1.0_.",
"x-prompt": "What is the path to the module or Routes definition where this NgRx state should be registered?"
},
"route": {
"type": "string",
"description": "The route that the Standalone NgRx Providers should be added to.",
"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": {
Expand Down
10 changes: 9 additions & 1 deletion packages/angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
},
"dependencies": {
"@angular-devkit/schematics": "~15.1.0",
"@nguniversal/builders": "~15.0.0",
"@nrwl/cypress": "file:../cypress",
"@nrwl/devkit": "file:../devkit",
"@nrwl/jest": "file:../jest",
Expand All @@ -61,6 +60,15 @@
"webpack": "^5.75.0",
"webpack-merge": "5.7.3"
},
"peerDependencies": {
"@nguniversal/builders": "~15.0.0",
"rxjs": "^6.5.3 || ^7.5.0"
},
"peerDependenciesMeta": {
"@nguniversal/builders": {
"optional": true
}
},
"publishConfig": {
"access": "public"
}
Expand Down
114 changes: 114 additions & 0 deletions packages/angular/src/generators/ngrx/__snapshots__/ngrx.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -604,3 +604,117 @@ import { UsersEffects } from './+state/users.effects';
import { UsersFacade } from './+state/users.facade';
export const appRoutes: Routes = [{ path: '', component: NxWelcomeComponent , providers: [UsersFacade, provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer), provideEffects(UsersEffects)]}];"
`;
exports[`ngrx angular v14 support should generate the ngrx effects using "inject" for versions >= 14.1.0 1`] = `
"import { Injectable, inject } from '@angular/core';
import { createEffect, Actions, ofType } from '@ngrx/effects';
import * as UsersActions from './users.actions';
import * as UsersFeature from './users.reducer';
import {switchMap, catchError, of} from 'rxjs';
@Injectable()
export class UsersEffects {
private actions$ = inject(Actions);
init$ = createEffect(() => this.actions$.pipe(
ofType(UsersActions.initUsers),
switchMap(() => of(UsersActions.loadUsersSuccess({ users: [] }))),
catchError((error) => {
console.error('Error', error);
return of(UsersActions.loadUsersFailure({ error }));
}
)
));
}
"
`;
exports[`ngrx angular v14 support should generate the ngrx effects with no usage of "inject" 1`] = `
"import { Injectable } from '@angular/core';
import { createEffect, Actions, ofType } from '@ngrx/effects';
import * as UsersActions from './users.actions';
import * as UsersFeature from './users.reducer';
import {switchMap, catchError, of} from 'rxjs';
@Injectable()
export class UsersEffects {
init$ = createEffect(() => this.actions$.pipe(
ofType(UsersActions.initUsers),
switchMap(() => of(UsersActions.loadUsersSuccess({ users: [] }))),
catchError((error) => {
console.error('Error', error);
return of(UsersActions.loadUsersFailure({ error }));
}
)
));
constructor(private readonly actions$: Actions) {}
}
"
`;
exports[`ngrx angular v14 support should generate the ngrx facade using "inject" for versions >= 14.1.0 1`] = `
"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 angular v14 support should generate the ngrx facade with no usage of "inject" 1`] = `
"import { Injectable } 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 {
/**
* 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));
constructor(private readonly store: Store) {}
/**
* Use the initialization action to perform one
* or more tasks in your Effects.
*/
init() {
this.store.dispatch(UsersActions.initUsers());
}
}
"
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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 {switchMap, catchError, of} from 'rxjs';

@Injectable()
export class <%= className %>Effects {
init$ = createEffect(() => this.actions$.pipe(
ofType(<%= className %>Actions.init<%= className %>),
switchMap(() => of(<%= className %>Actions.load<%= className %>Success({ <%= propertyName %>: [] }))),
catchError((error) => {
console.error('Error', error);
return of(<%= className %>Actions.load<%= className %>Failure({ error }));
}
)
));

constructor(private readonly actions$: Actions) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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';

@Injectable()
export class <%= className %>Facade {
/**
* Combine pieces of state using createSelector,
* and expose them as observables through the facade.
*/
loaded$ = this.store.pipe(select(<%= className %>Selectors.select<%= className %>Loaded));
all<%= className %>$ = this.store.pipe(select(<%= className %>Selectors.selectAll<%= className %>));
selected<%= className %>$ = this.store.pipe(select(<%= className %>Selectors.selectEntity));

constructor(private readonly store: Store) {}

/**
* Use the initialization action to perform one
* or more tasks in your Effects.
*/
init() {
this.store.dispatch(<%= className %>Actions.init<%= className %>());
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { GeneratorCallback, Tree } from '@nrwl/devkit';
import { addDependenciesToPackageJson, readJson } from '@nrwl/devkit';
import { gte } from 'semver';
import {
ngrxVersion,
rxjsVersion as defaultRxjsVersion,
} from '../../../utils/versions';
import { checkAndCleanWithSemver } from '@nrwl/workspace/src/utilities/version-utils';
import { gte } from 'semver';
import { getPkgVersionForAngularMajorVersion } from '../../../utils/version-utils';
import { rxjsVersion as defaultRxjsVersion } from '../../../utils/versions';
import { getInstalledAngularMajorVersion } from '../../utils/angular-version-utils';

export function addNgRxToPackageJson(tree: Tree): GeneratorCallback {
let rxjsVersion: string;
Expand All @@ -18,6 +17,13 @@ export function addNgRxToPackageJson(tree: Tree): GeneratorCallback {
rxjsVersion = checkAndCleanWithSemver('rxjs', defaultRxjsVersion);
}
const jasmineMarblesVersion = gte(rxjsVersion, '7.0.0') ? '~0.9.1' : '~0.8.3';

const angularMajorVersion = getInstalledAngularMajorVersion(tree);
const ngrxVersion = getPkgVersionForAngularMajorVersion(
'ngrxVersion',
angularMajorVersion
);

return addDependenciesToPackageJson(
tree,
{
Expand Down
18 changes: 17 additions & 1 deletion packages/angular/src/generators/ngrx/lib/generate-files.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Tree } from '@nrwl/devkit';
import { generateFiles, joinPathFragments, names } from '@nrwl/devkit';
import { lt } from 'semver';
import { getInstalledAngularVersion } from '../../utils/angular-version-utils';
import { NormalizedNgRxGeneratorOptions } from './normalize-options';

/**
Expand All @@ -14,7 +16,7 @@ export function generateNgrxFilesFromTemplates(

generateFiles(
tree,
joinPathFragments(__dirname, '..', 'files'),
joinPathFragments(__dirname, '..', 'files', 'latest'),
options.parentDirectory,
{
...options,
Expand All @@ -23,6 +25,20 @@ export function generateNgrxFilesFromTemplates(
}
);

const angularVersion = getInstalledAngularVersion(tree);
if (lt(angularVersion, '14.1.0')) {
generateFiles(
tree,
joinPathFragments(__dirname, '..', 'files', 'no-inject'),
options.parentDirectory,
{
...options,
...projectNames,
tmpl: '',
}
);
}

if (!options.facade) {
tree.delete(
joinPathFragments(
Expand Down
1 change: 1 addition & 0 deletions packages/angular/src/generators/ngrx/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { addImportsToModule } from './add-imports-to-module';
export { addNgRxToPackageJson } from './add-ngrx-to-package-json';
export { generateNgrxFilesFromTemplates } from './generate-files';
export { normalizeOptions } from './normalize-options';
export { validateOptions } from './validate-options';
41 changes: 41 additions & 0 deletions packages/angular/src/generators/ngrx/lib/validate-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { Tree } from '@nrwl/devkit';
import { tsquery } from '@phenomnomnominal/tsquery';
import { lt } from 'semver';
import { getInstalledAngularVersion } from '../../utils/angular-version-utils';
import type { NgRxGeneratorOptions } from '../schema';

export function validateOptions(
tree: Tree,
options: NgRxGeneratorOptions
): void {
if (!options.module && !options.parent) {
throw new Error('Please provide a value for "--parent"!');
}
if (options.module && !tree.exists(options.module)) {
throw new Error(`Module does not exist: ${options.module}.`);
}
if (options.parent && !tree.exists(options.parent)) {
throw new Error(`Parent does not exist: ${options.parent}.`);
}

const angularVersion = getInstalledAngularVersion(tree);
const parentPath = options.parent ?? options.module;
if (parentPath && lt(angularVersion, '14.1.0')) {
const parentContent = tree.read(parentPath, 'utf-8');
const ast = tsquery.ast(parentContent);

const NG_MODULE_DECORATOR_SELECTOR =
'ClassDeclaration > Decorator > CallExpression:has(Identifier[name=NgModule])';
const nodes = tsquery(ast, NG_MODULE_DECORATOR_SELECTOR, {
visitAllChildren: true,
});
if (nodes.length === 0) {
throw new Error(
`The provided parent path "${parentPath}" does not contain an "NgModule". ` +
'Please make sure to provide a path to an "NgModule" where the state will be registered. ' +
'If you are trying to use a "Routes" definition file (for Standalone API usage), ' +
'please note this is not supported in Angular versions lower than 14.1.0.'
);
}
}
}
Loading

0 comments on commit 1c21e0c

Please sign in to comment.