Skip to content

Commit ccd3dd7

Browse files
noratobrandonroberts
authored andcommitted
feat(data) create schematics to generate entity data (#2135)
1 parent 6ca3056 commit ccd3dd7

File tree

9 files changed

+362
-1
lines changed

9 files changed

+362
-1
lines changed

modules/schematics/collection.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,20 @@
5656
"schema": "./src/store/schema.json",
5757
"description": "Adds initial setup for state management"
5858
},
59-
6059
"selector": {
6160
"aliases": ["se"],
6261
"factory": "./src/selector",
6362
"schema": "./src/selector/schema.json",
6463
"description": "Add selectors"
6564
},
6665

66+
"data": {
67+
"aliases": ["dt"],
68+
"factory": "./src/data",
69+
"schema": "./src/data/schema.json",
70+
"description": "Adds a data entity service"
71+
},
72+
6773
"ng-add": {
6874
"aliases": ["init"],
6975
"factory": "./src/ng-add",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { TestBed } from '@angular/core/testing';
2+
import {
3+
EntityCollectionServiceElementsFactory
4+
} from '@ngrx/data';
5+
import { <%= classify(name) %>Service } from './<%= dasherize(name) %>.service';
6+
7+
describe('<%= classify(name) %>Service', () => {
8+
let service: <%= classify(name) %>Service;
9+
10+
beforeEach(async () => {
11+
await TestBed.configureTestingModule({
12+
providers: [
13+
EntityCollectionServiceElementsFactory,
14+
<%= classify(name) %>Service
15+
]
16+
});
17+
18+
await TestBed.compileComponents();
19+
});
20+
21+
beforeEach(() => {
22+
service = TestBed.get(<%= classify(name) %>Service)
23+
});
24+
25+
it('should create an instance', () => {
26+
expect(service).toBeTruthy();
27+
});
28+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Injectable } from '@angular/core';
2+
import {
3+
EntityCollectionServiceBase,
4+
EntityCollectionServiceElementsFactory
5+
} from '@ngrx/data';
6+
import { <%= classify(name) %> } from './<%= dasherize(name) %>';
7+
8+
@Injectable({ providedIn: 'root' })
9+
export class <%= classify(name) %>Service extends EntityCollectionServiceBase<<%= classify(name) %>> {
10+
constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) {
11+
super('<%= classify(name) %>', serviceElementsFactory);
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface <%= classify(name) %> {
2+
id?: any;
3+
}
+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import {
2+
SchematicTestRunner,
3+
UnitTestTree,
4+
} from '@angular-devkit/schematics/testing';
5+
import * as path from 'path';
6+
import { Schema as DataOptions } from './schema';
7+
import {
8+
getTestProjectPath,
9+
createWorkspace,
10+
defaultWorkspaceOptions,
11+
defaultAppOptions,
12+
} from '../../../schematics-core/testing';
13+
14+
describe('Data Schematic', () => {
15+
const schematicRunner = new SchematicTestRunner(
16+
'@ngrx/schematics',
17+
path.join(__dirname, '../../collection.json')
18+
);
19+
const defaultOptions: DataOptions = {
20+
name: 'foo',
21+
project: 'bar',
22+
spec: false,
23+
group: false,
24+
flat: true,
25+
};
26+
27+
const projectPath = getTestProjectPath();
28+
29+
let appTree: UnitTestTree;
30+
31+
beforeEach(async () => {
32+
appTree = await createWorkspace(schematicRunner, appTree);
33+
});
34+
35+
it('should create the data service to a specified project if provided', () => {
36+
const options = { ...defaultOptions, project: 'baz' };
37+
38+
const specifiedProjectPath = getTestProjectPath(defaultWorkspaceOptions, {
39+
...defaultAppOptions,
40+
name: 'baz',
41+
});
42+
43+
const tree = schematicRunner.runSchematic('data', options, appTree);
44+
const files = tree.files;
45+
expect(
46+
files.indexOf(`${specifiedProjectPath}/src/lib/foo.service.ts`)
47+
).toBeGreaterThanOrEqual(0);
48+
expect(
49+
files.indexOf(`${specifiedProjectPath}/src/lib/foo.ts`)
50+
).toBeGreaterThanOrEqual(0);
51+
});
52+
53+
it('should create the service and model files', () => {
54+
const tree = schematicRunner.runSchematic('data', defaultOptions, appTree);
55+
expect(
56+
tree.files.indexOf(`${projectPath}/src/app/foo.service.ts`)
57+
).toBeGreaterThanOrEqual(0);
58+
expect(
59+
tree.files.indexOf(`${projectPath}/src/app/foo.ts`)
60+
).toBeGreaterThanOrEqual(0);
61+
});
62+
63+
it('should create two files if spec is true', () => {
64+
const options = {
65+
...defaultOptions,
66+
spec: true,
67+
};
68+
const tree = schematicRunner.runSchematic('data', options, appTree);
69+
expect(
70+
tree.files.indexOf(`${projectPath}/src/app/foo.service.spec.ts`)
71+
).toBeGreaterThanOrEqual(0);
72+
expect(
73+
tree.files.indexOf(`${projectPath}/src/app/foo.service.ts`)
74+
).toBeGreaterThanOrEqual(0);
75+
});
76+
77+
describe('service class', () => {
78+
it('should import the correct model', () => {
79+
const options = { ...defaultOptions, spec: true };
80+
const tree = schematicRunner.runSchematic('data', options, appTree);
81+
const fileContent = tree.readContent(
82+
`${projectPath}/src/app/foo.service.ts`
83+
);
84+
85+
expect(fileContent).toMatch(/import { Foo } from '.\/foo';/);
86+
});
87+
88+
it('should extending EntityCollectionServiceBase', () => {
89+
const options = { ...defaultOptions, spec: true };
90+
const tree = schematicRunner.runSchematic('data', options, appTree);
91+
const fileContent = tree.readContent(
92+
`${projectPath}/src/app/foo.service.ts`
93+
);
94+
95+
expect(fileContent).toMatch(
96+
/export class FooService extends EntityCollectionServiceBase<Foo>/
97+
);
98+
99+
expect(fileContent).toMatch(/super\('Foo', serviceElementsFactory\);/);
100+
});
101+
});
102+
103+
it('should create model', () => {
104+
const options = { ...defaultOptions, spec: true };
105+
const tree = schematicRunner.runSchematic('data', options, appTree);
106+
const fileContent = tree.readContent(`${projectPath}/src/app/foo.ts`);
107+
108+
expect(fileContent).toMatch(/export interface Foo {/);
109+
expect(fileContent).toMatch(/id\?: any;/);
110+
});
111+
112+
it('should create spec class with right imports', () => {
113+
const options = { ...defaultOptions, spec: true };
114+
const tree = schematicRunner.runSchematic('data', options, appTree);
115+
const fileContent = tree.readContent(
116+
`${projectPath}/src/app/foo.service.spec.ts`
117+
);
118+
119+
expect(fileContent).toMatch(/import { FooService } from '.\/foo.service'/);
120+
});
121+
});

modules/schematics/src/data/index.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {
2+
apply,
3+
applyTemplates,
4+
branchAndMerge,
5+
chain,
6+
filter,
7+
mergeWith,
8+
move,
9+
noop,
10+
Rule,
11+
SchematicContext,
12+
Tree,
13+
url,
14+
} from '@angular-devkit/schematics';
15+
import {
16+
getProjectPath,
17+
parseName,
18+
stringUtils,
19+
} from '@ngrx/schematics/schematics-core';
20+
import { Schema as DataOptions } from './schema';
21+
22+
export default function(options: DataOptions): Rule {
23+
return (host: Tree, context: SchematicContext) => {
24+
options.path = getProjectPath(host, options);
25+
26+
const parsedPath = parseName(options.path, options.name);
27+
options.name = parsedPath.name;
28+
options.path = parsedPath.path;
29+
30+
const templateSource = apply(url('./files'), [
31+
options.spec
32+
? noop()
33+
: filter(path => !path.endsWith('.spec.ts.template')),
34+
applyTemplates({
35+
...stringUtils,
36+
'if-flat': (s: string) =>
37+
stringUtils.group(options.flat ? '' : s, options.group ? 'data' : ''),
38+
...options,
39+
}),
40+
move(parsedPath.path),
41+
]);
42+
43+
return chain([branchAndMerge(chain([mergeWith(templateSource)]))])(
44+
host,
45+
context
46+
);
47+
};
48+
}
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"$schema": "http://json-schema.org/schema",
3+
"id": "SchematicsNgRxData",
4+
"title": "NgRx Data Options Schema",
5+
"type": "object",
6+
"properties": {
7+
"name": {
8+
"description": "The name of the data entity.",
9+
"type": "string",
10+
"$default": {
11+
"$source": "argv",
12+
"index": 0
13+
}
14+
},
15+
"path": {
16+
"type": "string",
17+
"format": "path",
18+
"description": "The path to create the service.",
19+
"visible": false
20+
},
21+
"project": {
22+
"type": "string",
23+
"description": "The name of the project.",
24+
"aliases": ["p"]
25+
},
26+
"spec": {
27+
"type": "boolean",
28+
"description": "Specifies if a spec file is generated.",
29+
"default": false
30+
},
31+
"flat": {
32+
"type": "boolean",
33+
"default": true,
34+
"description": "Flag to indicate if a dir is created."
35+
},
36+
"group": {
37+
"type": "boolean",
38+
"default": false,
39+
"description": "Group the services within relative subfolders",
40+
"aliases": ["g"]
41+
}
42+
},
43+
"required": []
44+
}

modules/schematics/src/data/schema.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export interface Schema {
2+
/**
3+
* The name of the component.
4+
*/
5+
name: string;
6+
7+
/**
8+
* The path to create the component.
9+
*/
10+
path?: string;
11+
12+
/**
13+
* The name of the project.
14+
*/
15+
project?: string;
16+
17+
/**
18+
* Specifies if a spec file is generated.
19+
*/
20+
spec?: boolean;
21+
22+
/**
23+
* Flag to indicate if a dir is created.
24+
*/
25+
26+
flat?: boolean;
27+
28+
/**
29+
* Group entity metadata files within 'data' folder
30+
*/
31+
group?: boolean;
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Data
2+
3+
---
4+
5+
## Overview
6+
7+
Generates the data entity model and service.
8+
9+
## Command
10+
11+
```sh
12+
ng generate data EntityName [options]
13+
```
14+
15+
##### OR
16+
17+
```sh
18+
ng generate dt EntityName [options]
19+
```
20+
21+
### Options
22+
23+
Provide the project name where the entity files will be created.
24+
25+
- `--project`
26+
- Alias: `-p`
27+
- Type: `string`
28+
29+
Nest the data entity files within a folder based on the `data`.
30+
31+
- `--flat`
32+
- Type: `boolean`
33+
- Default: `true`
34+
35+
Group the data entity files within an `data` folder.
36+
37+
- `--group`
38+
- Alias: `-g`
39+
- Type: `boolean`
40+
- Default: `false`
41+
42+
Generate a spec file alongside the data entity files.
43+
44+
- `--spec`
45+
- Type: `boolean`
46+
- Default: `false`
47+
48+
#### Examples
49+
50+
Generate a `User` data entity files with an associated spec file.
51+
52+
```sh
53+
ng generate data User --spec
54+
```
55+
56+
Generate a `User` data entity files within a nested folder
57+
58+
```sh
59+
ng generate data User --flat false
60+
```
61+
62+
Generate a `User` data entity file within a nested `data` folder
63+
64+
```sh
65+
ng generate data User --group
66+
```

0 commit comments

Comments
 (0)