Skip to content

Commit fdd701e

Browse files
authored
feat(data): standalone support for ng add @ngrx/data (#4019)
Closes #3935
1 parent 099a07c commit fdd701e

File tree

3 files changed

+185
-5
lines changed

3 files changed

+185
-5
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Data ng-add Schematic Migration of ngrx-data Data ng-add Schematic for standalone application provides data without effects 1`] = `
4+
"import { ApplicationConfig } from '@angular/core';
5+
import { provideEntityData } from '@ngrx/data';
6+
import { entityConfig } from './entity-metadata';
7+
8+
export const appConfig: ApplicationConfig = {
9+
providers: [provideEntityData(entityConfig)]
10+
};
11+
"
12+
`;
13+
14+
exports[`Data ng-add Schematic Migration of ngrx-data Data ng-add Schematic for standalone application provides data without entityConfig 1`] = `
15+
"import { ApplicationConfig } from '@angular/core';
16+
import { provideEntityData, withEffects } from '@ngrx/data';
17+
18+
export const appConfig: ApplicationConfig = {
19+
providers: [provideEntityData({}, withEffects())]
20+
};
21+
"
22+
`;
23+
24+
exports[`Data ng-add Schematic Migration of ngrx-data Data ng-add Schematic for standalone application provides default data setup 1`] = `
25+
"import { ApplicationConfig } from '@angular/core';
26+
import { provideEntityData, withEffects } from '@ngrx/data';
27+
import { entityConfig } from './entity-metadata';
28+
29+
export const appConfig: ApplicationConfig = {
30+
providers: [provideEntityData(entityConfig, withEffects())]
31+
};
32+
"
33+
`;

modules/data/schematics/ng-add/index.spec.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,5 +367,61 @@ describe('Data ng-add Schematic', () => {
367367

368368
expect(actual).toBe(output);
369369
});
370+
371+
describe('Data ng-add Schematic for standalone application', () => {
372+
const projectPath = getTestProjectPath(undefined, {
373+
name: 'bar-standalone',
374+
});
375+
376+
const standaloneDefaultOptions = {
377+
...defaultOptions,
378+
project: 'bar-standalone',
379+
};
380+
381+
it('provides default data setup', async () => {
382+
const options = { ...standaloneDefaultOptions };
383+
const tree = await schematicRunner.runSchematic(
384+
'ng-add',
385+
options,
386+
appTree
387+
);
388+
389+
const content = tree.readContent(
390+
`${projectPath}/src/app/app.config.ts`
391+
);
392+
393+
expect(content).toMatchSnapshot();
394+
});
395+
396+
it('provides data without effects', async () => {
397+
const options = { ...standaloneDefaultOptions, effects: false };
398+
const tree = await schematicRunner.runSchematic(
399+
'ng-add',
400+
options,
401+
appTree
402+
);
403+
404+
const content = tree.readContent(
405+
`${projectPath}/src/app/app.config.ts`
406+
);
407+
408+
expect(content).toMatchSnapshot();
409+
});
410+
411+
it('provides data without entityConfig', async () => {
412+
const options = { ...standaloneDefaultOptions, entityConfig: false };
413+
const tree = await schematicRunner.runSchematic(
414+
'ng-add',
415+
options,
416+
appTree
417+
);
418+
419+
const content = tree.readContent(
420+
`${projectPath}/src/app/app.config.ts`
421+
);
422+
423+
expect(content).toMatchSnapshot();
424+
});
425+
});
370426
});
371427
});

modules/data/schematics/ng-add/index.ts

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import { Path } from '@angular-devkit/core';
33
import {
44
apply,
55
applyTemplates,
6+
branchAndMerge,
67
chain,
78
mergeWith,
89
move,
9-
noop,
1010
Rule,
1111
SchematicContext,
1212
SchematicsException,
13+
noop,
1314
Tree,
1415
url,
1516
} from '@angular-devkit/schematics';
@@ -29,6 +30,12 @@ import {
2930
visitTSSourceFiles,
3031
} from '../../schematics-core';
3132
import { Schema as EntityDataOptions } from './schema';
33+
import { getProjectMainFile } from '../../schematics-core/utility/project';
34+
import { isStandaloneApp } from '../../schematics-core/utility/standalone';
35+
import {
36+
addFunctionalProvidersToStandaloneBootstrap,
37+
callsProvidersFunction,
38+
} from '@schematics/angular/private/standalone';
3239

3340
function addNgRxDataToPackageJson() {
3441
return (host: Tree, context: SchematicContext) => {
@@ -95,6 +102,83 @@ function addEntityDataToNgModule(options: EntityDataOptions): Rule {
95102
};
96103
}
97104

105+
function addStandaloneConfig(options: EntityDataOptions): Rule {
106+
return (host: Tree) => {
107+
const mainFile = getProjectMainFile(host, options);
108+
if (host.exists(mainFile)) {
109+
const providerFn = 'provideEntityData';
110+
111+
if (callsProvidersFunction(host, mainFile, providerFn)) {
112+
// exit because the store config is already provided
113+
return host;
114+
}
115+
116+
const providerOptions = [
117+
...(options.entityConfig
118+
? [ts.factory.createIdentifier(`entityConfig`)]
119+
: [ts.factory.createIdentifier(`{}`)]),
120+
...(options.effects
121+
? [ts.factory.createIdentifier(`withEffects()`)]
122+
: []),
123+
];
124+
125+
const patchedConfigFile = addFunctionalProvidersToStandaloneBootstrap(
126+
host,
127+
mainFile,
128+
providerFn,
129+
'@ngrx/data',
130+
providerOptions
131+
);
132+
133+
const configFileContent = host.read(patchedConfigFile);
134+
const source = ts.createSourceFile(
135+
patchedConfigFile,
136+
configFileContent?.toString('utf-8') || '',
137+
ts.ScriptTarget.Latest,
138+
true
139+
);
140+
141+
const recorder = host.beginUpdate(patchedConfigFile);
142+
143+
const changes = [];
144+
145+
if (options.effects) {
146+
const withEffectsImport = insertImport(
147+
source,
148+
patchedConfigFile,
149+
'withEffects',
150+
'@ngrx/data'
151+
);
152+
153+
changes.push(withEffectsImport);
154+
}
155+
156+
if (options.entityConfig) {
157+
const entityConfigImport = insertImport(
158+
source,
159+
patchedConfigFile,
160+
'entityConfig',
161+
'./entity-metadata'
162+
);
163+
164+
changes.push(entityConfigImport);
165+
}
166+
167+
changes.forEach((change: any) => {
168+
recorder.insertLeft(change.pos, change.toAdd);
169+
});
170+
171+
host.commitUpdate(recorder);
172+
173+
return host;
174+
}
175+
176+
throw new SchematicsException(
177+
`Main file not found for a project ${options.project}`
178+
);
179+
};
180+
}
181+
98182
const renames = {
99183
NgrxDataModule: 'EntityDataModule',
100184
NgrxDataModuleWithoutEffects: 'EntityDataModuleWithoutEffects',
@@ -285,22 +369,29 @@ export default function (options: EntityDataOptions): Rule {
285369
return (host: Tree, context: SchematicContext) => {
286370
(options as any).name = '';
287371
options.path = getProjectPath(host, options);
372+
const mainFile = getProjectMainFile(host, options);
373+
const isStandalone = isStandaloneApp(host, mainFile);
288374
options.effects = options.effects === undefined ? true : options.effects;
289-
options.module = options.module
290-
? findModuleFromOptions(host, options as any)
291-
: options.module;
375+
options.module =
376+
options.module && !isStandalone
377+
? findModuleFromOptions(host, options as any)
378+
: options.module;
292379

293380
const parsedPath = parseName(options.path, '');
294381
options.path = parsedPath.path;
295382

383+
const configOrModuleUpdate = isStandalone
384+
? addStandaloneConfig(options)
385+
: addEntityDataToNgModule(options);
386+
296387
return chain([
297388
options && options.skipPackageJson ? noop() : addNgRxDataToPackageJson(),
298389
options.migrateNgrxData
299390
? chain([
300391
removeAngularNgRxDataFromPackageJson(),
301392
renameNgrxDataModule(),
302393
])
303-
: addEntityDataToNgModule(options),
394+
: branchAndMerge(chain([configOrModuleUpdate])),
304395
options.entityConfig
305396
? createEntityConfigFile(options, parsedPath.path)
306397
: noop(),

0 commit comments

Comments
 (0)