Skip to content

Commit 80becea

Browse files
BAE-7: Adds Provider API (#268)
* feat: adds provider based setup
1 parent a02b7ca commit 80becea

File tree

10 files changed

+934
-125
lines changed

10 files changed

+934
-125
lines changed

projects/ngrx-auto-entity-service/src/lib/config.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import { EntityCriteria, RetryCriteria } from './critera.model';
33
import { IEntityInfo } from '@briebug/ngrx-auto-entity';
44
import { Observable } from 'rxjs';
55

6-
export type DynamicAutoEntityServiceConfig = (...deps: any[]) => AutoEntityServiceConfig;
7-
86
export type APIPrefixResolver = (
97
operation: string,
108
info: IEntityInfo,
@@ -52,4 +50,4 @@ export interface AutoEntityServiceConfig {
5250
defaultRetry?: RetryCriteria;
5351
}
5452

55-
export const AUTO_ENTITY_CONFIG = new InjectionToken<AutoEntityServiceConfig>('auto-entity-config');
53+
export const AUTO_ENTITY_CONFIG = new InjectionToken<AutoEntityServiceConfig>('@briebug/ngrx-auto-entity-service Config');
Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,20 @@
1-
import { ModuleWithProviders, NgModule, Provider } from '@angular/core';
1+
import { ModuleWithProviders, NgModule } from '@angular/core';
22
import { HttpClientModule } from '@angular/common/http';
3-
import { EntityService } from './entity.service';
4-
import { AUTO_ENTITY_CONFIG, AutoEntityServiceConfig, DynamicAutoEntityServiceConfig } from './config';
5-
6-
const createConfigProvider = (config: AutoEntityServiceConfig | DynamicAutoEntityServiceConfig, deps?: any[]): Provider =>
7-
typeof config === 'function'
8-
? {
9-
provide: AUTO_ENTITY_CONFIG,
10-
useFactory: config,
11-
deps
12-
}
13-
: { provide: AUTO_ENTITY_CONFIG, useValue: config };
3+
import { AutoEntityServiceConfig } from './config';
4+
import { _provideAutoEntityService } from './ngrx-auto-entity-service.provider';
145

156
@NgModule({
16-
imports: [HttpClientModule],
17-
providers: [EntityService]
7+
imports: [HttpClientModule]
188
})
199
export class NgrxAutoEntityServiceModule {
20-
static forRoot(config: DynamicAutoEntityServiceConfig, deps?: any[]): ModuleWithProviders<NgrxAutoEntityServiceModule>;
2110
static forRoot(config: AutoEntityServiceConfig): ModuleWithProviders<NgrxAutoEntityServiceModule>;
22-
static forRoot(
23-
config: AutoEntityServiceConfig | DynamicAutoEntityServiceConfig,
24-
deps?: any[]
25-
): ModuleWithProviders<NgrxAutoEntityServiceModule> {
11+
static forRoot(config: () => AutoEntityServiceConfig): ModuleWithProviders<NgrxAutoEntityServiceModule>;
12+
/** @deprecated use `inject` to provide dependencies */
13+
static forRoot(config: (...deps: any[]) => AutoEntityServiceConfig, deps: any[]): ModuleWithProviders<NgrxAutoEntityServiceModule>;
14+
static forRoot(config: AutoEntityServiceConfig | (() => AutoEntityServiceConfig)): ModuleWithProviders<NgrxAutoEntityServiceModule> {
2615
return {
2716
ngModule: NgrxAutoEntityServiceModule,
28-
providers: [createConfigProvider(config, deps)]
17+
providers: [..._provideAutoEntityService(config)]
2918
};
3019
}
3120
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { APP_INITIALIZER, EnvironmentProviders, inject, makeEnvironmentProviders, Provider } from '@angular/core';
2+
import { HttpClient } from '@angular/common/http';
3+
import { AUTO_ENTITY_CONFIG, AutoEntityServiceConfig } from './config';
4+
import { EntityService } from './entity.service';
5+
import { noop } from 'rxjs';
6+
7+
declare const ngDevMode: unknown;
8+
9+
/** @internal */
10+
export function _provideAutoEntityService(config: AutoEntityServiceConfig | (() => AutoEntityServiceConfig), deps?: any[]): Provider[] {
11+
return [
12+
EntityService,
13+
typeof config === 'function'
14+
? { provide: AUTO_ENTITY_CONFIG, useFactory: config, deps }
15+
: { provide: AUTO_ENTITY_CONFIG, useValue: config }
16+
];
17+
}
18+
19+
/** @internal */
20+
export function _assertHttpClientProvided(): () => void {
21+
const http = inject(HttpClient, { optional: true });
22+
if (http == null) {
23+
console.error("[NGRX-AES] ! No provider for HttpClient. Make sure `provideHttpClient()` is included in you application's providers.");
24+
}
25+
return noop;
26+
}
27+
28+
/**
29+
* Sets up providers for the auto-entity entity service.
30+
*
31+
* @usageNotes
32+
*
33+
* ### Providing Auto-Entity Service
34+
*
35+
* Basic example of using the Auto-Entity Entity Service with your entities:
36+
* ```
37+
* bootstrapApplication(AppComponent, {
38+
* providers: [
39+
* // …
40+
* provideAutoEntityService({
41+
* urlPrefix: 'https://example.com/api'
42+
* }),
43+
* ]
44+
* });
45+
* ```
46+
*
47+
* ### Dynamic configuration
48+
*
49+
* You can also provide the Auto-Entity Entity Service configuration dynamically:
50+
* ```
51+
* bootstrapApplication(AppComponent, {
52+
* providers: [
53+
* // …
54+
* provideAutoEntityService(() => {
55+
* const configService = inject(ConfigService);
56+
* return {
57+
* urlPrefix: configService.apiBaseUrl
58+
* }
59+
* })
60+
* ]
61+
* });
62+
* ```
63+
*
64+
* @publicApi
65+
* @param config An Auto-Entity Entity Service configuration object or a function that returns an Auto-Entity Entity Service configuration object.
66+
* @returns A set of providers to set up an Auto-Entity Service.
67+
*/
68+
export function provideAutoEntityService(config: AutoEntityServiceConfig | (() => AutoEntityServiceConfig)): EnvironmentProviders {
69+
const providers: Provider[] = [];
70+
71+
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
72+
providers.push({
73+
provide: APP_INITIALIZER,
74+
useFactory: _assertHttpClientProvided,
75+
multi: true
76+
});
77+
}
78+
79+
return makeEnvironmentProviders([...providers, ..._provideAutoEntityService(config)]);
80+
}

projects/ngrx-auto-entity-service/src/public_api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
export { NgrxAutoEntityServiceModule } from './lib/ngrx-auto-entity-service.module';
6+
export { provideAutoEntityService } from './lib/ngrx-auto-entity-service.provider';
67
export { EntityService } from './lib/entity.service';
7-
export type { AutoEntityServiceConfig, DynamicAutoEntityServiceConfig, APIPrefixResolver } from './lib/config';
8+
export type { AutoEntityServiceConfig, APIPrefixResolver } from './lib/config';
89
export type { EntityCriteria, RetryCriteria, QueryCriteria } from './lib/critera.model';
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import { TestBed } from '@angular/core/testing';
2+
import { META_REDUCERS, MetaReducer, Store, StoreModule } from '@ngrx/store';
3+
import { EffectsModule, EffectSources } from '@ngrx/effects';
4+
5+
import { EntityEffects } from './effects/effects-all';
6+
import { ExtraEffects } from './effects/effects-extra';
7+
import { EntityIfNecessaryOperators } from './effects/if-necessary-operators';
8+
import { EntityOperators } from './effects/operators';
9+
import { autoEntityMetaReducer } from './reducer/meta-reducer';
10+
import {
11+
NgRxAutoEntityFeatureModule,
12+
NgrxAutoEntityModule,
13+
NgRxAutoEntityRootModuleNoEffects,
14+
NgRxAutoEntityRootModuleNoEntityEffects,
15+
NgRxAutoEntityRootModuleWithEffects
16+
} from './module';
17+
import { NEVER } from 'rxjs';
18+
import { NgrxAutoEntityService } from './service/service';
19+
import { NGRX_AUTO_ENTITY_APP_STORE } from './effects/if-necessary-operator-utils';
20+
21+
let effectSources: EffectSources;
22+
let entityService: NgrxAutoEntityService | null;
23+
let entityOps: EntityOperators | null;
24+
let entityIfNecessaryOps: EntityIfNecessaryOperators | null;
25+
let entityEffects: EntityEffects | null;
26+
let extraEffects: ExtraEffects | null;
27+
let metaReducers: MetaReducer[] | null;
28+
let appStore: Store | null;
29+
30+
beforeEach(() => {
31+
effectSources = null;
32+
entityService = null;
33+
entityOps = null;
34+
entityIfNecessaryOps = null;
35+
entityEffects = null;
36+
extraEffects = null;
37+
metaReducers = null;
38+
appStore = null;
39+
});
40+
41+
function resolveDependencies() {
42+
effectSources = TestBed.inject(EffectSources);
43+
entityService = TestBed.inject(NgrxAutoEntityService, null, { optional: true });
44+
entityOps = TestBed.inject(EntityOperators, null, { optional: true });
45+
entityIfNecessaryOps = TestBed.inject(EntityIfNecessaryOperators, null, { optional: true });
46+
entityEffects = TestBed.inject(EntityEffects, null, { optional: true });
47+
extraEffects = TestBed.inject(ExtraEffects, null, { optional: true });
48+
metaReducers = TestBed.inject(META_REDUCERS, null, { optional: true });
49+
appStore = TestBed.inject(NGRX_AUTO_ENTITY_APP_STORE, null, { optional: true }) as Store;
50+
}
51+
52+
const mockEffectSourcesProvider = {
53+
useValue: {
54+
addEffects: jest.fn(),
55+
toActions: jest.fn().mockReturnValue(NEVER)
56+
}
57+
};
58+
59+
describe('Module: NgRxAutoEntityRootModuleWithEffects', () => {
60+
it('should provide the correct dependencies', () => {
61+
TestBed.configureTestingModule({
62+
imports: [StoreModule.forRoot({}), EffectsModule.forRoot([]), NgrxAutoEntityModule.forRoot()]
63+
}).overrideProvider(EffectSources, mockEffectSourcesProvider);
64+
65+
resolveDependencies();
66+
67+
expect(entityService).toBeDefined();
68+
expect(entityOps).toBeDefined();
69+
expect(entityIfNecessaryOps).toBeDefined();
70+
expect(entityEffects).toBeDefined();
71+
expect(extraEffects).toBeDefined();
72+
expect(appStore).toBeNull();
73+
74+
expect(metaReducers).toContain(autoEntityMetaReducer);
75+
76+
expect(effectSources.addEffects).toHaveBeenCalledWith(entityEffects);
77+
expect(effectSources.addEffects).toHaveBeenCalledWith(extraEffects);
78+
});
79+
});
80+
81+
describe('Module: NgRxAutoEntityRootModuleNoEntityEffects', () => {
82+
it('should provide the correct dependencies', () => {
83+
TestBed.configureTestingModule({
84+
imports: [StoreModule.forRoot({}), EffectsModule.forRoot([]), NgrxAutoEntityModule.forRootNoEntityEffects()]
85+
}).overrideProvider(EffectSources, mockEffectSourcesProvider);
86+
87+
resolveDependencies();
88+
89+
expect(entityService).toBeDefined();
90+
expect(entityOps).toBeDefined();
91+
expect(entityIfNecessaryOps).toBeDefined();
92+
expect(entityEffects).toBeNull();
93+
expect(extraEffects).toBeDefined();
94+
expect(appStore).toBeNull();
95+
96+
expect(metaReducers).toContain(autoEntityMetaReducer);
97+
98+
expect(effectSources.addEffects).not.toHaveBeenCalledWith(entityEffects);
99+
expect(effectSources.addEffects).toHaveBeenCalledWith(extraEffects);
100+
});
101+
});
102+
103+
describe('Module: NgRxAutoEntityRootModuleNoEffects', () => {
104+
it('should provide the correct dependencies', () => {
105+
TestBed.configureTestingModule({
106+
imports: [StoreModule.forRoot({}), EffectsModule.forRoot([]), NgrxAutoEntityModule.forRootNoEffects()]
107+
}).overrideProvider(EffectSources, mockEffectSourcesProvider);
108+
109+
const effectSources = TestBed.inject(EffectSources);
110+
111+
resolveDependencies();
112+
113+
expect(entityService).toBeDefined();
114+
expect(entityOps).toBeDefined();
115+
expect(entityIfNecessaryOps).toBeDefined();
116+
expect(entityEffects).toBeNull();
117+
expect(extraEffects).toBeNull();
118+
expect(appStore).toBeNull();
119+
120+
expect(metaReducers).toContain(autoEntityMetaReducer);
121+
122+
expect(effectSources.addEffects).not.toHaveBeenCalledWith(entityEffects);
123+
expect(effectSources.addEffects).not.toHaveBeenCalledWith(extraEffects);
124+
});
125+
});
126+
127+
describe('Module: NgRxAutoEntityFeatureModule', () => {
128+
it('should provide the correct dependencies', () => {
129+
TestBed.configureTestingModule({
130+
imports: [NgrxAutoEntityModule.forFeature()]
131+
});
132+
});
133+
});
134+
135+
describe('Module: NgrxAutoEntityModule', () => {
136+
describe('Method: forRoot', () => {
137+
it('should return the NgRxAutoEntityRootModuleWithEffects module with providers', () => {
138+
const module = NgrxAutoEntityModule.forRoot();
139+
expect(module).toEqual({
140+
ngModule: NgRxAutoEntityRootModuleWithEffects,
141+
providers: expect.any(Array)
142+
});
143+
});
144+
});
145+
146+
describe('Method: forRootNoEntityEffects', () => {
147+
it('should return the NgRxAutoEntityRootModuleNoEntityEffects module with providers', () => {
148+
const module = NgrxAutoEntityModule.forRootNoEntityEffects();
149+
expect(module).toEqual({
150+
ngModule: NgRxAutoEntityRootModuleNoEntityEffects,
151+
providers: expect.any(Array)
152+
});
153+
});
154+
});
155+
156+
describe('Method: forRootNoEffects', () => {
157+
it('should return the NgRxAutoEntityRootModuleNoEffects module with providers', () => {
158+
const module = NgrxAutoEntityModule.forRootNoEffects();
159+
expect(module).toEqual({
160+
ngModule: NgRxAutoEntityRootModuleNoEffects,
161+
providers: expect.any(Array)
162+
});
163+
});
164+
});
165+
166+
describe('Method: forFeature', () => {
167+
it('should return the NgRxAutoEntityFeatureModule module with providers', () => {
168+
const module = NgrxAutoEntityModule.forFeature();
169+
expect(module).toEqual({
170+
ngModule: NgRxAutoEntityFeatureModule,
171+
providers: expect.any(Array)
172+
});
173+
});
174+
});
175+
});

0 commit comments

Comments
 (0)