diff --git a/alfa-client/libs/admin-settings/src/lib/admin-settings-resource.service.ts b/alfa-client/libs/admin-settings/src/lib/admin-settings-resource.service.ts index 6d2906574daafaab16ccbbf0ea972277430f9482..16d26ceec252126720a29c4ff57821faac79d6d5 100644 --- a/alfa-client/libs/admin-settings/src/lib/admin-settings-resource.service.ts +++ b/alfa-client/libs/admin-settings/src/lib/admin-settings-resource.service.ts @@ -1,7 +1,7 @@ import { ListResourceServiceConfig, ResourceListService, - ResourceRepository, + StateService, } from '@alfa-client/tech-shared'; import { Resource } from '@ngxp/rest'; import { SettingListLinkRel } from './admin-settings.linkrel'; @@ -17,18 +17,19 @@ export class SettingListResourceService extends ResourceListService< > {} export function createSettingListResourceService( - repository: ResourceRepository, configurationService: ConfigurationService, + stateService: StateService, ) { - return new ResourceListService(buildConfig(configurationService), repository); + return new ResourceListService(buildConfig(configurationService), stateService); } function buildConfig( configurationService: ConfigurationService, ): ListResourceServiceConfig<ConfigurationResource> { return { + stateInfo: { name: 'SettingState', featureName: 'settingList' }, baseResource: configurationService.get(), createLinkRel: SettingListLinkRel.CREATE, - listLinkRel: ConfigurationLinkRel.SETTING, + getLinkRel: ConfigurationLinkRel.SETTING, listResourceListLinkRel: SettingListLinkRel.LIST, saveLinkRel: ConfigurationLinkRel.SETTING, }; diff --git a/alfa-client/libs/admin-settings/src/lib/admin-settings.module.ts b/alfa-client/libs/admin-settings/src/lib/admin-settings.module.ts index 4e807a8a58633b55907f22310ec0be38f9bb3da0..ddb76b52e80053f8f5ebe1bb356741640c498cb6 100644 --- a/alfa-client/libs/admin-settings/src/lib/admin-settings.module.ts +++ b/alfa-client/libs/admin-settings/src/lib/admin-settings.module.ts @@ -97,7 +97,7 @@ import { TextFieldComponent } from './shared/text-field/text-field.component'; { provide: SettingListResourceService, useFactory: createSettingListResourceService, - deps: [ResourceRepository, ConfigurationService], + deps: [ConfigurationService, StateService], }, { provide: PostfachResourceService, diff --git a/alfa-client/libs/admin-settings/src/lib/configuration/configuration-resource.service.ts b/alfa-client/libs/admin-settings/src/lib/configuration/configuration-resource.service.ts index 2c48b189fde24bbec25fff8b832022f4c27bc5e4..51fcf93f8aab03e0865abfcd741bf2d9b3a393ad 100644 --- a/alfa-client/libs/admin-settings/src/lib/configuration/configuration-resource.service.ts +++ b/alfa-client/libs/admin-settings/src/lib/configuration/configuration-resource.service.ts @@ -27,7 +27,7 @@ function buildConfig(apiRootService: ApiRootService): ResourceServiceConfig<ApiR name: CONFIGURATION_FEATURE_KEY, featureName: CONFIGURATION_PATH, }, - resource: apiRootService.getApiRoot(), + baseResource: apiRootService.getApiRoot(), getLinkRel: ApiRootLinkRel.CONFIGURATION, }; } diff --git a/alfa-client/libs/admin-settings/src/lib/configuration/configuration.state.ts b/alfa-client/libs/admin-settings/src/lib/configuration/configuration.state.ts index fa9738fba4e1f93c66e858c0d045dedc2f25430b..c2c220bc4a5d3d6048053593b734f66e1747a0fe 100644 --- a/alfa-client/libs/admin-settings/src/lib/configuration/configuration.state.ts +++ b/alfa-client/libs/admin-settings/src/lib/configuration/configuration.state.ts @@ -1,13 +1,17 @@ -import { ResourceReducer, ResourceState, createResourceActions } from '@alfa-client/tech-shared'; +import { + ListResourceReducer, + ListResourceState, + createListResourceActions, +} from '@alfa-client/tech-shared'; import { Action, ActionReducerMap } from '@ngrx/store'; export const CONFIGURATION_FEATURE_KEY = 'ConfigurationState'; export const CONFIGURATION_PATH = 'configuration'; -export function configurationResourceReducer(state: ResourceState, action: Action) { - const resourceReducer: ResourceReducer = new ResourceReducer( - createResourceActions(CONFIGURATION_FEATURE_KEY), +export function configurationResourceReducer(state: ListResourceState, action: Action) { + const resourceReducer: ListResourceReducer = new ListResourceReducer( + createListResourceActions({ name: CONFIGURATION_FEATURE_KEY, featureName: CONFIGURATION_PATH }), ); return resourceReducer.reducer(state, action); } diff --git a/alfa-client/libs/admin-settings/src/lib/postfach/postfach-resource.service.ts b/alfa-client/libs/admin-settings/src/lib/postfach/postfach-resource.service.ts index ef203bc239deca1b8e4d90be475f28238efbaef5..a645bf82d3055b3fe604c92f57efb3aeeed89999 100644 --- a/alfa-client/libs/admin-settings/src/lib/postfach/postfach-resource.service.ts +++ b/alfa-client/libs/admin-settings/src/lib/postfach/postfach-resource.service.ts @@ -25,7 +25,7 @@ export function createPostfachResourceService( function buildConfig(settingService: SettingsService): ResourceServiceConfig<PostfachResource> { return { stateInfo: { name: POSTFACH_FEATURE_KEY, featureName: POSTFACH_PATH }, - resource: settingService.getPostfach(), + baseResource: settingService.getPostfach(), getLinkRel: PostfachLinkRel.SELF, edit: { linkRel: PostfachLinkRel.SELF }, }; diff --git a/alfa-client/libs/admin-settings/src/lib/postfach/postfach.state.ts b/alfa-client/libs/admin-settings/src/lib/postfach/postfach.state.ts index 0084f3b240eec0afe967896f607b3778b803dcb1..cb024086786e251dbf1b9ed14347619e381a6055 100644 --- a/alfa-client/libs/admin-settings/src/lib/postfach/postfach.state.ts +++ b/alfa-client/libs/admin-settings/src/lib/postfach/postfach.state.ts @@ -1,13 +1,17 @@ -import { ResourceReducer, ResourceState, createResourceActions } from '@alfa-client/tech-shared'; +import { + SingleResourceReducer, + SingleResourceState, + createSingleResourceActions, +} from '@alfa-client/tech-shared'; import { Action, ActionReducerMap } from '@ngrx/store'; export const POSTFACH_FEATURE_KEY = 'PostfachState'; export const POSTFACH_PATH = 'postfach'; -export function postfachResourceReducer(state: ResourceState, action: Action) { - const resourceReducer: ResourceReducer = new ResourceReducer( - createResourceActions(POSTFACH_FEATURE_KEY), +export function postfachResourceReducer(state: SingleResourceState, action: Action) { + const resourceReducer: SingleResourceReducer = new SingleResourceReducer( + createSingleResourceActions({ name: POSTFACH_FEATURE_KEY, featureName: POSTFACH_PATH }), ); return resourceReducer.reducer(state, action); } diff --git a/alfa-client/libs/bescheid-shared/src/lib/+state/bescheid.reducer.ts b/alfa-client/libs/bescheid-shared/src/lib/+state/bescheid.reducer.ts index c944a700d3b6011a5d85b11289e11b5e9509c78c..8d0c95be8fc0643e04432c68ddc92e0a3f913756 100644 --- a/alfa-client/libs/bescheid-shared/src/lib/+state/bescheid.reducer.ts +++ b/alfa-client/libs/bescheid-shared/src/lib/+state/bescheid.reducer.ts @@ -6,11 +6,13 @@ import { } from '@alfa-client/command-shared'; import { ApiError, - ResourceReducer, + ListResourceReducer, + SingleResourceReducer, StateResource, createEmptyStateResource, createErrorStateResource, - createResourceActions, + createListResourceActions, + createSingleResourceActions, createStateResource, } from '@alfa-client/tech-shared'; import { HttpErrorResponse } from '@angular/common/http'; @@ -23,6 +25,7 @@ export const BESCHEID_FEATURE_KEY = 'BescheidState'; export const BESCHEID_PATH = 'bescheid'; export const BESCHEID_DRAFT_PATH = 'bescheidDraft'; +export const BESCHEID_LIST_PATH = 'bescheidList'; export interface BescheidState { bescheidCommand: StateResource<CommandResource>; @@ -37,7 +40,6 @@ export const bescheidReducer: ActionReducer<BescheidState, Action> = createReduc on( CommandActions.createCommand, (state: BescheidState, props: CreateCommandProps): BescheidState => { - console.info('CREATE command: ', state); return isCreateBescheidCommand(props.command.order) ? { ...state, bescheidCommand: { ...state.bescheidCommand, loading: true } } : state; @@ -70,13 +72,22 @@ export function reducer(state: BescheidState, action: Action): BescheidState { return bescheidReducer(state, action); } -export function bescheidResourceReducer(state: BescheidState, action: Action) { - const resourceReducer: ResourceReducer = new ResourceReducer( - createResourceActions(BESCHEID_FEATURE_KEY), - ); - return resourceReducer.reducer(state, action); -} export const reducers: ActionReducerMap<any> = { [BESCHEID_PATH]: bescheidReducer, - [BESCHEID_DRAFT_PATH]: bescheidResourceReducer, + [BESCHEID_DRAFT_PATH]: bescheidDraftResourceReducer, + [BESCHEID_LIST_PATH]: bescheidListResourceReducer, }; + +function bescheidDraftResourceReducer(state: BescheidState, action: Action) { + const resourceReducer: SingleResourceReducer = new SingleResourceReducer( + createSingleResourceActions({ name: BESCHEID_FEATURE_KEY, featureName: BESCHEID_DRAFT_PATH }), + ); + return resourceReducer.reducer(state, action); +} + +function bescheidListResourceReducer(state: BescheidState, action: Action) { + const resourceReducer: ListResourceReducer = new ListResourceReducer( + createListResourceActions({ name: BESCHEID_FEATURE_KEY, featureName: BESCHEID_LIST_PATH }), + ); + return resourceReducer.reducer(state, action); +} diff --git a/alfa-client/libs/bescheid-shared/src/lib/bescheid-shared.module.ts b/alfa-client/libs/bescheid-shared/src/lib/bescheid-shared.module.ts index 4f55bc5fdac79bd196a7fd02a433cf9668fe9c41..fc6c3220944d893644d2ea9a037ecb5c9b2cc88d 100644 --- a/alfa-client/libs/bescheid-shared/src/lib/bescheid-shared.module.ts +++ b/alfa-client/libs/bescheid-shared/src/lib/bescheid-shared.module.ts @@ -5,6 +5,5 @@ import { BESCHEID_FEATURE_KEY, reducers } from './+state/bescheid.reducer'; @NgModule({ imports: [CommonModule, StoreModule.forFeature(BESCHEID_FEATURE_KEY, reducers)], - // imports: [CommonModule, StoreModule.forFeature(BESCHEID_FEATURE_KEY, reducer)], }) export class BescheidSharedModule {} diff --git a/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.ts b/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.ts index 813eb9eb74f9f3dc2256543f1cbd380c59129079..46e1442dc34c7c73d29f653e8400525f7c3c9c82 100644 --- a/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.ts +++ b/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.ts @@ -51,7 +51,11 @@ import { } from '../../../tech-shared/src/lib/resource/resource.model'; import { ResourceRepository } from '../../../tech-shared/src/lib/resource/resource.repository'; import { BescheidFacade } from './+state/bescheid.facade'; -import { BESCHEID_DRAFT_PATH, BESCHEID_FEATURE_KEY } from './+state/bescheid.reducer'; +import { + BESCHEID_DRAFT_PATH, + BESCHEID_FEATURE_KEY, + BESCHEID_LIST_PATH, +} from './+state/bescheid.reducer'; import { BescheidLinkRel, BescheidListLinkRel } from './bescheid.linkrel'; import { Bescheid, @@ -116,7 +120,7 @@ export class BescheidService { ); this.bescheidListService = new ResourceListService( this.buildBescheidListServiceConfig(), - repository, + this.stateService, ); } @@ -126,7 +130,7 @@ export class BescheidService { name: BESCHEID_FEATURE_KEY, featureName: BESCHEID_DRAFT_PATH, }, - resource: this.vorgangService.getVorgangWithEingang(), + baseResource: this.vorgangService.getVorgangWithEingang(), getLinkRel: VorgangWithEingangLinkRel.BESCHEID_DRAFT, delete: { linkRel: BescheidLinkRel.DELETE, order: CommandOrder.DELETE_BESCHEID }, edit: { linkRel: BescheidLinkRel.UPDATE, order: CommandOrder.UPDATE_BESCHEID }, @@ -135,8 +139,12 @@ export class BescheidService { buildBescheidListServiceConfig(): ListResourceServiceConfig<VorgangWithEingangResource> { return { + stateInfo: { + name: BESCHEID_FEATURE_KEY, + featureName: BESCHEID_LIST_PATH, + }, baseResource: this.vorgangService.getVorgangWithEingang(), - listLinkRel: VorgangWithEingangLinkRel.BESCHEIDE, + getLinkRel: VorgangWithEingangLinkRel.BESCHEIDE, listResourceListLinkRel: BescheidListLinkRel.BESCHEID_LIST, }; } diff --git a/alfa-client/libs/tech-shared/src/lib/ngrx/actions.ts b/alfa-client/libs/tech-shared/src/lib/ngrx/actions.ts index 88af729d21fdee66f88b0c716225aadb4bcc4bdf..2976dafda8b4e771e373859918c69b36077b5c0e 100644 --- a/alfa-client/libs/tech-shared/src/lib/ngrx/actions.ts +++ b/alfa-client/libs/tech-shared/src/lib/ngrx/actions.ts @@ -24,6 +24,8 @@ import { Action, ActionCreator, createAction, props } from '@ngrx/store'; import { TypedAction } from '@ngrx/store/src/models'; import { Resource, ResourceUri } from '@ngxp/rest'; +import { StateInfo } from '../resource/resource.model'; +import { ListResource } from '../resource/resource.util'; import { ApiError, HttpError } from '../tech.model'; export const EMPTY_ACTION: Action = {} as Action; @@ -45,43 +47,72 @@ export interface LoadResourceSuccessProps { resource: Resource; } +export interface LoadListResourceSuccessProps { + listResource: ListResource; +} + export interface LoadResourceFailureProps { error: HttpError; } export interface LoadResourceSuccessProps {} -export interface ResourceLoadActions { +export interface ResourceActions { loadAction: TypedActionCreatorWithProps<ResourceUriProps>; - loadSuccessAction: TypedActionCreatorWithProps<LoadResourceSuccessProps>; loadFailureAction: TypedActionCreatorWithProps<LoadResourceFailureProps>; -} - -export interface ResourceActions extends ResourceLoadActions { clearAction: TypedActionCreator; reloadAction: TypedActionCreator; } -export function createResourceActions(featureName: string): ResourceActions { - const loadAction: TypedActionCreatorWithProps<ResourceUriProps> = createAction( - createActionType(featureName, 'Load Resource'), - props<ResourceUriProps>(), - ); +export interface SingleResourceLoadActions extends ResourceActions { + loadSuccessAction: TypedActionCreatorWithProps<LoadResourceSuccessProps>; +} + +export interface ListResourceLoadActions extends ResourceActions { + loadListSuccessAction: TypedActionCreatorWithProps<LoadListResourceSuccessProps>; +} + +export function createSingleResourceActions(stateInfo: StateInfo): SingleResourceLoadActions { + const stateName: string = stateInfo.name; + const subPath: string = stateInfo.featureName; + + const actions: ResourceActions = createResourceActions(stateName, `${subPath} Resource`); const loadSuccessAction: TypedActionCreatorWithProps<LoadResourceSuccessProps> = createAction( - createActionType(featureName, 'Load Resource Success'), + createActionType(stateName, `Load ${subPath} Resource Success`), props<LoadResourceSuccessProps>(), ); + return { ...actions, loadSuccessAction }; +} + +export function createListResourceActions(stateInfo: StateInfo): ListResourceLoadActions { + const stateName: string = stateInfo.name; + const subPath: string = stateInfo.featureName; + + const actions: ResourceActions = createResourceActions(stateName, `${subPath} ListResource`); + const loadListSuccessAction: TypedActionCreatorWithProps<LoadListResourceSuccessProps> = + createAction( + createActionType(stateName, `Load ${subPath} ListResource Success`), + props<LoadListResourceSuccessProps>(), + ); + return { ...actions, loadListSuccessAction }; +} + +function createResourceActions(stateName: string, message: string): ResourceActions { + const loadAction: TypedActionCreatorWithProps<ResourceUriProps> = createAction( + createActionType(stateName, `Load ${message}`), + props<ResourceUriProps>(), + ); const loadFailureAction: TypedActionCreatorWithProps<LoadResourceFailureProps> = createAction( - createActionType(featureName, 'Load Resource Failure'), + createActionType(stateName, `Load ${message} Failure`), props<LoadResourceFailureProps>(), ); const clearAction: TypedActionCreator = createAction( - createActionType(featureName, 'Clear Resource'), + createActionType(stateName, `Clear ${message}`), ); const reloadAction: TypedActionCreator = createAction( - createActionType(featureName, 'Reload Resource'), + createActionType(stateName, `Reload ${message}`), ); - return { loadAction, loadSuccessAction, loadFailureAction, clearAction, reloadAction }; + return { loadAction, loadFailureAction, clearAction, reloadAction }; } function createActionType(stateName: string, message: string): string { diff --git a/alfa-client/libs/tech-shared/src/lib/ngrx/del.reducer.service.ts b/alfa-client/libs/tech-shared/src/lib/ngrx/del.reducer.service.ts index 6163f7e96c6e44c277efd13a97880890d40c90e8..22fc605c62099e8930b8c10a799f6d89b3289641 100644 --- a/alfa-client/libs/tech-shared/src/lib/ngrx/del.reducer.service.ts +++ b/alfa-client/libs/tech-shared/src/lib/ngrx/del.reducer.service.ts @@ -1,9 +1,9 @@ import { Injectable } from '@angular/core'; import { Action, ActionReducer, ActionReducerMap, ReducerManager } from '@ngrx/store'; import { StateInfo } from '../resource/resource.model'; -import { ResourceActions } from './actions'; -import { ResourceReducer } from './del.resource.reducer'; -import { ResourceState } from './resource.state'; +import { SingleResourceLoadActions } from './actions'; +import { SingleResourceReducer } from './del.resource.reducer'; +import { SingleResourceStateService } from './resource.state.service'; @Injectable({ providedIn: 'root', @@ -11,8 +11,8 @@ import { ResourceState } from './resource.state'; export class ReducerService { constructor(private reducerManager: ReducerManager) {} - public addFeatureReducer<T>(stateInfo: StateInfo, resourceActions: ResourceActions) { - const resourceReducer = new ResourceReducer(resourceActions); + public addFeatureReducer<T>(stateInfo: StateInfo, resourceActions: SingleResourceLoadActions) { + const resourceReducer = new SingleResourceReducer(resourceActions); //Funktioniert aber überschreibt die bestehenden reducer :( // this.reducerManager.addReducer(stateInfo.name, resourceReducer.reducer); @@ -36,17 +36,20 @@ export class ReducerService { const existingReducer = allReducer[featureStateName]; - this.reducerManager.addReducer(featureStateName, (state: ResourceState, action: Action) => { - console.info('State in addReducer: ', state); - const newState = { - ...state, - ['bescheidCommand']: { ...state['bescheidCommand'] }, - ['bescheidDraft']: reducer(state[attributeName], action), - }; + this.reducerManager.addReducer( + featureStateName, + (state: SingleResourceStateService<any>, action: Action) => { + console.info('State in addReducer: ', state); + const newState = { + ...state, + ['bescheidCommand']: { ...state['bescheidCommand'] }, + ['bescheidDraft']: reducer(state[attributeName], action), + }; - // const newState = combineReducers(existingReducer, reducer); - return newState; - }); + // const newState = combineReducers(existingReducer, reducer); + return newState; + }, + ); } // addSubReducerTest2(parentKey, subKey: string, reducer: any) { diff --git a/alfa-client/libs/tech-shared/src/lib/ngrx/del.resource.reducer.ts b/alfa-client/libs/tech-shared/src/lib/ngrx/del.resource.reducer.ts index 107f31b41148c569bf80cda9a5c65e390714d4d4..05a1f89bd3b429bb50a83f3b0d1748209e0fff08 100644 --- a/alfa-client/libs/tech-shared/src/lib/ngrx/del.resource.reducer.ts +++ b/alfa-client/libs/tech-shared/src/lib/ngrx/del.resource.reducer.ts @@ -4,20 +4,31 @@ import { createErrorStateResource, createStateResource, } from '../resource/resource.util'; -import { LoadResourceFailureProps, LoadResourceSuccessProps, ResourceActions } from './actions'; -import { ResourceState, initialResourceState } from './resource.state'; +import { + ListResourceLoadActions, + LoadListResourceSuccessProps, + LoadResourceFailureProps, + LoadResourceSuccessProps, + SingleResourceLoadActions, +} from './actions'; +import { + ListResourceState, + SingleResourceState, + initialListResourceState, + initialResourceState, +} from './resource.state'; -export class ResourceReducer { +export class SingleResourceReducer { public reducer: ActionReducer<any, Action>; - constructor(private actions: ResourceActions) { + constructor(private actions: SingleResourceLoadActions) { this.init(); } private init(): void { this.reducer = createReducer( initialResourceState, - on(this.actions.loadAction, (state: ResourceState): ResourceState => { + on(this.actions.loadAction, (state: SingleResourceState): SingleResourceState => { console.info('State after LOAD', { ...state, resource: { ...state.resource, loading: true, reload: false }, @@ -29,7 +40,7 @@ export class ResourceReducer { }), on( this.actions.loadSuccessAction, - (state: ResourceState, props: LoadResourceSuccessProps): ResourceState => { + (state: SingleResourceState, props: LoadResourceSuccessProps): SingleResourceState => { console.info('State after SUCCESS', { ...state, resource: createStateResource(props.resource), @@ -42,21 +53,21 @@ export class ResourceReducer { ), on( this.actions.loadFailureAction, - (state: ResourceState, props: LoadResourceFailureProps): ResourceState => ({ + (state: SingleResourceState, props: LoadResourceFailureProps): SingleResourceState => ({ ...state, resource: createErrorStateResource(props.error), }), ), on( this.actions.clearAction, - (state: ResourceState): ResourceState => ({ + (state: SingleResourceState): SingleResourceState => ({ ...state, resource: createEmptyStateResource(), }), ), on( this.actions.reloadAction, - (state: ResourceState): ResourceState => ({ + (state: SingleResourceState): SingleResourceState => ({ ...state, resource: { ...state.resource, reload: true }, }), @@ -64,3 +75,61 @@ export class ResourceReducer { ); } } + +export class ListResourceReducer { + public reducer: ActionReducer<any, Action>; + + constructor(private actions: ListResourceLoadActions) { + this.init(); + } + + private init(): void { + this.reducer = createReducer( + initialListResourceState, + on(this.actions.loadAction, (state: ListResourceState): ListResourceState => { + console.info('State after LOAD', { + ...state, + resource: { ...state.listResource, loading: true, reload: false }, + }); + return { + ...state, + listResource: { ...state.listResource, loading: true }, + }; + }), + on( + this.actions.loadListSuccessAction, + (state: ListResourceState, props: LoadListResourceSuccessProps): ListResourceState => { + console.info('State after SUCCESS', { + ...state, + listResource: createStateResource(props.listResource), + }); + return { + ...state, + listResource: createStateResource(props.listResource), + }; + }, + ), + on( + this.actions.loadFailureAction, + (state: ListResourceState, props: LoadResourceFailureProps): ListResourceState => ({ + ...state, + listResource: createErrorStateResource(props.error), + }), + ), + on( + this.actions.clearAction, + (state: ListResourceState): ListResourceState => ({ + ...state, + listResource: createEmptyStateResource(), + }), + ), + on( + this.actions.reloadAction, + (state: ListResourceState): ListResourceState => ({ + ...state, + listResource: { ...state.listResource, reload: true }, + }), + ), + ); + } +} diff --git a/alfa-client/libs/tech-shared/src/lib/ngrx/effects.service.ts b/alfa-client/libs/tech-shared/src/lib/ngrx/effects.service.ts index 91e9112aa8215fe731064df4e9455430c95957d9..167d2aa039991eb595329f4d0f5d20562569d349 100644 --- a/alfa-client/libs/tech-shared/src/lib/ngrx/effects.service.ts +++ b/alfa-client/libs/tech-shared/src/lib/ngrx/effects.service.ts @@ -1,12 +1,10 @@ import { Injectable } from '@angular/core'; import { Actions, EffectSources } from '@ngrx/effects'; import { ResourceRepository } from '../resource/resource.repository'; -import { ResourceActions } from './actions'; -import { ResourceEffects } from './resource.effects'; +import { ListResourceLoadActions, SingleResourceLoadActions } from './actions'; +import { ListResourceEffects, ResourceEffects } from './resource.effects'; -@Injectable({ - providedIn: 'root', -}) +@Injectable() export class EffectService { constructor( private actions$: Actions, @@ -14,8 +12,15 @@ export class EffectService { private repository: ResourceRepository, ) {} - public addEffects(resourceActions: ResourceActions): void { - const effect = new ResourceEffects(this.actions$, this.repository, resourceActions); - this.effectSources.addEffects({ ...effect }); + public addSingleResourceEffects(resourceActions: SingleResourceLoadActions): void { + this.effectSources.addEffects({ + ...new ResourceEffects(this.actions$, this.repository, resourceActions), + }); + } + + public addListResourceEffects(resourceActions: ListResourceLoadActions): void { + this.effectSources.addEffects({ + ...new ListResourceEffects(this.actions$, this.repository, resourceActions), + }); } } diff --git a/alfa-client/libs/tech-shared/src/lib/ngrx/resource.effects.ts b/alfa-client/libs/tech-shared/src/lib/ngrx/resource.effects.ts index 1975516fda46fb3b58ff96e35eaa9822bb62deba..e57f71b366cbfaf8f5a6b02e04a48a9335e3b156 100644 --- a/alfa-client/libs/tech-shared/src/lib/ngrx/resource.effects.ts +++ b/alfa-client/libs/tech-shared/src/lib/ngrx/resource.effects.ts @@ -26,13 +26,14 @@ import { Resource } from '@ngxp/rest'; import { of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; import { ResourceRepository } from '../resource/resource.repository'; -import { ResourceActions, ResourceUriProps } from './actions'; +import { ListResource } from '../resource/resource.util'; +import { ListResourceLoadActions, ResourceUriProps, SingleResourceLoadActions } from './actions'; export class ResourceEffects<T extends Resource> { constructor( private actions$: Actions, private repository: ResourceRepository, - private resourceActions: ResourceActions, + private resourceActions: SingleResourceLoadActions, ) {} loadByUri$ = createEffect(() => @@ -47,3 +48,23 @@ export class ResourceEffects<T extends Resource> { ), ); } + +export class ListResourceEffects<T extends ListResource> { + constructor( + private actions$: Actions, + private repository: ResourceRepository, + private resourceActions: ListResourceLoadActions, + ) {} + + loadByUri$ = createEffect(() => + this.actions$.pipe( + ofType(this.resourceActions.loadAction), + switchMap((props: ResourceUriProps) => { + return this.repository.getResource<T>(props.resourceUri).pipe( + map((listResource: T) => this.resourceActions.loadListSuccessAction({ listResource })), + catchError((error) => of(this.resourceActions.loadFailureAction({ error }))), + ); + }), + ), + ); +} diff --git a/alfa-client/libs/tech-shared/src/lib/ngrx/resource.loader.ts b/alfa-client/libs/tech-shared/src/lib/ngrx/resource.loader.ts new file mode 100644 index 0000000000000000000000000000000000000000..1805d65a48f8a621ce519c44f5c6f0e4a427fec6 --- /dev/null +++ b/alfa-client/libs/tech-shared/src/lib/ngrx/resource.loader.ts @@ -0,0 +1,95 @@ +import { Resource, getUrl, hasLink } from '@ngxp/rest'; +import { isEqual, isNull } from 'lodash-es'; +import { Observable, combineLatest, filter, startWith, tap } from 'rxjs'; +import { ServiceConfig } from '../resource/resource.model'; +import { mapToFirst, mapToResource } from '../resource/resource.rxjs.operator'; +import { + StateResource, + createEmptyStateResource, + isInvalidResourceCombination, + isLoadingRequired, + isStateResoureStable, +} from '../resource/resource.util'; +import { isNotNull } from '../tech.util'; +import { GenerellResourceStateService } from './resource.state.service'; + +export class ResourceLoader<B extends Resource, T extends Resource> { + configResource: B = null; + + constructor( + private config: ServiceConfig<B>, + private resourceStateService: GenerellResourceStateService<T>, + ) {} + + public get(): Observable<StateResource<T>> { + return combineLatest([ + this.resourceStateService.selectResource(), + this.getConfigResource(), + ]).pipe( + tap(([stateResource, configResource]) => + this.handleConfigResourceChanged(<StateResource<T>>stateResource, configResource), + ), + filter( + ([stateResource, configResource]) => + !isInvalidResourceCombination(<StateResource<T>>stateResource, configResource), + ), + mapToFirst<T, B>(), + startWith(createEmptyStateResource<T>(true)), + ); + } + + private getConfigResource(): Observable<B> { + return this.config.baseResource.pipe(filter(isStateResoureStable), mapToResource<B>()); + } + + handleConfigResourceChanged(stateResource: StateResource<T>, configResource: B): void { + if (!isEqual(this.configResource, configResource)) { + this.configResource = configResource; + this.updateStateResourceByConfigResource(stateResource, configResource); + } else if (this.shouldLoadResource(stateResource, configResource)) { + this.loadResource(configResource); + } + } + + updateStateResourceByConfigResource(stateResource: StateResource<T>, configResource: B): void { + if (!isStateResoureStable(stateResource)) { + return; + } + if (this.shouldClearStateResource(stateResource, configResource)) { + this.resourceStateService.clearResource(); + } else if (isNotNull(configResource) && hasLink(configResource, this.config.getLinkRel)) { + this.loadResource(configResource); + } + } + + shouldClearStateResource(stateResource: StateResource<T>, configResource: B): boolean { + return ( + (isNull(configResource) || this.hasNotGetLink(configResource)) && + !this.isStateResourceEmpty(stateResource) + ); + } + + private hasNotGetLink(configResource: B) { + return isNotNull(configResource) && !hasLink(configResource, this.config.getLinkRel); + } + + private isStateResourceEmpty(stateResource: StateResource<T>): boolean { + return isEqual(stateResource, createEmptyStateResource()); + } + + shouldLoadResource(stateResource: StateResource<T>, configResource: B): boolean { + return ( + isNotNull(configResource) && + hasLink(configResource, this.config.getLinkRel) && + isLoadingRequired(stateResource) + ); + } + + loadResource(configResource: B): void { + this.resourceStateService.loadResource(this.getGetUrl(configResource)); + } + + private getGetUrl(configResource: B): string { + return getUrl(configResource, this.config.getLinkRel); + } +} diff --git a/alfa-client/libs/tech-shared/src/lib/ngrx/resource.selector.ts b/alfa-client/libs/tech-shared/src/lib/ngrx/resource.selector.ts index d510a104ef3d7c903656c4f62903fc84e1357a60..76fd21b9bfa86f970c3d674849998ed7d24d6450 100644 --- a/alfa-client/libs/tech-shared/src/lib/ngrx/resource.selector.ts +++ b/alfa-client/libs/tech-shared/src/lib/ngrx/resource.selector.ts @@ -1,24 +1,41 @@ import { createFeatureSelector, createSelector } from '@ngrx/store'; -import { isUndefined } from 'lodash-es'; import { StateInfo } from '../resource/resource.model'; -import { ResourceState } from './resource.state'; - -function getState<T>(stateInfo: StateInfo, state: any): ResourceState { - return ( - isUndefined(stateInfo.featureName) ? state - : state.hasOwnProperty(stateInfo.featureName) ? state[stateInfo.featureName] - : state - ); -} +import { ListResourceState, SingleResourceState } from './resource.state'; export const selectResource = <T>(stateInfo: StateInfo) => createSelector( - createFeatureSelector<ResourceState>(stateInfo.name), - (state: ResourceState) => <T>getState(stateInfo, state).resource, + createFeatureSelector<SingleResourceState>(stateInfo.name), + (state: SingleResourceState) => + <T>getFeatureState<SingleResourceState>(stateInfo, state).resource, ); export const existResource = <T>(stateInfo: StateInfo) => createSelector( - createFeatureSelector<ResourceState>(stateInfo.name), - (state: ResourceState) => <T>(<any>getState(stateInfo, state).resource)?.loaded, + createFeatureSelector<SingleResourceState>(stateInfo.name), + (state: SingleResourceState) => + <T>getFeatureState<SingleResourceState>(stateInfo, state).resource.loaded, + ); + +export const selectListResource = <T>(stateInfo: StateInfo) => + createSelector( + createFeatureSelector<ListResourceState>(stateInfo.name), + (state: ListResourceState) => + <T>getFeatureState<ListResourceState>(stateInfo, state).listResource, ); + +// export const selectListItems = <T>( +// stateInfo: StateInfo, +// ): MemoizedSelector<object, T, (s1: ResourceState) => T> => +// createSelector( +// createFeatureSelector<ResourceState>(stateInfo.name), +// (state: ResourceState) => { +// const featureState = getFeatureState<ListResourceState>(stateInfo, state); +// const listResource: StateResource<T> = isNotNull(featureState.listResource.resource) && + +// // return <T>getFeatureState<ResourceState>(stateInfo, state).resource; +// } +// ); + +function getFeatureState<T>(stateInfo: StateInfo, state: any): T { + return <T>state.hasOwnProperty(stateInfo.featureName) ? state[stateInfo.featureName] : state; +} diff --git a/alfa-client/libs/tech-shared/src/lib/ngrx/resource.state.service.ts b/alfa-client/libs/tech-shared/src/lib/ngrx/resource.state.service.ts index 6260501038632981305670f82428daa8d20412e0..e866a4f7741d9986c83755315f8b5e5f657033ad 100644 --- a/alfa-client/libs/tech-shared/src/lib/ngrx/resource.state.service.ts +++ b/alfa-client/libs/tech-shared/src/lib/ngrx/resource.state.service.ts @@ -1,19 +1,16 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { Resource, ResourceUri, getUrl, hasLink } from '@ngxp/rest'; -import { isEqual, isNull } from 'lodash-es'; -import { Observable, combineLatest, filter, startWith, tap } from 'rxjs'; -import { ResourceServiceConfig, StateInfo } from '../resource/resource.model'; -import { mapToFirst, mapToResource } from '../resource/resource.rxjs.operator'; +import { ResourceUri } from '@ngxp/rest'; +import { Observable } from 'rxjs'; +import { StateInfo } from '../resource/resource.model'; +import { StateResource } from '../resource/resource.util'; import { - StateResource, - createEmptyStateResource, - isInvalidResourceCombination, - isLoadingRequired, - isStateResoureStable, -} from '../resource/resource.util'; -import { isNotNull } from '../tech.util'; -import { ResourceActions, createResourceActions } from './actions'; + ListResourceLoadActions, + ResourceActions, + SingleResourceLoadActions, + createListResourceActions, + createSingleResourceActions, +} from './actions'; import { EffectService } from './effects.service'; import * as ResourceSelectors from '../ngrx/resource.selector'; @@ -25,36 +22,34 @@ export class StateService { private effectService: EffectService, ) {} - public createInstance(stateInfo: StateInfo): ResourceStateService { - return new ResourceStateService(stateInfo, this.store, this.effectService); + public createSingleResourceService<T>(stateInfo: StateInfo): SingleResourceStateService<T> { + return new SingleResourceStateService(stateInfo, this.store, this.effectService); + } + + public createListResourceStateService<T>(stateInfo: StateInfo): ListResourceStateService<T> { + return new ListResourceStateService(stateInfo, this.store, this.effectService); } } -export class ResourceStateService { +export abstract class GenerellResourceStateService<T> { actions: ResourceActions; constructor( - private stateInfo: StateInfo, - private store: Store, - private effectService: EffectService, + protected stateInfo: StateInfo, + protected store: Store, + protected effectService: EffectService, ) { this.init(); } - public init() { + public init(): void { this.initActions(); this.initEffects(); - - return this; } - private initActions(): void { - this.actions = createResourceActions(this.stateInfo.name); - } + protected abstract initActions(): void; - private initEffects(): void { - this.effectService.addEffects(this.actions); - } + protected abstract initEffects(): void; public clearResource(): void { this.store.dispatch(this.actions.clearAction()); @@ -68,92 +63,57 @@ export class ResourceStateService { this.store.dispatch(this.actions.reloadAction()); } - public selectResource<T>(): Observable<StateResource<T>> { - return this.store.select(ResourceSelectors.selectResource(this.stateInfo)); - } - - public existsResource(): Observable<boolean> { - return this.store.select(ResourceSelectors.existResource(this.stateInfo)); - } + public abstract selectResource<T>(): Observable<StateResource<T>>; } -export class ResourceLoader<B extends Resource, T extends Resource> { - configResource: B = null; +export class SingleResourceStateService<T> extends GenerellResourceStateService<T> { + actions: SingleResourceLoadActions; constructor( - private config: ResourceServiceConfig<B>, - private resourceStateService: ResourceStateService, - ) {} - - public load() { - return combineLatest([ - this.resourceStateService.selectResource(), - this.getConfigResource(), - ]).pipe( - tap(([stateResource, configResource]) => - this.handleConfigResourceChanged(<StateResource<T>>stateResource, configResource), - ), - filter( - ([stateResource, configResource]) => - !isInvalidResourceCombination(<StateResource<T>>stateResource, configResource), - ), - mapToFirst<T, B>(), - startWith(createEmptyStateResource<T>(true)), - ); + protected stateInfo: StateInfo, + protected store: Store, + protected effectService: EffectService, + ) { + super(stateInfo, store, effectService); } - private getConfigResource(): Observable<B> { - return this.config.resource.pipe(filter(isStateResoureStable), mapToResource<B>()); + protected initActions(): void { + this.actions = createSingleResourceActions(this.stateInfo); } - handleConfigResourceChanged(stateResource: StateResource<T>, configResource: B): void { - if (!isEqual(this.configResource, configResource)) { - this.configResource = configResource; - this.updateStateResourceByConfigResource(stateResource, configResource); - } else if (this.shouldLoadResource(stateResource, configResource)) { - this.loadResource(configResource); - } + protected initEffects(): void { + this.effectService.addSingleResourceEffects(this.actions); } - updateStateResourceByConfigResource(stateResource: StateResource<T>, configResource: B): void { - if (!isStateResoureStable(stateResource)) { - return; - } - if (this.shouldClearStateResource(stateResource, configResource)) { - this.resourceStateService.clearResource(); - } else if (isNotNull(configResource) && hasLink(configResource, this.config.getLinkRel)) { - this.loadResource(configResource); - } + public selectResource<T>(): Observable<StateResource<T>> { + return this.store.select(ResourceSelectors.selectResource(this.stateInfo)); } - shouldClearStateResource(stateResource: StateResource<T>, configResource: B): boolean { - return ( - (isNull(configResource) || this.hasNotGetLink(configResource)) && - !this.isStateResourceEmpty(stateResource) - ); + public existsResource(): Observable<boolean> { + return this.store.select(ResourceSelectors.existResource(this.stateInfo)); } +} - private hasNotGetLink(configResource: B) { - return isNotNull(configResource) && !hasLink(configResource, this.config.getLinkRel); - } +export class ListResourceStateService<T> extends GenerellResourceStateService<T> { + actions: ListResourceLoadActions; - private isStateResourceEmpty(stateResource: StateResource<T>): boolean { - return isEqual(stateResource, createEmptyStateResource()); + constructor( + protected stateInfo: StateInfo, + protected store: Store, + protected effectService: EffectService, + ) { + super(stateInfo, store, effectService); } - shouldLoadResource(stateResource: StateResource<T>, configResource: B): boolean { - return ( - isNotNull(configResource) && - hasLink(configResource, this.config.getLinkRel) && - isLoadingRequired(stateResource) - ); + protected initActions(): void { + this.actions = createListResourceActions(this.stateInfo); } - loadResource(configResource: B): void { - this.resourceStateService.loadResource(this.getGetUrl(configResource)); + protected initEffects(): void { + this.effectService.addListResourceEffects(this.actions); } - private getGetUrl(configResource: B): string { - return getUrl(configResource, this.config.getLinkRel); + public selectResource<T>(): Observable<StateResource<T>> { + return this.store.select(ResourceSelectors.selectListResource(this.stateInfo)); } } diff --git a/alfa-client/libs/tech-shared/src/lib/ngrx/resource.state.ts b/alfa-client/libs/tech-shared/src/lib/ngrx/resource.state.ts index 597f431f8788c5470747e6cb31ea0a6fae19967c..447db94dc854e402951fb190e8842323320f81c4 100644 --- a/alfa-client/libs/tech-shared/src/lib/ngrx/resource.state.ts +++ b/alfa-client/libs/tech-shared/src/lib/ngrx/resource.state.ts @@ -1,10 +1,18 @@ import { Resource } from '@ngxp/rest'; -import { StateResource, createEmptyStateResource } from '../resource/resource.util'; +import { ListResource, StateResource, createEmptyStateResource } from '../resource/resource.util'; -export interface ResourceState { +export interface SingleResourceState { resource?: StateResource<Resource>; } -export const initialResourceState: ResourceState = { +export const initialResourceState: SingleResourceState = { resource: createEmptyStateResource<Resource>(), }; + +export interface ListResourceState { + listResource?: StateResource<ListResource>; +} + +export const initialListResourceState: ListResourceState = { + listResource: createEmptyStateResource<ListResource>(), +}; diff --git a/alfa-client/libs/tech-shared/src/lib/resource/list-resource.service.ts b/alfa-client/libs/tech-shared/src/lib/resource/list-resource.service.ts index 0a4dfa4f9050e29f527d0143c188116ab3a99fcb..e7fa7cbce2463605e09e6b289ffe160a7409e4ad 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/list-resource.service.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/list-resource.service.ts @@ -1,30 +1,9 @@ -import { Resource, ResourceUri, getUrl, hasLink } from '@ngxp/rest'; -import { isEqual, isNull } from 'lodash-es'; -import { - BehaviorSubject, - Observable, - combineLatest, - filter, - first, - map, - startWith, - tap, -} from 'rxjs'; -import { isNotNull, isNotUndefined } from '../tech.util'; -import { CreateResourceData, ListItemResource, ListResourceServiceConfig } from './resource.model'; -import { ResourceRepository } from './resource.repository'; -import { mapToFirst, mapToResource } from './resource.rxjs.operator'; -import { - ListResource, - StateResource, - createEmptyStateResource, - createStateResource, - getEmbeddedResources, - isEmptyStateResource, - isInvalidResourceCombination, - isLoadingRequired, - isStateResoureStable, -} from './resource.util'; +import { Resource } from '@ngxp/rest'; +import { Observable, filter, map } from 'rxjs'; +import { ResourceLoader } from '../ngrx/resource.loader'; +import { ListResourceStateService, StateService } from '../ngrx/resource.state.service'; +import { ListItemResource, ListResourceServiceConfig } from './resource.model'; +import { ListResource, StateResource, getEmbeddedResources } from './resource.util'; /** * B = Type of baseresource @@ -39,198 +18,230 @@ export class ResourceListService< readonly nextLink: string = 'next'; readonly prevLink: string = 'prev'; - readonly listResource: BehaviorSubject<StateResource<T>> = new BehaviorSubject( - createEmptyStateResource(), - ); + // readonly listResource: BehaviorSubject<StateResource<T>> = new BehaviorSubject( + // createEmptyStateResource(), + // ); - readonly selectedResource: BehaviorSubject<StateResource<I>> = new BehaviorSubject( - createEmptyStateResource(), - ); + // readonly selectedResource: BehaviorSubject<StateResource<I>> = new BehaviorSubject( + // createEmptyStateResource(), + // ); baseResource: B = null; + resourceStateService: ListResourceStateService<T>; + resourceLoader: ResourceLoader<B, T>; + constructor( private config: ListResourceServiceConfig<B>, - private repository: ResourceRepository, - ) {} - - public getList(): Observable<StateResource<T>> { - return combineLatest([this.listResource.asObservable(), this.getConfigResource()]).pipe( - tap(([stateResource, configResource]) => this.handleChanges(stateResource, configResource)), - tap(([, configResource]) => this.handleNullConfigResource(configResource)), - filter(([stateResource]) => !isInvalidResourceCombination(stateResource, this.baseResource)), - mapToFirst<T, B>(), - startWith(createEmptyStateResource<T>(true)), - ); - } - - private getConfigResource(): Observable<B> { - return this.config.baseResource.pipe(filter(isStateResoureStable<B>), mapToResource<B>()); - } - - handleChanges(stateResource: StateResource<T>, configResource: B): void { - if (!isEqual(this.baseResource, configResource)) { - this.handleConfigResourceChanges(configResource); - } else if (this.shouldLoadResource(stateResource, configResource)) { - this.loadListResource(configResource, this.config.listLinkRel); - } - } - - handleConfigResourceChanges(newConfigResource: B): void { - this.baseResource = newConfigResource; - if (this.hasListLinkRel() && isStateResoureStable(this.listResource.value)) { - this.loadListResource(this.baseResource, this.config.listLinkRel); - } else if (!this.hasListLinkRel() && isStateResoureStable(this.listResource.value)) { - this.clearCurrentListResource(); - } - } - - private hasListLinkRel(): boolean { - return hasLink(this.baseResource, this.config.listLinkRel); - } - - shouldLoadResource(stateResource: StateResource<T>, configResource: B): boolean { - return isNotNull(configResource) && isLoadingRequired(stateResource) && this.hasListLinkRel(); - } - - handleNullConfigResource(configResource: B): void { - if (this.shouldClearStateResource(configResource)) { - this.clearCurrentListResource(); - } + private stateService: StateService, + ) { + this.initStateService(); + this.initResourceLoader(); } - private clearCurrentListResource(): void { - this.listResource.next(createEmptyStateResource()); - } - - shouldClearStateResource(configResource: B): boolean { - return isNull(configResource) && !isEmptyStateResource(this.listResource.value); - } - - public create(toCreate: unknown): Observable<Resource> { - this.verifyBeforeCreation(); - return this.repository.createResource( - this.buildCreateResourceData(toCreate, this.config.createLinkRel), + private initStateService(): void { + this.resourceStateService = this.stateService.createListResourceStateService<T>( + this.config.stateInfo, ); } - private verifyBeforeCreation(): void { - this.verifyValidListResource(); - this.throwErrorOn(!this.isCreateLinkPresent(), 'No creation link exists.'); - } - - private buildCreateResourceData(toCreate: any, linkRel: string): CreateResourceData<T> { - return { - resource: this.getListResource(), - linkRel, - toCreate, - }; - } - - private isCreateLinkPresent(): boolean { - return this.hasLinkRel(this.config.createLinkRel); + private initResourceLoader(): void { + this.resourceLoader = new ResourceLoader<B, T>(this.config, this.resourceStateService); } - public select(uri: ResourceUri): void { - this.verifyBeforeSelection(uri); - this.setSelectedResourceLoading(); - this.repository - .getResource(uri) - .pipe(first()) - .subscribe((loadedResource) => { - this.selectedResource.next(createStateResource(<I>loadedResource)); - }); - } - - verifyBeforeSelection(uri: ResourceUri): void { - this.verifyValidListResource(); - this.throwErrorOn(!this.existsUriInList(uri), 'No entry match with given uri.'); - } - - private verifyValidListResource(): void { - this.throwErrorOn(isNull(this.getListResource()), 'No list resource available.'); - } - - setSelectedResourceLoading(): void { - this.selectedResource.next({ ...this.selectedResource.value, loading: true }); - } - - existsUriInList(uri: ResourceUri): boolean { - const listResources: Resource[] = getEmbeddedResources( - this.listResource.value, - this.config.listLinkRel, - ); - - return isNotUndefined(listResources.find((resource) => getUrl(resource) === uri)); - } - - public unselect(): void { - this.selectedResource.next(createEmptyStateResource()); - } - - public getSelected(): Observable<StateResource<Resource>> { - return this.selectedResource.asObservable(); - } + public getList(): Observable<StateResource<T>> { + return this.resourceLoader.get(); + } + + //TODO migrieren + + // public getList(): Observable<StateResource<T>> { + // return combineLatest([this.listResource.asObservable(), this.getConfigResource()]).pipe( + // tap(([stateResource, configResource]) => this.handleChanges(stateResource, configResource)), + // tap(([, configResource]) => this.handleNullConfigResource(configResource)), + // filter(([stateResource]) => !isInvalidResourceCombination(stateResource, this.baseResource)), + // mapToFirst<T, B>(), + // startWith(createEmptyStateResource<T>(true)), + // ); + // } + + // private getConfigResource(): Observable<B> { + // return this.config.baseResource.pipe(filter(isStateResoureStable<B>), mapToResource<B>()); + // } + + // handleChanges(stateResource: StateResource<T>, configResource: B): void { + // if (!isEqual(this.baseResource, configResource)) { + // this.handleConfigResourceChanges(configResource); + // } else if (this.shouldLoadResource(stateResource, configResource)) { + // this.loadListResource(configResource, this.config.getLinkRel); + // } + // } + + // handleConfigResourceChanges(newConfigResource: B): void { + // this.baseResource = newConfigResource; + // if (this.hasListLinkRel() && isStateResoureStable(this.listResource.value)) { + // this.loadListResource(this.baseResource, this.config.getLinkRel); + // } else if (!this.hasListLinkRel() && isStateResoureStable(this.listResource.value)) { + // this.clearCurrentListResource(); + // } + // } + + // private hasListLinkRel(): boolean { + // return hasLink(this.baseResource, this.config.getLinkRel); + // } + + // shouldLoadResource(stateResource: StateResource<T>, configResource: B): boolean { + // return isNotNull(configResource) && isLoadingRequired(stateResource) && this.hasListLinkRel(); + // } + + // handleNullConfigResource(configResource: B): void { + // if (this.shouldClearStateResource(configResource)) { + // this.clearCurrentListResource(); + // } + // } + + // private clearCurrentListResource(): void { + // this.listResource.next(createEmptyStateResource()); + // } + + // shouldClearStateResource(configResource: B): boolean { + // return isNull(configResource) && !isEmptyStateResource(this.listResource.value); + // } + + //TODO migrieren + // public create(toCreate: unknown): Observable<Resource> { + // // this.verifyBeforeCreation(); + // return this.repository.createResource( + // this.buildCreateResourceData(toCreate, this.config.createLinkRel), + // ); + // } + + // private verifyBeforeCreation(): void { + // this.verifyValidListResource(); + // this.throwErrorOn(!this.isCreateLinkPresent(), 'No creation link exists.'); + // } + + //TODO migrieren + // private buildCreateResourceData(toCreate: any, linkRel: string): CreateResourceData<T> { + // return { + // resource: this.getListResource(), + // linkRel, + // toCreate, + // }; + // } + + // private isCreateLinkPresent(): boolean { + // return this.hasLinkRel(this.config.createLinkRel); + // } + + //TODO migrieren + // public select(uri: ResourceUri): void { + // // this.verifyBeforeSelection(uri); + // this.setSelectedResourceLoading(); + // this.repository + // .getResource(uri) + // .pipe(first()) + // .subscribe((loadedResource) => { + // this.selectedResource.next(createStateResource(<I>loadedResource)); + // }); + // } + + // verifyBeforeSelection(uri: ResourceUri): void { + // this.verifyValidListResource(); + // this.throwErrorOn(!this.existsUriInList(uri), 'No entry match with given uri.'); + // } + + // private verifyValidListResource(): void { + // this.throwErrorOn(isNull(this.getListResource()), 'No list resource available.'); + // } + + // setSelectedResourceLoading(): void { + // this.selectedResource.next({ ...this.selectedResource.value, loading: true }); + // } + + // existsUriInList(uri: ResourceUri): boolean { + // const listResources: Resource[] = getEmbeddedResources( + // this.listResource.value, + // this.config.getLinkRel, + // ); + + // return isNotUndefined(listResources.find((resource) => getUrl(resource) === uri)); + // } + + //TODO migrieren + // public unselect(): void { + // this.selectedResource.next(createEmptyStateResource()); + // } + + // //TODO migrieren + // public getSelected(): Observable<StateResource<Resource>> { + // return this.selectedResource.asObservable(); + // } public refresh(): void { - this.listResource.next({ ...this.listResource.value, reload: true }); + this.resourceStateService.reloadResource(); + // this.listResource.next({ ...this.listResource.value, reload: true }); } + //TODO migrieren public prev(): void { - this.throwErrorOn(!this.hasPrevLink(), 'There is no previous page.'); - this.loadListResource(this.getListResource(), this.prevLink); + // this.throwErrorOn(!this.hasPrevLink(), 'There is no previous page.'); + //TODO Migrieren + // this.loadListResource(this.getListResource(), this.prevLink); } + //TODO migrieren public next(): void { - this.throwErrorOn(!this.hasNextLink(), 'There is no next page.'); - this.loadListResource(this.getListResource(), this.nextLink); - } - - loadListResource(resource: B | T, linkRel: string): void { - this.setStateResourceLoading(); - this.repository - .getListResource(resource, linkRel) - .pipe(first()) - .subscribe((loadedListResource: T) => this.updateListResource(loadedListResource)); - } - - setStateResourceLoading(): void { - console.info('Update State set listResource loading...'); - this.listResource.next(createEmptyStateResource(true)); - } - - updateListResource(listResource: T): void { - console.info('Update State listResource: ', listResource); - this.listResource.next(createStateResource(listResource)); - } - - private throwErrorOn(condition: boolean, errorMsg: string): void { - if (condition) throw Error(errorMsg); - } - - public hasMore(): boolean { - return this.hasNextLink(); - } - - private hasNextLink(): boolean { - return this.hasLinkRel(this.nextLink); - } - - public isFirst(): boolean { - return !this.hasPrevLink(); - } - - private hasPrevLink(): boolean { - return this.hasLinkRel(this.prevLink); - } - - private hasLinkRel(linkRel: string): boolean { - return hasLink(this.getListResource(), linkRel); - } - - private getListResource(): T { - return this.listResource.value.resource; - } + // this.throwErrorOn(!this.hasNextLink(), 'There is no next page.'); + //TODO Migrieren + // this.loadListResource(this.getListResource(), this.nextLink); + } + + // loadListResource(resource: B | T, linkRel: string): void { + // this.setStateResourceLoading(); + // this.repository + // .getListResource(resource, linkRel) + // .pipe(first()) + // .subscribe((loadedListResource: T) => this.updateListResource(loadedListResource)); + // } + + // setStateResourceLoading(): void { + // console.info('Update State set listResource loading...'); + // this.listResource.next(createEmptyStateResource(true)); + // } + + // updateListResource(listResource: T): void { + // console.info('Update State listResource: ', listResource); + // this.listResource.next(createStateResource(listResource)); + // } + + // private throwErrorOn(condition: boolean, errorMsg: string): void { + // if (condition) throw Error(errorMsg); + // } + + // public hasMore(): boolean { + // return this.hasNextLink(); + // } + + // private hasNextLink(): boolean { + // return this.hasLinkRel(this.nextLink); + // } + + // public isFirst(): boolean { + // return !this.hasPrevLink(); + // } + + // private hasPrevLink(): boolean { + // return this.hasLinkRel(this.prevLink); + // } + + // private hasLinkRel(linkRel: string): boolean { + // return hasLink(this.getListResource(), linkRel); + // } + + // private getListResource(): T { + // return this.listResource.value.resource; + // } public getItems(): Observable<ListItemResource[]> { return this.getList().pipe( diff --git a/alfa-client/libs/tech-shared/src/lib/resource/resource.model.ts b/alfa-client/libs/tech-shared/src/lib/resource/resource.model.ts index f4e537398c9ae4cec27673b53417b50657c9f1c4..09437b2a437b9686b8b7367196b97167c77a3e32 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/resource.model.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/resource.model.ts @@ -2,9 +2,17 @@ import { Resource } from '@ngxp/rest'; import { Observable } from 'rxjs'; import { StateResource } from './resource.util'; -export interface ListResourceServiceConfig<B> { +export interface StateInfo { + name: string; + featureName: string; //TODO Rename +} + +export interface ServiceConfig<B> { + stateInfo: StateInfo; baseResource: Observable<StateResource<B>>; - listLinkRel: LinkRelationName; + getLinkRel: LinkRelationName; +} +export interface ListResourceServiceConfig<B> extends ServiceConfig<B> { listResourceListLinkRel: LinkRelationName; createLinkRel?: LinkRelationName; saveLinkRel?: LinkRelationName; @@ -25,15 +33,7 @@ export interface SaveResourceData<T> { export interface ListItemResource extends Resource {} export declare type LinkRelationName = string; -export interface StateInfo { - name: string; - featureName: string; -} - -export interface ResourceServiceConfig<B> { - stateInfo: StateInfo; - resource: Observable<StateResource<B>>; - getLinkRel: LinkRelationName; +export interface ResourceServiceConfig<B> extends ServiceConfig<B> { delete?: { linkRel: LinkRelationName; order?: string }; edit?: { linkRel: LinkRelationName; order?: string }; } diff --git a/alfa-client/libs/tech-shared/src/lib/resource/resource.service.ts b/alfa-client/libs/tech-shared/src/lib/resource/resource.service.ts index 254db478b683054a314933c42c52649d7a0865e9..349b15f0203fc6f2f18d74aa4f60781ff453e9d0 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/resource.service.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/resource.service.ts @@ -1,6 +1,7 @@ import { Resource, ResourceUri } from '@ngxp/rest'; import { Observable } from 'rxjs'; -import { ResourceLoader, ResourceStateService, StateService } from '../ngrx/resource.state.service'; +import { ResourceLoader } from '../ngrx/resource.loader'; +import { SingleResourceStateService, StateService } from '../ngrx/resource.state.service'; import { ResourceServiceConfig } from './resource.model'; import { StateResource } from './resource.util'; @@ -9,7 +10,7 @@ import { StateResource } from './resource.util'; * T = Type of the resource which is working on */ export abstract class ResourceService<B extends Resource, T extends Resource> { - resourceStateService: ResourceStateService; + resourceStateService: SingleResourceStateService<T>; resourceLoader: ResourceLoader<B, T>; constructor( @@ -21,7 +22,9 @@ export abstract class ResourceService<B extends Resource, T extends Resource> { } private initStateService(): void { - this.resourceStateService = this.stateService.createInstance(this.config.stateInfo); + this.resourceStateService = this.stateService.createSingleResourceService( + this.config.stateInfo, + ); } private initResourceLoader(): void { @@ -29,7 +32,7 @@ export abstract class ResourceService<B extends Resource, T extends Resource> { } public get(): Observable<StateResource<T>> { - return this.resourceLoader.load(); + return this.resourceLoader.get(); } public reloadByResourceUri(resourceUri: ResourceUri): void { diff --git a/alfa-client/libs/tech-shared/src/lib/tech-shared.module.ts b/alfa-client/libs/tech-shared/src/lib/tech-shared.module.ts index f8992bb61a78dd5e740e94deed61f690bda81628..610fbba6c2b34fa9b84966d597dd4fb398acf6d7 100644 --- a/alfa-client/libs/tech-shared/src/lib/tech-shared.module.ts +++ b/alfa-client/libs/tech-shared/src/lib/tech-shared.module.ts @@ -27,6 +27,7 @@ import { Injector, NgModule } from '@angular/core'; import { HttpBinaryFileInterceptor } from './interceptor/http-binary-file.interceptor'; import { HttpXsrfInterceptor } from './interceptor/http-xsrf.interceptor'; import { XhrInterceptor } from './interceptor/xhr.interceptor'; +import { EffectService } from './ngrx/effects.service'; import { StateService } from './ngrx/resource.state.service'; import { ConvertForDataTestPipe } from './pipe/convert-for-data-test.pipe'; import { ConvertToBooleanPipe } from './pipe/convert-to-boolean.pipe'; @@ -88,6 +89,7 @@ import { ToTrafficLightPipe } from './pipe/to-traffic-light.pipe'; ], providers: [ StateService, + EffectService, { provide: HTTP_INTERCEPTORS, useClass: XhrInterceptor,