Skip to content

Commit

Permalink
feat(data): add initial standalone APIs (#3647)
Browse files Browse the repository at this point in the history
Closes #3553
  • Loading branch information
santoshyadavdev authored Nov 7, 2022
1 parent 72b1876 commit aa7ed66
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 255 deletions.
18 changes: 18 additions & 0 deletions modules/data/src/entity-data-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { InjectionToken } from '@angular/core';
import { MetaReducer } from '@ngrx/store';
import { EntityCache } from './reducers/entity-cache';
import { EntityAction } from './actions/entity-action';
import { EntityMetadataMap } from './entity-metadata/entity-metadata';
import { EntityCollection } from './reducers/entity-collection';

export interface EntityDataModuleConfig {
entityMetadata?: EntityMetadataMap;
entityCacheMetaReducers?: (
| MetaReducer<EntityCache>
| InjectionToken<MetaReducer<EntityCache>>
)[];
entityCollectionMetaReducers?: MetaReducer<EntityCollection, EntityAction>[];
// Initial EntityCache state or a function that returns that state
initialEntityCacheState?: EntityCache | (() => EntityCache);
pluralNames?: { [name: string]: string };
}
164 changes: 11 additions & 153 deletions modules/data/src/entity-data-without-effects.module.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,10 @@
import { ModuleWithProviders, NgModule } from '@angular/core';
import { EntityDataModuleConfig } from './entity-data-config';
import {
ModuleWithProviders,
NgModule,
Inject,
Injector,
InjectionToken,
Optional,
OnDestroy,
} from '@angular/core';

import {
Action,
combineReducers,
MetaReducer,
ReducerManager,
StoreModule,
} from '@ngrx/store';

import { CorrelationIdGenerator } from './utils/correlation-id-generator';
import { EntityDispatcherDefaultOptions } from './dispatchers/entity-dispatcher-default-options';
import { EntityAction } from './actions/entity-action';
import { EntityActionFactory } from './actions/entity-action-factory';
import { EntityCache } from './reducers/entity-cache';
import { EntityCacheDispatcher } from './dispatchers/entity-cache-dispatcher';
import { entityCacheSelectorProvider } from './selectors/entity-cache-selector';
import { EntityCollectionServiceElementsFactory } from './entity-services/entity-collection-service-elements-factory';
import { EntityCollectionServiceFactory } from './entity-services/entity-collection-service-factory';
import { EntityServices } from './entity-services/entity-services';
import { EntityCollection } from './reducers/entity-collection';
import { EntityCollectionCreator } from './reducers/entity-collection-creator';
import { EntityCollectionReducerFactory } from './reducers/entity-collection-reducer';
import { EntityCollectionReducerMethodsFactory } from './reducers/entity-collection-reducer-methods';
import { EntityCollectionReducerRegistry } from './reducers/entity-collection-reducer-registry';
import { EntityDispatcherFactory } from './dispatchers/entity-dispatcher-factory';
import { EntityDefinitionService } from './entity-metadata/entity-definition.service';
import { EntityMetadataMap } from './entity-metadata/entity-metadata';
import { EntityCacheReducerFactory } from './reducers/entity-cache-reducer';
import {
ENTITY_CACHE_NAME,
ENTITY_CACHE_NAME_TOKEN,
ENTITY_CACHE_META_REDUCERS,
ENTITY_COLLECTION_META_REDUCERS,
INITIAL_ENTITY_CACHE_STATE,
} from './reducers/constants';

import { DefaultLogger } from './utils/default-logger';
import { EntitySelectorsFactory } from './selectors/entity-selectors';
import { EntitySelectors$Factory } from './selectors/entity-selectors$';
import { EntityServicesBase } from './entity-services/entity-services-base';
import { EntityServicesElements } from './entity-services/entity-services-elements';
import { Logger, PLURAL_NAMES_TOKEN } from './utils/interfaces';

export interface EntityDataModuleConfig {
entityMetadata?: EntityMetadataMap;
entityCacheMetaReducers?: (
| MetaReducer<EntityCache, Action>
| InjectionToken<MetaReducer<EntityCache, Action>>
)[];
entityCollectionMetaReducers?: MetaReducer<EntityCollection, EntityAction>[];
// Initial EntityCache state or a function that returns that state
initialEntityCacheState?: EntityCache | (() => EntityCache);
pluralNames?: { [name: string]: string };
}
provideRootEntityDataWithoutEffects,
ENTITY_DATA_WITHOUT_EFFECTS_PROVIDERS,
initializeEntityDataWithoutEffects,
} from './provide-entity-data';

