Skip to content
This repository has been archived by the owner on May 21, 2021. It is now read-only.

Commit

Permalink
build(lib): Changed whole logic of Query library
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianmusial committed Jan 29, 2021
1 parent 94f76b6 commit 2c25c5c
Show file tree
Hide file tree
Showing 14 changed files with 370 additions and 157 deletions.
142 changes: 46 additions & 96 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,117 +17,67 @@ Library requires `@ngrx/store` module.

## Usage

State (for example `homepage.state.ts`):
Import QueryModule in your app.module.ts:
```ts
import { queryReducer } from '@ngsm/query';
import { HomepageApiResponseDto } from 'your-api-dto.interfaces.ts';
import { QueryModule } from '@ngsm/query';

export interface HomepageQueryState {
getHomepageApiQuery?: Query<HomepageApiResponseDto>;
}

export const HOMEPAGE_QUERY_KEY = 'homepageQuery';

export interface HomepagePartialState {
readonly [HOMEPAGE_QUERY_KEY]: HomepageQueryState;
// Your feature states, for example:
// readonly [HOMEPAGE_FEATURE_KEY]: HomepageState;
}
```

Reducer (for example `homepage.reducer.ts`):
```ts
import { Action } from '@ngrx/store';
import { HomepageQueryState } from './homepage.state';

...

export function homepageQueryReducer(state: HomepageQueryState | undefined, action: Action) {
return queryReducer(state, action);
...,
@NgModule({
....,
imports: [
....,
QueryModule
]
}
```
Selectors (for example `homepage.selectors.ts`):
```ts
import { createFeatureSelector, createSelector } from '@ngrx/store';

export const homepageQueryState = createFeatureSelector<HomepagePartialState, HomepageQueryState>(HOMEPAGE_QUERY_KEY);

export const getHomepageApiQuery = createSelector(
homepageQueryState,
(state: HomepageQueryState) => state.getHomepageApiQuery
);
```
State module (for example `homepage-state.module.ts`):
How to use @Query decorator in your HTTP service:
```ts
import { NgModule } from '@angular/core';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { Query } from '@ngsm/query';
import { HttpClient } from '@angular/common/http';

import { HomapageEffects } from './homepage.effects';
import { HomapageFacade } from './homepage.facade';
import { homepageQueryReducer, homepageReducer } from './homepage.reducer';
import { HOMEPAGE_QUERY_KEY } from './homepage.state';

@NgModule({
imports: [
StoreModule.forFeature(HOMEPAGE_QUERY_KEY, homepageQueryReducer),
EffectsModule.forFeature([HomapageEffects]),
],
providers: [HomapageFacade]
})
export class HomapageStateModule {}
```
@Injectable()
export class CarRepository {
constructor(private http: HttpClient) { }

Effects (for example `homepage.effects.ts`):
```ts
...

getHomepageApi$ = createEffect(() =>
this.actions$.pipe(
ofType(HomepageActions.getHomepageApi),
mergeMap(() => concat(
// run inProgress action
of(QueryActions.inProgress({ query: HomepageQuery.getHomepageApiQuery })),
this.homepageRepository
.getHomepageApi()
.pipe(
mergeMap((response) => [
// run success action
QueryActions.success({ query: HomepageQuery.getHomepageApiQuery, response }),
]),
catchError(error => [
// run failure action
QueryActions.failure({ query: HomepageQuery.getHomepageApiQuery, error }),
])
)
))
)
);

...
@Query({ name: 'getCars', groups: ['cars'] })
getCars(): Observable<object> {
return this.http.get<object>('API_URL');
}
}
```
Facade (for example `homepage.facade.ts`):
In your component (for example `car.component.ts`):
```ts
...
import { QueryFacade } from '@ngsm/query';
...

@Injectable()
export class HomepageFacade {
getHomepageQuery$ = this.store.pipe(select(HomepageSelectors.getHomepageQuery));

loader$ = isQueryInProgress$([
this.getHomepageQuery$,
// add all feature queries
...,
]);

constructor(private store: Store<HomepagePartialState>) {}

dispatch(action: Action) {
this.store.dispatch(action);
@Component({
selector: 'app-cars',
templateUrl: './cars.component.html',
})
export class CarsComponent implements OnInit, OnDestroy {
// Available methods:
loader$ = this.queryFacade.isInProgress$('cars');
response$ = this.queryFacade.response$<YOUR_TYPE>('getCars');
query$ = this.queryFacade.query$<YOUR_TYPE>('getCars');
error$ = this.queryFacade.error$('getCars');
status$ = this.queryFacade.status$('getCars');

constructor(
private carRepository: CarRepository,
private queryFacade: QueryFacade,
) {}

ngOnInit() {
this.carRepository
.getCars()
.subscribe();
}

ngOnDestroy() { }
}
```
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ngsm/query",
"version": "0.1.0",
"version": "0.2.1-alpha.0",
"private": false,
"dependencies": {
"@angular/common": "^9.1.0",
Expand Down
7 changes: 5 additions & 2 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import * as QueryActions from './query.actions';
import * as QueryActions from './store/query.actions';

export * from './query.model';
export * from './query.reducers';
export * from './query.utils';
export * from './query.decorator';
export * from './query.helpers';
export * from './store/query.facade';
export * from './query.module';

export { QueryActions };
43 changes: 43 additions & 0 deletions src/lib/query.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { map, catchError } from 'rxjs/operators';
import { APP_BOOTSTRAP_LISTENER } from '@angular/core';
import { Store } from '@ngrx/store';
import { QueryConfig } from './query.model';
import * as QueryActions from './store/query.actions';

let _store;

export const BOOTSTRAP_QUERY_PROVIDER = {
provide: APP_BOOTSTRAP_LISTENER,
multi: true,
deps: [Store],
useFactory: (s) => {
_store = s;
return (store) => store;
}
};

export const Query = (queryConfig: QueryConfig): MethodDecorator => (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) => {
const original = descriptor.value;

descriptor.value = function () {
_store.dispatch(QueryActions.inProgress({ queryConfig }));

return original.apply(this, arguments)
.pipe(
map((response) => {
_store.dispatch(QueryActions.success({ queryConfig, response }));
return response;
}),
catchError((error) => {
_store.dispatch(QueryActions.failure({ queryConfig, error }));
throw error;
})
);
};

return descriptor;
};
78 changes: 78 additions & 0 deletions src/lib/query.helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { QueryResponse, QueryConfig, QueryGroups, Queries } from './query.model';
import { QueryState } from './store/query.state';
import { isQueryGroupInProgress } from './query.utils';

export const getInitialQuery = (): QueryResponse<null> => ({
status: null,
response: null,
error: null,
isDirty: false,
isInProgress: false,
isSuccess: false,
isError: false,
});

export const getQueriesGroup = (
queryState: QueryState,
queryConfig: QueryConfig,
query: QueryResponse<any>,
groupName: string,
): Queries[] => {
const { groups } = queryState;
const { name } = queryConfig;

return {
...(groups[groupName] ? groups[groupName].queries : []),
[name]: query,
};
};

export const getQueryGroups = (
queryState: QueryState,
queryConfig: QueryConfig,
query: QueryResponse<any>
): QueryGroups => {
const { groups: stateGroups } = queryState;
const { groups } = queryConfig;
const extendedGroups = { ...stateGroups };

if (!groups) {
return extendedGroups;
}

groups.map(groupName => {
const queries = getQueriesGroup(queryState, queryConfig, query, groupName);
extendedGroups[groupName] = {
isInProgress: isQueryGroupInProgress(queries),
queries
};
});

return extendedGroups;
};

export const getQueries = (
queryState: QueryState,
queryConfig: QueryConfig,
query: QueryResponse<any>
): Queries => {
return {
...queryState.queries,
[queryConfig.name]: query
};
};

export const parseQueryState = (
queryState: QueryState,
queryConfig: QueryConfig,
query: QueryResponse<any>
): QueryState => {
const groups = getQueryGroups(queryState, queryConfig, query);
const queries = getQueries(queryState, queryConfig, query);

return {
...queryState,
queries,
groups
};
};
30 changes: 26 additions & 4 deletions src/lib/query.model.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
import { HttpErrorResponse } from '@angular/common/http';

export interface Queries {
[key: string]: QueryResponse<any>;
}

export interface QueryGroup {
queries: Queries[];
isInProgress: boolean;
}

export interface QueryGroups {
[key: string]: QueryGroup;
}

export interface QueryConfig {
name: string;
groups?: string[];
}

export enum QueryStatus {
Success = 'SUCCESS',
InProgress = 'IN_PROGRESS',
Failure = 'FAILURE',
}

export interface Query<T> {
status?: QueryStatus;
response?: T;
error?: HttpErrorResponse;
export interface QueryResponse<T> {
response: T;
error: HttpErrorResponse;
status: QueryStatus;
isSuccess: boolean;
isError: boolean;
isInProgress: boolean;
isDirty: boolean;
}
18 changes: 18 additions & 0 deletions src/lib/query.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { QueryReducer } from './store/query.reducer';

import { QUERY_STORE_KEY } from './store/query.state';
import { QueryFacade } from './store/query.facade';
import { BOOTSTRAP_QUERY_PROVIDER } from './query.decorator';

@NgModule({
imports: [
StoreModule.forFeature(QUERY_STORE_KEY, QueryReducer),
],
providers: [
QueryFacade,
BOOTSTRAP_QUERY_PROVIDER
]
})
export class QueryModule {}
Loading

0 comments on commit 2c25c5c

Please sign in to comment.