Skip to content

Commit c7a296f

Browse files
Jordan Powelljordanpowell88
authored andcommitted
refactor(effects): refactor types in effects, expose metadata key for createEffect refactor(example): close sidenav when logoutConfirmation is dispatched (ngrx#2189) Closes ngrx#2186 docs: remove github link from navigation (ngrx#2188) Closes ngrx#2185 feat(schematics): add message prompts for individual schematics (ngrx#2143) test(store): fix flaky integration test (ngrx#2209)
2 parents b109c03 + e641d9d commit c7a296f

49 files changed

Lines changed: 763 additions & 282 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { EntityMetadataMap, EntityDataModuleConfig } from '@ngrx/data';
2+
3+
const entityMetadata: EntityMetadataMap = {};
4+
5+
const pluralNames = { };
6+
7+
export const entityConfig: EntityDataModuleConfig = {
8+
entityMetadata,
9+
pluralNames
10+
};

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

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,45 @@ describe('Data ng-add Schematic', () => {
6868
expect(thrownError).toBeDefined();
6969
});
7070

71+
it('should add entity-metadata config to EntityDataModule', () => {
72+
const options = { ...defaultOptions, effects: false, entityConfig: true };
73+
74+
const tree = schematicRunner.runSchematic('ng-add', options, appTree);
75+
const content = tree.readContent(`${projectPath}/src/app/app.module.ts`);
76+
expect(content).toMatch(
77+
/import { entityConfig } from '.\/entity-metadata'/
78+
);
79+
expect(content).toMatch(
80+
/EntityDataModuleWithoutEffects.forRoot\(entityConfig\)/
81+
);
82+
});
83+
84+
it('should add entity-metadata config file', () => {
85+
const options = { ...defaultOptions, entityConfig: true };
86+
87+
const tree = schematicRunner.runSchematic('ng-add', options, appTree);
88+
expect(
89+
tree.files.indexOf(`${projectPath}/src/app/entity-metadata.ts`)
90+
).toBeGreaterThanOrEqual(0);
91+
});
92+
93+
it('should add entity-metadata config to EntityDataModule', () => {
94+
const options = { ...defaultOptions, entityConfig: true };
95+
96+
const tree = schematicRunner.runSchematic('ng-add', options, appTree);
97+
const content = tree.readContent(`${projectPath}/src/app/app.module.ts`);
98+
expect(content).toMatch(
99+
/import { entityConfig } from '.\/entity-metadata'/
100+
);
101+
expect(content).toMatch(/EntityDataModule.forRoot\(entityConfig\)/);
102+
});
103+
71104
it('should import EntityDataModuleWithoutEffects into a specified module', () => {
72105
const options = {
73106
...defaultOptions,
74107
module: 'app.module.ts',
75108
effects: false,
109+
entityConfig: false,
76110
};
77111

78112
const tree = schematicRunner.runSchematic('ng-add', options, appTree);
@@ -83,15 +117,15 @@ describe('Data ng-add Schematic', () => {
83117
});
84118

85119
it('should register EntityDataModule in the provided module', () => {
86-
const options = { ...defaultOptions };
120+
const options = { ...defaultOptions, entityConfig: false };
87121

88122
const tree = schematicRunner.runSchematic('ng-add', options, appTree);
89123
const content = tree.readContent(`${projectPath}/src/app/app.module.ts`);
90124
expect(content).toMatch(/EntityDataModule\n/);
91125
});
92126

93127
it('should register EntityDataModuleWithoutEffects in the provided module', () => {
94-
const options = { ...defaultOptions, effects: false };
128+
const options = { ...defaultOptions, effects: false, entityConfig: false };
95129

96130
const tree = schematicRunner.runSchematic('ng-add', options, appTree);
97131
const content = tree.readContent(`${projectPath}/src/app/app.module.ts`);

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

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,33 @@
1-
import * as ts from 'typescript';
1+
import { Path } from '@angular-devkit/core';
22
import {
3-
Rule,
4-
SchematicContext,
5-
Tree,
3+
apply,
4+
applyTemplates,
65
chain,
6+
mergeWith,
7+
move,
78
noop,
9+
Rule,
10+
SchematicContext,
811
SchematicsException,
12+
Tree,
13+
url,
914
} from '@angular-devkit/schematics';
1015
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
1116
import {
17+
addImportToModule,
1218
addPackageToPackageJson,
13-
platformVersion,
19+
commitChanges,
20+
createReplaceChange,
1421
findModuleFromOptions,
15-
insertImport,
1622
getProjectPath,
23+
insertImport,
1724
parseName,
18-
addImportToModule,
19-
createReplaceChange,
25+
platformVersion,
2026
ReplaceChange,
27+
stringUtils,
2128
visitTSSourceFiles,
22-
commitChanges,
2329
} from '@ngrx/data/schematics-core';
30+
import * as ts from 'typescript';
2431
import { Schema as EntityDataOptions } from './schema';
2532

2633
function addNgRxDataToPackageJson() {
@@ -53,6 +60,7 @@ function addEntityDataToNgModule(options: EntityDataOptions): Rule {
5360
const moduleToImport = options.effects
5461
? 'EntityDataModule'
5562
: 'EntityDataModuleWithoutEffects';
63+
5664
const effectsModuleImport = insertImport(
5765
source,
5866
modulePath,
@@ -63,11 +71,24 @@ function addEntityDataToNgModule(options: EntityDataOptions): Rule {
6371
const [dateEntityNgModuleImport] = addImportToModule(
6472
source,
6573
modulePath,
66-
moduleToImport,
74+
options.entityConfig
75+
? [moduleToImport, 'forRoot(entityConfig)'].join('.')
76+
: moduleToImport,
6777
''
6878
);
6979

7080
const changes = [effectsModuleImport, dateEntityNgModuleImport];
81+
82+
if (options.entityConfig) {
83+
const entityConfigImport = insertImport(
84+
source,
85+
modulePath,
86+
'entityConfig',
87+
'./entity-metadata'
88+
);
89+
changes.push(entityConfigImport);
90+
}
91+
7192
commitChanges(host, source.fileName, changes);
7293

7394
return host;
@@ -248,6 +269,18 @@ function throwIfModuleNotSpecified(host: Tree, module?: string) {
248269
}
249270
}
250271

272+
function createEntityConfigFile(options: EntityDataOptions, path: Path) {
273+
return mergeWith(
274+
apply(url('./files'), [
275+
applyTemplates({
276+
...stringUtils,
277+
...options,
278+
}),
279+
move(path),
280+
])
281+
);
282+
}
283+
251284
export default function(options: EntityDataOptions): Rule {
252285
return (host: Tree, context: SchematicContext) => {
253286
(options as any).name = '';
@@ -268,6 +301,9 @@ export default function(options: EntityDataOptions): Rule {
268301
renameNgrxDataModule(),
269302
])
270303
: addEntityDataToNgModule(options),
304+
options.entityConfig
305+
? createEntityConfigFile(options, parsedPath.path)
306+
: noop(),
271307
])(host, context);
272308
};
273309
}

modules/data/schematics/ng-add/schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
"default": false,
4040
"description": "Migrate from ngrx-data, will rename modules.",
4141
"alias": "migrate"
42+
},
43+
"entityConfig": {
44+
"type": "boolean",
45+
"default": true,
46+
"description": "Create the Entity config file"
4247
}
4348
},
4449
"required": []

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ export interface Schema {
55
project?: string;
66
module?: string;
77
migrateNgrxData?: boolean;
8+
entityConfig?: boolean;
89
}

modules/effects/spec/effect_creator.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ describe('createEffect()', () => {
99
});
1010

1111
it('should dispatch by default', () => {
12-
const effect: any = createEffect(() => of({ type: 'a' }));
12+
const effect = createEffect(() => of({ type: 'a' }));
1313

1414
expect(effect['__@ngrx/effects_create__']).toEqual(
1515
jasmine.objectContaining({ dispatch: true })
1616
);
1717
});
1818

1919
it('should be possible to explicitly create a dispatching effect', () => {
20-
const effect: any = createEffect(() => of({ type: 'a' }), {
20+
const effect = createEffect(() => of({ type: 'a' }), {
2121
dispatch: true,
2222
});
2323

@@ -27,7 +27,7 @@ describe('createEffect()', () => {
2727
});
2828

2929
it('should be possible to create a non-dispatching effect', () => {
30-
const effect: any = createEffect(() => of({ type: 'a' }), {
30+
const effect = createEffect(() => of({ someProp: 'a' }), {
3131
dispatch: false,
3232
});
3333

@@ -37,7 +37,7 @@ describe('createEffect()', () => {
3737
});
3838

3939
it('should be possible to create a non-dispatching effect returning a non-action', () => {
40-
const effect: any = createEffect(() => of('foo'), {
40+
const effect = createEffect(() => of('foo'), {
4141
dispatch: false,
4242
});
4343

modules/effects/src/effect_creator.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { Observable } from 'rxjs';
22
import { Action } from '@ngrx/store';
3-
import { EffectMetadata, EffectConfig } from './models';
3+
import { EffectMetadata, EffectConfig, DEFAULT_EFFECT_CONFIG } from './models';
44

55
const CREATE_EFFECT_METADATA_KEY = '__@ngrx/effects_create__';
66

7+
interface CreateEffectMetadata {
8+
[CREATE_EFFECT_METADATA_KEY]: EffectConfig;
9+
}
10+
711
type DispatchType<T> = T extends { dispatch: infer U } ? U : unknown;
812
type ObservableReturnType<T> = T extends false
913
? Observable<unknown>
@@ -45,27 +49,22 @@ export function createEffect<
4549
T extends DispatchType<C>,
4650
O extends ObservableReturnType<T>,
4751
R extends O | ((...args: any[]) => O)
48-
>(source: () => R, config?: Partial<C>): R {
52+
>(source: () => R, config?: Partial<C>): R & CreateEffectMetadata {
4953
const effect = source();
50-
// Right now both createEffect and @Effect decorator set default values.
51-
// Ideally that should only be done in one place that aggregates that info,
52-
// for example in mergeEffects().
5354
const value: EffectConfig = {
54-
dispatch: true,
55-
resubscribeOnError: true,
55+
...DEFAULT_EFFECT_CONFIG,
5656
...config, // Overrides any defaults if values are provided
5757
};
5858
Object.defineProperty(effect, CREATE_EFFECT_METADATA_KEY, {
5959
value,
6060
});
61-
return effect;
61+
return effect as typeof effect & CreateEffectMetadata;
6262
}
6363

64-
export function getCreateEffectMetadata<T>(instance: T): EffectMetadata<T>[] {
65-
const propertyNames = Object.getOwnPropertyNames(instance) as Extract<
66-
keyof T,
67-
string
68-
>[];
64+
export function getCreateEffectMetadata<
65+
T extends { [props in keyof T]: Object }
66+
>(instance: T): EffectMetadata<T>[] {
67+
const propertyNames = Object.getOwnPropertyNames(instance) as Array<keyof T>;
6968

7069
const metadata: EffectMetadata<T>[] = propertyNames
7170
.filter(
Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
import { compose } from '@ngrx/store';
2-
import { EffectMetadata, EffectConfig } from './models';
2+
3+
import {
4+
DEFAULT_EFFECT_CONFIG,
5+
EffectConfig,
6+
EffectMetadata,
7+
EffectPropertyKey,
8+
} from './models';
39
import { getSourceForInstance } from './utils';
410

511
const METADATA_KEY = '__@ngrx/effects__';
612

7-
export function Effect<T>({
8-
dispatch = true,
9-
resubscribeOnError = true,
10-
}: EffectConfig = {}): PropertyDecorator {
11-
return function<K extends Extract<keyof T, string>>(
13+
export function Effect(config: EffectConfig = {}) {
14+
return function<T extends Object, K extends EffectPropertyKey<T>>(
1215
target: T,
1316
propertyName: K
1417
) {
15-
// Right now both createEffect and @Effect decorator set default values.
16-
// Ideally that should only be done in one place that aggregates that info,
17-
// for example in mergeEffects().
1818
const metadata: EffectMetadata<T> = {
19+
...DEFAULT_EFFECT_CONFIG,
20+
...config, // Overrides any defaults if values are provided
1921
propertyName,
20-
dispatch,
21-
resubscribeOnError,
2222
};
23-
setEffectMetadataEntries<T>(target, [metadata]);
24-
} as (target: {}, propertyName: string | symbol) => void;
23+
addEffectMetadataEntry<T>(target, metadata);
24+
};
2525
}
2626

2727
export function getEffectDecoratorMetadata<T>(
@@ -35,23 +35,38 @@ export function getEffectDecoratorMetadata<T>(
3535
return effectsDecorators;
3636
}
3737

38-
function setEffectMetadataEntries<T>(
38+
/**
39+
* Type guard to detemine whether METADATA_KEY is already present on the Class
40+
* constructor
41+
*/
42+
function hasMetadataEntries<T extends Object>(
43+
sourceProto: T
44+
): sourceProto is typeof sourceProto & {
45+
constructor: typeof sourceProto.constructor & {
46+
[METADATA_KEY]: EffectMetadata<T>[];
47+
};
48+
} {
49+
return sourceProto.constructor.hasOwnProperty(METADATA_KEY);
50+
}
51+
52+
/** Add Effect Metadata to the Effect Class constructor under specific key */
53+
function addEffectMetadataEntry<T extends object>(
3954
sourceProto: T,
40-
entries: EffectMetadata<T>[]
55+
metadata: EffectMetadata<T>
4156
) {
42-
const constructor = sourceProto.constructor;
43-
const meta: Array<EffectMetadata<T>> = constructor.hasOwnProperty(
44-
METADATA_KEY
45-
)
46-
? (constructor as any)[METADATA_KEY]
47-
: Object.defineProperty(constructor, METADATA_KEY, { value: [] })[
48-
METADATA_KEY
49-
];
50-
Array.prototype.push.apply(meta, entries);
57+
if (hasMetadataEntries(sourceProto)) {
58+
sourceProto.constructor[METADATA_KEY].push(metadata);
59+
} else {
60+
Object.defineProperty(sourceProto.constructor, METADATA_KEY, {
61+
value: [metadata],
62+
});
63+
}
5164
}
5265

53-
function getEffectMetadataEntries<T>(sourceProto: T): EffectMetadata<T>[] {
54-
return sourceProto.constructor.hasOwnProperty(METADATA_KEY)
55-
? (sourceProto.constructor as any)[METADATA_KEY]
66+
function getEffectMetadataEntries<T extends object>(
67+
sourceProto: T
68+
): EffectMetadata<T>[] {
69+
return hasMetadataEntries(sourceProto)
70+
? sourceProto.constructor[METADATA_KEY]
5671
: [];
5772
}

modules/effects/src/effect_notification.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Notification, Observable } from 'rxjs';
44

55
export interface EffectNotification {
66
effect: Observable<any> | (() => Observable<any>);
7-
propertyName: string;
7+
propertyName: PropertyKey;
88
sourceName: string;
99
sourceInstance: any;
1010
notification: Notification<Action | null | undefined>;
@@ -46,7 +46,7 @@ function getEffectName({
4646
}: EffectNotification) {
4747
const isMethod = typeof sourceInstance[propertyName] === 'function';
4848

49-
return `"${sourceName}.${propertyName}${isMethod ? '()' : ''}"`;
49+
return `"${sourceName}.${String(propertyName)}${isMethod ? '()' : ''}"`;
5050
}
5151

5252
function stringify(action: Action | null | undefined) {

0 commit comments

Comments
 (0)