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

build(lib): Changed whole logic of Query library based on decorator #6

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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