diff --git a/projects/neuroglia/angular-ngrx-component-store-odata-table/src/lib/odata-table.store.ts b/projects/neuroglia/angular-ngrx-component-store-odata-table/src/lib/odata-table.store.ts index 76092c4..c349628 100644 --- a/projects/neuroglia/angular-ngrx-component-store-odata-table/src/lib/odata-table.store.ts +++ b/projects/neuroglia/angular-ngrx-component-store-odata-table/src/lib/odata-table.store.ts @@ -2,10 +2,11 @@ import { Injectable, Injector, inject } from '@angular/core'; import { ODATA_DATA_SOURCE_ENDPOINT, ODataDataSource } from '@neuroglia/angular-data-source-odata'; import { ColumnDefinition, + IQueryableTableStore, QueryableTableState, QueryableTableStore, + QueryableTableConfig, } from '@neuroglia/angular-ngrx-component-store-queryable-table'; -import { validateAuthorizations } from '@neuroglia/authorization-rule'; import { Observable, of } from 'rxjs'; import { filter, map, switchMap, takeUntil } from 'rxjs/operators'; import { ODataPrimitiveTypeEnum } from './models'; @@ -14,9 +15,13 @@ import { ODataMetadataService } from './odata-metadata.service'; @Injectable() export class ODataTableStore< - TState extends QueryableTableState = QueryableTableState, - TData = any, -> extends QueryableTableStore { + TState extends QueryableTableState = QueryableTableState, + TData = any, + TConfig extends QueryableTableConfig = QueryableTableConfig, + > + extends QueryableTableStore + implements IQueryableTableStore +{ /** Holds the datasource instance */ protected dataSource: ODataDataSource | null; @@ -28,35 +33,32 @@ export class ODataTableStore< } /** @inheritdoc */ - protected getServiceEndpoint(initialState: Partial): string { + protected getServiceDataEnpoint(config: TConfig): string { return ( - initialState.dataUrl || - `${initialState.serviceUrl}${!initialState.serviceUrl!.endsWith('/') ? '/' : ''}${initialState.target}${ - initialState.query || '' - }` + config.dataUrl || + `${config.serviceUrl}${!config.serviceUrl!.endsWith('/') ? '/' : ''}${config.target}${config.query || ''}` ); } /** @inheritdoc */ - protected getColumnDefinitions(initialState: Partial): Observable { - return !initialState.useMetadata - ? of(initialState.columnDefinitions).pipe( + protected getColumnDefinitions(config: TConfig): Observable { + return !config.useMetadata + ? of(config.columnDefinitions).pipe( filter((definitions: ColumnDefinition[] | undefined) => !!definitions?.length), map((definitions: ColumnDefinition[] | undefined) => definitions as ColumnDefinition[]), ) - : this.odataMetadataService.getMetadata(initialState.serviceUrl!).pipe( + : this.odataMetadataService.getMetadata(config.serviceUrl!).pipe( takeUntil(this.destroy$), switchMap((_: ODataMetadataSchema.Metadata) => - !initialState.targetType - ? this.odataMetadataService.getColumnDefinitions(initialState.target!) - : this.odataMetadataService.getColumnDefinitionsForQualifiedName(initialState.targetType), + !config.targetType + ? this.odataMetadataService.getColumnDefinitions(config.target!) + : this.odataMetadataService.getColumnDefinitionsForQualifiedName(config.targetType), ), map((definitions: ColumnDefinition[]) => { - const token = this.keycloak?.getKeycloakInstance()?.tokenParsed; - const stateDefinitionNames = (initialState.columnDefinitions || []).map((def) => def.name); + const stateDefinitionNames = (config.columnDefinitions || []).map((def) => def.name); const columnDefinitions = [ ...definitions.filter((def) => !stateDefinitionNames.includes(def.name)), - ...(initialState.columnDefinitions || []).map((stateDef) => { + ...(config.columnDefinitions || []).map((stateDef) => { const def = definitions.find((def) => def.name === stateDef.name); if (!def) { return stateDef; @@ -64,7 +66,7 @@ export class ODataTableStore< const columnDefinition = { ...def, ...stateDef }; return columnDefinition; }), - ].filter((def) => !def.authorizations || (token && validateAuthorizations(token, def.authorizations))); + ]; return columnDefinitions as ColumnDefinition[]; }), ); @@ -76,13 +78,13 @@ export class ODataTableStore< } /** @inheritdoc */ - protected injectDataSource(): ODataDataSource { + protected injectDataSource(config: TConfig): Observable> { const dataUrl = this.get((state) => state.dataUrl); const dataSourceInjector = Injector.create({ name: 'DataSourceInjector', parent: this.injector, providers: [ODataDataSource, { provide: ODATA_DATA_SOURCE_ENDPOINT, useValue: dataUrl }], }); - return dataSourceInjector.get(ODataDataSource) as ODataDataSource; + return of(dataSourceInjector.get(ODataDataSource) as ODataDataSource); } } diff --git a/projects/neuroglia/angular-ngrx-component-store-queryable-table/src/lib/models/queryable-table-store-interface.ts b/projects/neuroglia/angular-ngrx-component-store-queryable-table/src/lib/models/queryable-table-store-interface.ts index 481c70c..7747f88 100644 --- a/projects/neuroglia/angular-ngrx-component-store-queryable-table/src/lib/models/queryable-table-store-interface.ts +++ b/projects/neuroglia/angular-ngrx-component-store-queryable-table/src/lib/models/queryable-table-store-interface.ts @@ -3,10 +3,12 @@ import { QueryableTableState } from './queryable-table.state'; import { ColumnDefinition } from './column-definition'; import { Paging, Sort } from '@neuroglia/angular-data-source-queryable'; import { Filters } from './filters'; +import { QueryableTableConfig } from './queryable-table-config'; export interface IQueryableTableStore< TState extends QueryableTableState = QueryableTableState, TData = any, + TConfig extends QueryableTableConfig = QueryableTableConfig, > { /** Selects the data */ data$: Observable; @@ -58,7 +60,7 @@ export interface IQueryableTableStore< * Initializes the state and its underlying datasource * @param initialState A partial state with at least the service URL, the entity name and the column definitions */ - init(initialState: Partial): Observable; + init(config: TConfig): Observable; /** * Clears persisted data */ diff --git a/projects/neuroglia/angular-ngrx-component-store-queryable-table/src/lib/models/queryable-table.state.ts b/projects/neuroglia/angular-ngrx-component-store-queryable-table/src/lib/models/queryable-table.state.ts index dfd608c..fb2dad8 100644 --- a/projects/neuroglia/angular-ngrx-component-store-queryable-table/src/lib/models/queryable-table.state.ts +++ b/projects/neuroglia/angular-ngrx-component-store-queryable-table/src/lib/models/queryable-table.state.ts @@ -2,6 +2,7 @@ import { Sort } from '@neuroglia/angular-data-source-queryable'; import { ColumnDefinition } from './column-definition'; import { Filters } from './filters'; import { SerializedFilter } from './serialized-filter'; +import { QueryableTableConfig } from './queryable-table-config'; /** * Represents the state of an queryable table diff --git a/projects/neuroglia/angular-ngrx-component-store-queryable-table/src/lib/queryable-table.store.ts b/projects/neuroglia/angular-ngrx-component-store-queryable-table/src/lib/queryable-table.store.ts index 80e886c..9433441 100644 --- a/projects/neuroglia/angular-ngrx-component-store-queryable-table/src/lib/queryable-table.store.ts +++ b/projects/neuroglia/angular-ngrx-component-store-queryable-table/src/lib/queryable-table.store.ts @@ -5,12 +5,13 @@ import { ComponentStore } from '@ngrx/component-store'; import { KeycloakService } from 'keycloak-angular'; import { Filter as ODataQueryFilter } from 'odata-query'; import { Observable } from 'rxjs'; -import { map, take, takeUntil, tap } from 'rxjs/operators'; +import { map, switchMap, take, takeUntil, tap } from 'rxjs/operators'; import { ColumnDefinition, Filter, Filters, IQueryableTableStore, + QueryableTableConfig, QueryableTableState, SerializedFilter, expandRowColumnDefinition, @@ -18,6 +19,7 @@ import { } from './models'; import { persistenceKeys } from './persistence-keys'; import { QueryableTableTemplateProvider } from './queryable-table-template-provider.service'; +import { validateAuthorizations } from '@neuroglia/authorization-rule'; /** Creates a default empty state */ export function createEmptyQueryableTableState(): QueryableTableState { @@ -73,6 +75,7 @@ function asODataQueryFilter(filters: Filters): ODataQueryFilter { export abstract class QueryableTableStore< TState extends QueryableTableState = QueryableTableState, TData = any, + TConfig extends QueryableTableConfig = QueryableTableConfig, > extends ComponentStore implements IQueryableTableStore @@ -153,16 +156,16 @@ export abstract class QueryableTableStore< } /** - * Builts the service endpoint URL based on the provided state + * Builds the data endpoint URL based on the provided state * @param state */ - protected abstract getServiceEndpoint(initialState: Partial): string; + protected abstract getServiceDataEnpoint(config: TConfig): string; /** * Gets the @see {@link ColumnDefinition}s for the provided state * @param initialState */ - protected abstract getColumnDefinitions(initialState: Partial): Observable; + protected abstract getColumnDefinitions(config: TConfig): Observable; /** * Returns a the string type for the current datasource, e.g. 'Edm.String' for OData @@ -172,29 +175,40 @@ export abstract class QueryableTableStore< /** * Instanciates and returns the store's data source */ - protected abstract injectDataSource(): QueryableDataSource; + protected abstract injectDataSource(config: TConfig): Observable>; /** * Initializes the state and its underlying datasource - * @param initialState A partial state with at least the service URL, the entity name and the column definitions + * @param config A partial state with at least the service URL, the entity name and the column definitions */ - init(initialState: Partial): Observable { + init(config: TConfig): Observable { if (this.dataSource) { throw 'The store has already been initialized for this component'; } - if (!initialState.dataSourceType) { + if (!config.dataSourceType) { throw 'Unable to initialize, missing service data source type'; } - if (!initialState.serviceUrl) { + if (!config.serviceUrl) { throw 'Unable to initialize, missing service URL'; } - if (!initialState.target) { + if (!config.target) { throw 'Unable to initialize, missing entity name'; } - if (!initialState.useMetadata && !initialState.columnDefinitions) { + if (!config.useMetadata && !config.columnDefinitions) { throw 'Unable to initialize, missing columns definitions'; } - const dataUrl = this.getServiceEndpoint(initialState); + const { + dataSourceType, + serviceUrl, + target, + useMetadata, + expand, + select, + enableSelection, + enableRowExpansion, + query, + } = config; + const dataUrl = this.getServiceDataEnpoint(config); this.columnSettingsStorage = new StorageHandler( persistenceKeys.columnSettings + '::' + dataUrl, ); @@ -202,36 +216,39 @@ export abstract class QueryableTableStore< this.sortStorage = new StorageHandler(persistenceKeys.sort + '::' + dataUrl, null, window.sessionStorage); this.pageSizeStorage = new StorageHandler(persistenceKeys.pageSize + '::' + dataUrl, null, window.sessionStorage); this.pageIndexStorage = new StorageHandler(persistenceKeys.pageIndex + '::' + dataUrl, null, window.sessionStorage); - return this.getColumnDefinitions(initialState).pipe( + return this.getColumnDefinitions(config).pipe( tap((definitions: ColumnDefinition[]) => { const userPreferences = this.columnSettingsStorage!.getItem() || []; - if (initialState.enableSelection) { + if (config.enableSelection) { definitions = [selectRowColumnDefinition, ...definitions]; } - if (initialState.enableRowExpansion) { + if (config.enableRowExpansion) { definitions = [expandRowColumnDefinition, ...definitions]; } - const columnDefinitions = definitions.map((def, index) => { - const columnDefinition = { ...def }; - const userPreference = userPreferences.find((columnDef) => columnDef.name === def.name); - columnDefinition.type = def.type || this.getStringType(); - if (userPreference) { - if (userPreference.position != null) columnDefinition.position = userPreference.position; - if (userPreference.isVisible != null) columnDefinition.isVisible = userPreference.isVisible; - } - columnDefinition.sticky = userPreference?.sticky || columnDefinition.sticky || ''; - if (def.position == null) columnDefinition.position = index + 1; - if (def.isVisible == null) columnDefinition.isVisible = true; - if (def.isSortable == null) columnDefinition.isSortable = false; - if (def.isFilterable == null) columnDefinition.isFilterable = false; - if (def.isEnum == null) columnDefinition.isEnum = false; - if (def.isCollection == null) columnDefinition.isCollection = false; - if (def.isNavigationProperty == null) columnDefinition.isNavigationProperty = false; - if (def.isNullable == null) columnDefinition.isNullable = false; - return columnDefinition; - }); + const token = this.keycloak?.getKeycloakInstance()?.tokenParsed; + const columnDefinitions = definitions + .map((def, index) => { + const columnDefinition = { ...def }; + const userPreference = userPreferences.find((columnDef) => columnDef.name === def.name); + columnDefinition.type = def.type || this.getStringType(); + if (userPreference) { + if (userPreference.position != null) columnDefinition.position = userPreference.position; + if (userPreference.isVisible != null) columnDefinition.isVisible = userPreference.isVisible; + } + columnDefinition.sticky = userPreference?.sticky || columnDefinition.sticky || ''; + if (def.position == null) columnDefinition.position = index + 1; + if (def.isVisible == null) columnDefinition.isVisible = true; + if (def.isSortable == null) columnDefinition.isSortable = false; + if (def.isFilterable == null) columnDefinition.isFilterable = false; + if (def.isEnum == null) columnDefinition.isEnum = false; + if (def.isCollection == null) columnDefinition.isCollection = false; + if (def.isNavigationProperty == null) columnDefinition.isNavigationProperty = false; + if (def.isNullable == null) columnDefinition.isNullable = false; + return columnDefinition; + }) + .filter((def) => !def.authorizations || (token && validateAuthorizations(token, def.authorizations))); let filters = Object.fromEntries( - (this.filtersStorage!.getItem() || (initialState.filters as SerializedFilter[]) || []) + (this.filtersStorage!.getItem() || (config.filters as SerializedFilter[]) || []) .map((entry) => { const columnDefinition = columnDefinitions.find((colDef) => colDef.name === entry.columnName); if (!columnDefinition) { @@ -239,9 +256,9 @@ export abstract class QueryableTableStore< } const filter = this.queryableTableTemplateProvider.getFilter( columnDefinition, - initialState.dataSourceType!, - initialState.serviceUrl!, - initialState.target!, + config.dataSourceType!, + config.serviceUrl!, + config.target!, ); if (!filter) { return [null, null]; @@ -253,15 +270,21 @@ export abstract class QueryableTableStore< if (!Object.keys(filters).length) { filters = this.get((state) => state.filters as Filters); } - const sort = this.sortStorage!.getItem() || initialState.sort || this.get((state) => state.sort); - const pageSize = - this.pageSizeStorage!.getItem() || initialState.pageSize || this.get((state) => state.pageSize); - const pageIndex = - this.pageIndexStorage!.getItem() || initialState.pageIndex || this.get((state) => state.pageIndex); + const sort = this.sortStorage!.getItem() || config.sort || this.get((state) => state.sort); + const pageSize = this.pageSizeStorage!.getItem() || this.get((state) => state.pageSize); + const pageIndex = this.pageIndexStorage!.getItem() || this.get((state) => state.pageIndex); const enableColumnSettings = - initialState.enableColumnSettings === false ? false : this.get((state) => state.enableColumnSettings); + config.enableColumnSettings === false ? false : this.get((state) => state.enableColumnSettings); this.patchState({ - ...initialState, + dataSourceType, + serviceUrl, + target, + useMetadata, + expand, + select, + enableSelection, + enableRowExpansion, + query, dataUrl, columnDefinitions, filters, @@ -270,8 +293,13 @@ export abstract class QueryableTableStore< pageIndex, enableColumnSettings, } as TState); + }), + switchMap((_) => { + return this.injectDataSource(config); + }), + tap((dataSource) => { + this.dataSource = dataSource; const state = this.get(); - this.dataSource = this.injectDataSource(); if (state.select) { this.dataSource.select(state.select); } diff --git a/projects/neuroglia/angular-ui-material-odata-table/src/lib/material-odata-table.store.ts b/projects/neuroglia/angular-ui-material-odata-table/src/lib/material-odata-table.store.ts index 0a5d588..a341a2c 100644 --- a/projects/neuroglia/angular-ui-material-odata-table/src/lib/material-odata-table.store.ts +++ b/projects/neuroglia/angular-ui-material-odata-table/src/lib/material-odata-table.store.ts @@ -1,27 +1,20 @@ -import { Injectable, Type, inject } from '@angular/core'; -import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; +import { Injectable, inject } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; import { ODataTableStore } from '@neuroglia/angular-ngrx-component-store-odata-table'; -import { isSet } from '@neuroglia/common'; +import { QueryableTableConfig, QueryableTableState } from '@neuroglia/angular-ngrx-component-store-queryable-table'; import { - ColumnDefinition, - Filter, - Filters, - QueryableTableState, - SerializedFilter, -} from '@neuroglia/angular-ngrx-component-store-queryable-table'; -import { - ColumnSettingsComponent, - ColumnSettingsDialogData, - FilterDialogData, IMaterialQueryableTableStore, - isFilters, showColumnSettingsDialog, showFilterDialog, } from '@neuroglia/angular-ui-material-queryable-table'; /** The state of an OData table specialized for AngularMaterial */ @Injectable() -export class MaterialODataTableStore = QueryableTableState, TData = any> +export class MaterialODataTableStore< + TState extends QueryableTableState = QueryableTableState, + TData = any, + TConfig extends QueryableTableConfig = QueryableTableConfig, + > extends ODataTableStore implements IMaterialQueryableTableStore { diff --git a/projects/neuroglia/angular-ui-material-queryable-table/src/lib/material-queryable-table.store.ts b/projects/neuroglia/angular-ui-material-queryable-table/src/lib/material-queryable-table.store.ts index 053bf95..d635513 100644 --- a/projects/neuroglia/angular-ui-material-queryable-table/src/lib/material-queryable-table.store.ts +++ b/projects/neuroglia/angular-ui-material-queryable-table/src/lib/material-queryable-table.store.ts @@ -7,6 +7,7 @@ import { ColumnDefinition, Filter, Filters, + QueryableTableConfig, QueryableTableState, QueryableTableStore, SerializedFilter, @@ -82,6 +83,7 @@ export function showColumnSettingsDialog< export class MaterialQueryableTableStore< TState extends QueryableTableState = QueryableTableState, TData = any, + TConfig extends QueryableTableConfig = QueryableTableConfig, > extends QueryableTableStore implements IMaterialQueryableTableStore @@ -91,16 +93,17 @@ export class MaterialQueryableTableStore< constructor() { super(); } - protected getServiceEndpoint(initialState: Partial): string { + + protected getServiceDataEnpoint(config: TConfig): string { throw new Error('Method not implemented.'); } - protected getColumnDefinitions(initialState: Partial): Observable { + protected getColumnDefinitions(config: TConfig): Observable { throw new Error('Method not implemented.'); } protected getStringType(): string { throw new Error('Method not implemented.'); } - protected injectDataSource(): QueryableDataSource { + protected injectDataSource(config: TConfig): Observable> { throw new Error('Method not implemented.'); }