/**
* Module without effects or dataservices which means no HTTP calls
Expand All @@ -69,105 +13,19 @@ export interface EntityDataModuleConfig {
* therefore opt-out of @ngrx/effects for entities
*/
@NgModule({
imports: [
StoreModule, // rely on Store feature providers rather than Store.forFeature()
],
providers: [
CorrelationIdGenerator,
EntityDispatcherDefaultOptions,
EntityActionFactory,
EntityCacheDispatcher,
EntityCacheReducerFactory,
entityCacheSelectorProvider,
EntityCollectionCreator,
EntityCollectionReducerFactory,
EntityCollectionReducerMethodsFactory,
EntityCollectionReducerRegistry,
EntityCollectionServiceElementsFactory,
EntityCollectionServiceFactory,
EntityDefinitionService,
EntityDispatcherFactory,
EntitySelectorsFactory,
EntitySelectors$Factory,
EntityServicesElements,
{ provide: ENTITY_CACHE_NAME_TOKEN, useValue: ENTITY_CACHE_NAME },
{ provide: EntityServices, useClass: EntityServicesBase },
{ provide: Logger, useClass: DefaultLogger },
],
providers: [ENTITY_DATA_WITHOUT_EFFECTS_PROVIDERS],
})
export class EntityDataModuleWithoutEffects implements OnDestroy {
private entityCacheFeature: any;

export class EntityDataModuleWithoutEffects {
static forRoot(
config: EntityDataModuleConfig
): ModuleWithProviders<EntityDataModuleWithoutEffects> {
return {
ngModule: EntityDataModuleWithoutEffects,
providers: [
{
provide: ENTITY_CACHE_META_REDUCERS,
useValue: config.entityCacheMetaReducers
? config.entityCacheMetaReducers
: [],
},
{
provide: ENTITY_COLLECTION_META_REDUCERS,
useValue: config.entityCollectionMetaReducers
? config.entityCollectionMetaReducers
: [],
},
{
provide: PLURAL_NAMES_TOKEN,
multi: true,
useValue: config.pluralNames ? config.pluralNames : {},
},
],
};
}

constructor(
private reducerManager: ReducerManager,
entityCacheReducerFactory: EntityCacheReducerFactory,
private injector: Injector,
// optional params
@Optional()
@Inject(ENTITY_CACHE_NAME_TOKEN)
private entityCacheName: string,
@Optional()
@Inject(INITIAL_ENTITY_CACHE_STATE)
private initialState: any,
@Optional()
@Inject(ENTITY_CACHE_META_REDUCERS)
private metaReducers: (
| MetaReducer<EntityCache, Action>
| InjectionToken<MetaReducer<EntityCache, Action>>
)[]
) {
// Add the @ngrx/data feature to the Store's features
// as Store.forFeature does for StoreFeatureModule
const key = entityCacheName || ENTITY_CACHE_NAME;

initialState =
typeof initialState === 'function' ? initialState() : initialState;

const reducers: MetaReducer<EntityCache, Action>[] = (
metaReducers || []
).map((mr) => {
return mr instanceof InjectionToken ? injector.get(mr) : mr;
});

this.entityCacheFeature = {
key,
reducers: entityCacheReducerFactory.create(),
reducerFactory: combineReducers,
initialState: initialState || {},
metaReducers: reducers,
providers: [provideRootEntityDataWithoutEffects(config)],
};
reducerManager.addFeature(this.entityCacheFeature);
}

// eslint-disable-next-line @angular-eslint/contextual-lifecycle
ngOnDestroy() {
this.reducerManager.removeFeature(this.entityCacheFeature);
constructor() {
initializeEntityDataWithoutEffects();
}
}
111 changes: 13 additions & 98 deletions modules/data/src/entity-data.module.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,22 @@
import { ModuleWithProviders, NgModule } from '@angular/core';

import { EffectsModule, EffectSources } from '@ngrx/effects';

import { DefaultDataServiceFactory } from './dataservices/default-data.service';

import {
DefaultPersistenceResultHandler,
PersistenceResultHandler,
} from './dataservices/persistence-result-handler.service';

import {
DefaultHttpUrlGenerator,
HttpUrlGenerator,
} from './dataservices/http-url-generator';

import { EntityCacheDataService } from './dataservices/entity-cache-data.service';
import { EntityCacheEffects } from './effects/entity-cache-effects';
import { EntityDataService } from './dataservices/entity-data.service';
import { EntityEffects } from './effects/entity-effects';

import { ENTITY_METADATA_TOKEN } from './entity-metadata/entity-metadata';

import {
ENTITY_CACHE_META_REDUCERS,
ENTITY_COLLECTION_META_REDUCERS,
} from './reducers/constants';
import { Pluralizer, PLURAL_NAMES_TOKEN } from './utils/interfaces';
import { DefaultPluralizer } from './utils/default-pluralizer';

import { EntityDataModuleConfig } from './entity-data-config';
import { EntityDataModuleWithoutEffects } from './entity-data-without-effects.module';
import {
EntityDataModuleConfig,
EntityDataModuleWithoutEffects,
} from './entity-data-without-effects.module';
ENTITY_DATA_PROVIDERS,
initializeEntityData,
provideRootEntityData,
provideRootEntityDataWithoutEffects,
} from './provide-entity-data';

/**
* entity-data main module includes effects and HTTP data services
* Configure with `forRoot`.
* No `forFeature` yet.
*/
@NgModule({
imports: [
EntityDataModuleWithoutEffects,
EffectsModule, // do not supply effects because can't replace later
],
providers: [
DefaultDataServiceFactory,
EntityCacheDataService,
EntityDataService,
EntityCacheEffects,
EntityEffects,
{ provide: HttpUrlGenerator, useClass: DefaultHttpUrlGenerator },
{
provide: PersistenceResultHandler,
useClass: DefaultPersistenceResultHandler,
},
{ provide: Pluralizer, useClass: DefaultPluralizer },
],
imports: [EntityDataModuleWithoutEffects],
providers: [ENTITY_DATA_PROVIDERS],
})
export class EntityDataModule {
static forRoot(
Expand All @@ -64,59 +25,13 @@ export class EntityDataModule {
return {
ngModule: EntityDataModule,
providers: [
// TODO: Moved these effects classes up to EntityDataModule itself
// Remove this comment if that was a mistake.
// EntityCacheEffects,
// EntityEffects,
{
provide: ENTITY_METADATA_TOKEN,
multi: true,
useValue: config.entityMetadata ? config.entityMetadata : [],
},
{
provide: ENTITY_CACHE_META_REDUCERS,
useValue: config.entityCacheMetaReducers
? config.entityCacheMetaReducers
: [],
},
{
provide: ENTITY_COLLECTION_META_REDUCERS,
useValue: config.entityCollectionMetaReducers
? config.entityCollectionMetaReducers
: [],
},
{
provide: PLURAL_NAMES_TOKEN,
multi: true,
useValue: config.pluralNames ? config.pluralNames : {},
},
provideRootEntityDataWithoutEffects(config),
provideRootEntityData(config),
],
};
}

constructor(
private effectSources: EffectSources,
entityCacheEffects: EntityCacheEffects,
entityEffects: EntityEffects
) {
// We can't use `forFeature()` because, if we did, the developer could not
// replace the entity-data `EntityEffects` with a custom alternative.
// Replacing that class is an extensibility point we need.
//
// The FEATURE_EFFECTS token is not exposed, so can't use that technique.
// Warning: this alternative approach relies on an undocumented API
// to add effect directly rather than through `forFeature()`.
// The danger is that EffectsModule.forFeature evolves and we no longer perform a crucial step.
this.addEffects(entityCacheEffects);
this.addEffects(entityEffects);
}

/**
* Add another class instance that contains effects.
* @param effectSourceInstance a class instance that implements effects.
* Warning: undocumented @ngrx/effects API
*/
addEffects(effectSourceInstance: any) {
this.effectSources.addEffects(effectSourceInstance);
constructor() {
initializeEntityData();
}
}
14 changes: 10 additions & 4 deletions modules/data/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,15 @@ export {
toUpdateFactory,
} from './utils/utilities';

// // EntityDataConfig
export { EntityDataModuleConfig } from './entity-data-config';

// // EntityDataModule
export {
EntityDataModuleConfig,
EntityDataModuleWithoutEffects,
} from './entity-data-without-effects.module';
export { EntityDataModuleWithoutEffects } from './entity-data-without-effects.module';
export { EntityDataModule } from './entity-data.module';

// // Standalone APIs
export {
provideEntityData,
provideEntityDataWithoutEffects,
} from './provide-entity-data';
Loading

0 comments on commit aa7ed66

Please sign in to comment.