@one-paragon/angular-utilities 2.8.2 → 2.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/karma.conf.js +43 -0
- package/ng-package.json +7 -0
- package/package.json +15 -27
- package/src/action-state/action-state-spinner/action-state-spinner.component.css +16 -0
- package/src/action-state/action-state-spinner/action-state-spinner.component.html +7 -0
- package/src/action-state/action-state-spinner/action-state-spinner.component.spec.ts +25 -0
- package/src/action-state/action-state-spinner/action-state-spinner.component.ts +26 -0
- package/src/action-state/action-state-ui/action-state-ui.module.ts +13 -0
- package/src/action-state/index.ts +8 -0
- package/src/action-state/ngrx-ext/ngrx-ext.module.ts +14 -0
- package/src/action-state/ngrx.ts +69 -0
- package/src/http-request-state/RequestStateFactory.ts +56 -0
- package/src/http-request-state/RequestStateStore.ts +360 -0
- package/src/http-request-state/deprecated.ts +20 -0
- package/src/http-request-state/directives/HttpStateDirectiveBase.ts +29 -0
- package/src/http-request-state/directives/http-error-state-directive.ts +21 -0
- package/src/http-request-state/directives/http-inProgress-state-directive.ts +19 -0
- package/src/http-request-state/directives/http-notStarted-state-directive.ts +19 -0
- package/src/http-request-state/directives/http-success-state-directive.ts +29 -0
- package/src/http-request-state/directives/index.ts +5 -0
- package/src/http-request-state/directives/request-state-directive.spec.ts +73 -0
- package/src/http-request-state/directives/request-state-directive.ts +78 -0
- package/src/http-request-state/documentation/CREATE-REQUESTOR.md +667 -0
- package/src/http-request-state/documentation/README.md +191 -0
- package/src/http-request-state/documentation/REQUEST-STATE-STORE-CONFIG.md +648 -0
- package/src/http-request-state/documentation/REQUESTOR.md +616 -0
- package/src/http-request-state/helpers.ts +30 -0
- package/src/http-request-state/http-state-module.ts +23 -0
- package/src/http-request-state/index.ts +7 -0
- package/src/http-request-state/models/view-context.ts +18 -0
- package/src/http-request-state/observable.spec.ts +43 -0
- package/src/http-request-state/request-state.ts +66 -0
- package/src/http-request-state/rxjs/getRequestorBody.ts +10 -0
- package/src/http-request-state/rxjs/getRequestorState.ts +8 -0
- package/src/http-request-state/rxjs/index.ts +4 -0
- package/src/http-request-state/rxjs/tapError.ts +16 -0
- package/src/http-request-state/rxjs/tapSuccess.ts +16 -0
- package/src/http-request-state/strategies.spec.ts +42 -0
- package/src/http-request-state/types.ts +54 -0
- package/src/ngrx/actionable-selector.ts +189 -0
- package/src/ngrx/index.ts +1 -0
- package/src/public-api.ts +40 -0
- package/src/rxjs/defaultShareReplay.ts +8 -0
- package/src/rxjs/index.ts +5 -0
- package/src/rxjs/mapError.ts +8 -0
- package/src/rxjs/rxjs-operators.ts +130 -0
- package/src/rxjs/subjectifier.ts +17 -0
- package/src/rxjs/subscriber.directive.ts +57 -0
- package/src/specs/clickSubject.spec.ts +99 -0
- package/src/specs/dialog.spec.ts +101 -0
- package/src/specs/toggleGroupDirective.spec.ts +229 -0
- package/src/table-builder/classes/DefaultSettings.ts +11 -0
- package/src/table-builder/classes/MatTableObservableDataSource.ts +23 -0
- package/src/table-builder/classes/TableBuilderConfig.ts +49 -0
- package/src/table-builder/classes/TableBuilderDataSource.ts +64 -0
- package/src/table-builder/classes/TableState.ts +96 -0
- package/src/table-builder/classes/data-store.ts +10 -0
- package/src/table-builder/classes/display-col.ts +5 -0
- package/src/table-builder/classes/filter-info.ts +129 -0
- package/src/table-builder/classes/table-builder-general-settings.ts +233 -0
- package/src/table-builder/classes/table-builder.ts +105 -0
- package/src/table-builder/classes/table-store.helpers.ts +109 -0
- package/src/table-builder/classes/table-store.ts +540 -0
- package/src/table-builder/components/array-column.component.ts +34 -0
- package/src/table-builder/components/column-builder/column-builder.component.html +109 -0
- package/src/table-builder/components/column-builder/column-builder.component.scss +43 -0
- package/src/table-builder/components/column-builder/column-builder.component.spec.ts +49 -0
- package/src/table-builder/components/column-builder/column-builder.component.ts +130 -0
- package/src/table-builder/components/column-builder/column-helpers.ts +54 -0
- package/src/table-builder/components/column-header-menu/column-header-menu.component.html +128 -0
- package/src/table-builder/components/column-header-menu/column-header-menu.component.scss +97 -0
- package/src/table-builder/components/column-header-menu/column-header-menu.component.ts +113 -0
- package/src/table-builder/components/date-filter/date-filter.component.html +39 -0
- package/src/table-builder/components/date-filter/date-filter.component.ts +33 -0
- package/src/table-builder/components/date-time-filter/date-time-filter.component.html +25 -0
- package/src/table-builder/components/date-time-filter/date-time-filter.component.ts +33 -0
- package/src/table-builder/components/filter/filter.component.html +120 -0
- package/src/table-builder/components/filter/filter.component.scss +60 -0
- package/src/table-builder/components/filter/filter.component.spec.ts +86 -0
- package/src/table-builder/components/filter/filter.component.ts +73 -0
- package/src/table-builder/components/filter/in-list/in-list-filter.component.ts +171 -0
- package/src/table-builder/components/gen-col-displayer/gen-col-displayer.component.html +60 -0
- package/src/table-builder/components/gen-col-displayer/gen-col-displayer.component.scss +57 -0
- package/src/table-builder/components/gen-col-displayer/gen-col-displayer.component.ts +44 -0
- package/src/table-builder/components/generic-table/generic-table.component.html +140 -0
- package/src/table-builder/components/generic-table/generic-table.component.scss +45 -0
- package/src/table-builder/components/generic-table/generic-table.component.ts +531 -0
- package/src/table-builder/components/generic-table/paginator.component.ts +125 -0
- package/src/table-builder/components/group-by-list/group-by-list.component.css +24 -0
- package/src/table-builder/components/group-by-list/group-by-list.component.html +21 -0
- package/src/table-builder/components/group-by-list/group-by-list.component.spec.ts +23 -0
- package/src/table-builder/components/group-by-list/group-by-list.component.ts +26 -0
- package/src/table-builder/components/in-filter/in-filter.component.css +22 -0
- package/src/table-builder/components/in-filter/in-filter.component.html +38 -0
- package/src/table-builder/components/in-filter/in-filter.component.ts +66 -0
- package/src/table-builder/components/index.ts +9 -0
- package/src/table-builder/components/initialization-component/initialization.component.html +78 -0
- package/src/table-builder/components/initialization-component/initialization.component.ts +28 -0
- package/src/table-builder/components/link-column.component.ts +42 -0
- package/src/table-builder/components/number-filter/number-filter.component.css +10 -0
- package/src/table-builder/components/number-filter/number-filter.component.html +32 -0
- package/src/table-builder/components/number-filter/number-filter.component.spec.ts +30 -0
- package/src/table-builder/components/number-filter/number-filter.component.ts +34 -0
- package/src/table-builder/components/profiles-menu/profiles-menu.component.html +77 -0
- package/src/table-builder/components/profiles-menu/profiles-menu.component.scss +126 -0
- package/src/table-builder/components/profiles-menu/profiles-menu.component.spec.ts +23 -0
- package/src/table-builder/components/profiles-menu/profiles-menu.component.ts +64 -0
- package/src/table-builder/components/reset-menu/reset-menu.component.css +3 -0
- package/src/table-builder/components/reset-menu/reset-menu.component.html +10 -0
- package/src/table-builder/components/reset-menu/reset-menu.component.ts +87 -0
- package/src/table-builder/components/scroll-strategy.ts +139 -0
- package/src/table-builder/components/sort-menu/sort-menu.component-store.ts +57 -0
- package/src/table-builder/components/sort-menu/sort-menu.component.html +115 -0
- package/src/table-builder/components/sort-menu/sort-menu.component.scss +119 -0
- package/src/table-builder/components/sort-menu/sort-menu.component.ts +88 -0
- package/src/table-builder/components/table-container/table-container.component.html +94 -0
- package/src/table-builder/components/table-container/table-container.component.scss +60 -0
- package/src/table-builder/components/table-container/table-container.component.ts +467 -0
- package/src/table-builder/components/table-container/table-container.helpers/data-state.helpers.ts +113 -0
- package/src/table-builder/components/table-container/table-container.helpers/filter-state.helpers.ts +125 -0
- package/src/table-builder/components/table-container/table-container.helpers/groupBy.helpers.ts +172 -0
- package/src/table-builder/components/table-container/table-container.helpers/meta-data.helpers.ts +19 -0
- package/src/table-builder/components/table-container/table-container.helpers/sort-state.helpers.ts +47 -0
- package/src/table-builder/components/table-container/tableProps.ts +21 -0
- package/src/table-builder/components/table-container/virtual-scroll-container.ts +216 -0
- package/src/table-builder/components/table-container-filter/filter-list/filter-list.component.html +42 -0
- package/src/table-builder/components/table-container-filter/filter-list/filter-list.component.ts +47 -0
- package/src/table-builder/components/table-container-filter/gen-filter-displayer/gen-filter-displayer.component.css +40 -0
- package/src/table-builder/components/table-container-filter/gen-filter-displayer/gen-filter-displayer.component.html +11 -0
- package/src/table-builder/components/table-container-filter/gen-filter-displayer/gen-filter-displayer.component.spec.ts +85 -0
- package/src/table-builder/components/table-container-filter/gen-filter-displayer/gen-filter-displayer.component.ts +35 -0
- package/src/table-builder/components/table-container-filter/table-wrapper-filter-store.ts +13 -0
- package/src/table-builder/components/table-header-menu/table-header-menu.component.css +21 -0
- package/src/table-builder/components/table-header-menu/table-header-menu.component.html +48 -0
- package/src/table-builder/components/table-header-menu/table-header-menu.component.ts +36 -0
- package/src/table-builder/directives/custom-cell-directive.ts +63 -0
- package/src/table-builder/directives/custom-header-directive.ts +16 -0
- package/src/table-builder/directives/group-row-directive.ts +91 -0
- package/src/table-builder/directives/index.ts +8 -0
- package/src/table-builder/directives/multi-sort.directive.spec.ts +124 -0
- package/src/table-builder/directives/multi-sort.directive.ts +58 -0
- package/src/table-builder/directives/resize-column.directive.ts +107 -0
- package/src/table-builder/directives/table-wrapper.directive.ts +13 -0
- package/src/table-builder/directives/tb-filter.directive.ts +376 -0
- package/src/table-builder/documentation/table-builder/CUSTOM-CELL.md +568 -0
- package/src/table-builder/documentation/table-builder/CUSTOM-GROUP-ROW.md +356 -0
- package/src/table-builder/documentation/table-builder/METADATA-DOCUMENTATION.md +517 -0
- package/src/table-builder/documentation/table-builder/STYLER-STYLE.md +228 -0
- package/src/table-builder/documentation/table-builder/TABLE-BUILDER-CONFIG.md +325 -0
- package/src/table-builder/documentation/table-builder/TABLE-BUILDER-SETTINGS.md +515 -0
- package/src/table-builder/documentation/table-builder/TABLE-BUILDER.md +430 -0
- package/src/table-builder/documentation/table-builder/TABLE-CONTAINER.md +628 -0
- package/src/table-builder/enums/filterTypes.ts +39 -0
- package/src/table-builder/functions/boolean-filter-function.ts +12 -0
- package/src/table-builder/functions/date-filter-function.ts +85 -0
- package/src/table-builder/functions/download-data.ts +11 -0
- package/src/table-builder/functions/null-filter-function.ts +9 -0
- package/src/table-builder/functions/number-filter-function.ts +47 -0
- package/src/table-builder/functions/sort-data-function.ts +80 -0
- package/src/table-builder/functions/string-filter-function.ts +59 -0
- package/src/table-builder/interfaces/ColumnInfo.ts +9 -0
- package/src/table-builder/interfaces/dictionary.ts +3 -0
- package/src/table-builder/interfaces/meta-data.ts +279 -0
- package/src/table-builder/ngrx/tableBuilderStateStore.ts +203 -0
- package/src/table-builder/pipes/column-total.pipe.ts +16 -0
- package/src/table-builder/pipes/format-filter-type.pipe.ts +12 -0
- package/src/table-builder/pipes/format-filter-value.pipe.ts +71 -0
- package/src/table-builder/pipes/key-display.ts +13 -0
- package/src/table-builder/services/all-values-filter-creator.service.ts +92 -0
- package/src/table-builder/services/export-to-csv.service.ts +117 -0
- package/src/table-builder/services/link-creator.service.ts +98 -0
- package/src/table-builder/services/table-template-service.ts +47 -0
- package/src/table-builder/services/transform-creator.ts +90 -0
- package/src/table-builder/specs/table-custom-filters.spec.ts +262 -0
- package/src/table-builder/styles/collapser.styles.scss +16 -0
- package/src/table-builder/table-builder.module.ts +42 -0
- package/src/table-builder/types/group-types.ts +42 -0
- package/src/table-builder/types/index.ts +1 -0
- package/src/test.ts +17 -0
- package/src/utilities/array-helpers.ts +13 -0
- package/src/utilities/directives/auto-focus.directive.ts +20 -0
- package/src/utilities/directives/clickEmitterDirective.ts +15 -0
- package/src/utilities/directives/clickSubject.ts +19 -0
- package/src/utilities/directives/conditional-classes.directive.ts +36 -0
- package/src/utilities/directives/dialog-service.ts +19 -0
- package/src/utilities/directives/dialog.ts +174 -0
- package/src/utilities/directives/mat-toggle-group-directive.ts +60 -0
- package/src/utilities/directives/prevent-enter.directive.ts +12 -0
- package/src/utilities/directives/stop-propagation.directive.ts +19 -0
- package/src/utilities/directives/styler.ts +45 -0
- package/src/utilities/directives/trim-whitespace.directive.ts +20 -0
- package/src/utilities/index.ts +22 -0
- package/src/utilities/module.ts +53 -0
- package/src/utilities/pipes/function.pipe.ts +21 -0
- package/src/utilities/pipes/phone.pipe.ts +20 -0
- package/src/utilities/pipes/space-case.pipes.spec.ts +47 -0
- package/src/utilities/pipes/space-case.pipes.ts +29 -0
- package/tsconfig.lib.json +20 -0
- package/tsconfig.lib.prod.json +10 -0
- package/tsconfig.spec.json +17 -0
- package/fesm2022/one-paragon-angular-utilities.mjs +0 -7328
- package/fesm2022/one-paragon-angular-utilities.mjs.map +0 -1
- package/types/one-paragon-angular-utilities.d.ts +0 -2197
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { combineLatest, Observable, of, pipe, Subject, Subscription } from 'rxjs';
|
|
2
|
+
import { RequestFactory as RequestFactory, RequestStrategy as RequestStrategy, RequestStatus, RequestState, RequestStateOptions, RequestStateCancelled } from './types';
|
|
3
|
+
import { createFailure, createSuccess, inProgress, isErrorState, isSuccessOrErrorState, isSuccessState, notStarted } from './helpers';
|
|
4
|
+
import { concatMap, filter, map, mergeMap, startWith, switchMap, takeUntil, tap } from 'rxjs/operators';
|
|
5
|
+
import { HttpErrorResponse } from '@angular/common/http';
|
|
6
|
+
import { mapError } from '../rxjs/mapError';
|
|
7
|
+
import { defaultShareReplay } from '../rxjs/defaultShareReplay';
|
|
8
|
+
import { assertInInjectionContext, computed, DestroyRef, effect, inject, InjectionToken, Injector, isSignal, signal, Signal, untracked } from '@angular/core';
|
|
9
|
+
import { toObservable } from '@angular/core/rxjs-interop';
|
|
10
|
+
import { notNull } from '../rxjs';
|
|
11
|
+
|
|
12
|
+
export interface RequestResponse<TParam extends any[], T> {
|
|
13
|
+
requestParams: [...TParam];
|
|
14
|
+
response: RequestState<T>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class RequestStateStore<TParam extends any[], V, R = null, T = R extends null ? V : R> {
|
|
18
|
+
private _state = signal<RequestResponse<TParam, T>>({ requestParams: null as any, response: notStarted });
|
|
19
|
+
state = this._state.asReadonly();
|
|
20
|
+
private options?: RequestStateOptions;
|
|
21
|
+
private project?: (v: V) => R;
|
|
22
|
+
private req: RequestFactory<TParam, V>;
|
|
23
|
+
private injector = inject(Injector)
|
|
24
|
+
private destroy$ = new Subject<void>();
|
|
25
|
+
private defaultErrorHandling?: (e?: any) => void;
|
|
26
|
+
private _useDefaultErrorHandler?: boolean;
|
|
27
|
+
private defaultSuccessHandling?: (e?: T) => void;
|
|
28
|
+
|
|
29
|
+
constructor(req: RequestFactory<TParam, V>, options?: RequestStateOptions<TParam>, project?: (v: V) => R) {
|
|
30
|
+
this.assertInjectionContext();
|
|
31
|
+
this.injector.get(DestroyRef).onDestroy(() => this.destroy$.next());
|
|
32
|
+
|
|
33
|
+
this.project = project;
|
|
34
|
+
this.options = options;
|
|
35
|
+
this.req = req;
|
|
36
|
+
|
|
37
|
+
const config = inject(RequestStateStoreConfigToken, { optional: true });
|
|
38
|
+
if(config){
|
|
39
|
+
this.defaultErrorHandling = config.defaultErrorHandling;
|
|
40
|
+
this._useDefaultErrorHandler = config.useDefaultErrorHandler;
|
|
41
|
+
this.defaultSuccessHandling = config.defaultSuccessHandling;
|
|
42
|
+
|
|
43
|
+
if(this._useDefaultErrorHandler){
|
|
44
|
+
this.effectOnState('error', e => {
|
|
45
|
+
if(!this.errorHandled){
|
|
46
|
+
this.#onDefaultErrorHandling(e.error);
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.request = (this.options?.strategy === RequestStrategy.singleUse) ? this.singleUseRequest : this.flattenedRequest;
|
|
53
|
+
if(options?.autoRequest){
|
|
54
|
+
(this.request as any)();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
reset() {
|
|
59
|
+
this._state.set({ requestParams: null as any, response: notStarted });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private flatteningStrategy = () => {
|
|
63
|
+
if(this.options?.strategy === RequestStrategy.concurrent)
|
|
64
|
+
return mergeMap( (params: [...TParam]) => this.createRequest(...params));
|
|
65
|
+
|
|
66
|
+
if(this.options?.strategy === RequestStrategy.sequential)
|
|
67
|
+
return concatMap( (params: [...TParam]) => this.createRequest(...params));
|
|
68
|
+
|
|
69
|
+
return switchMap( (params: [...TParam]) => ((params[0] as any) instanceof CancellationToken) ?
|
|
70
|
+
of({ requestParams: params[0], response: { status: RequestStatus.cancelled } as RequestStateCancelled } ) : this.createRequest(...params) );
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private createRequestPipeline = () => {
|
|
74
|
+
const origin$ = new Subject<[...TParam]>();
|
|
75
|
+
const sub = origin$.pipe(
|
|
76
|
+
this.flatteningStrategy(),
|
|
77
|
+
tap<RequestResponse<TParam, T>>(state => this._state.set(state)),
|
|
78
|
+
pipe(takeUntil(this.destroy$))
|
|
79
|
+
).subscribe();
|
|
80
|
+
return (params: [...TParam]) => {
|
|
81
|
+
origin$.next(params);
|
|
82
|
+
return sub;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private requestPipeLine = this.createRequestPipeline();
|
|
87
|
+
|
|
88
|
+
private flattenedRequest = (...value: [...TParam]) => {
|
|
89
|
+
return this.requestPipeLine(value);
|
|
90
|
+
}
|
|
91
|
+
private singleUseRequest = (...value: [...TParam]) => {
|
|
92
|
+
if(this.state().response.status !== RequestStatus.notStarted) {
|
|
93
|
+
throw new Error("state can not be reused. either reset the state by calling reset() or use a state requester that allows concurrent requests.");
|
|
94
|
+
}
|
|
95
|
+
return this.requestPipeLine(value);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
$selectRequestState = computed(() => this.state().response);
|
|
99
|
+
|
|
100
|
+
$selectStatus = computed(() => this.$selectRequestState()?.status);
|
|
101
|
+
$isSuccess = computed(() => isSuccessState(this.$selectRequestState()));
|
|
102
|
+
$isError = computed(() => isErrorState(this.$selectRequestState()));
|
|
103
|
+
$isInProgress = computed(() => this.$selectStatus() === RequestStatus.inProgress);
|
|
104
|
+
$isNotStarted = computed(() => this.$selectStatus() === RequestStatus.notStarted);
|
|
105
|
+
|
|
106
|
+
$selectError = computed(() => {
|
|
107
|
+
const state = this.$selectRequestState();
|
|
108
|
+
if(isErrorState(state)) return state.error
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
$selectResponse = computed(() => {
|
|
112
|
+
const state = this.$selectRequestState();
|
|
113
|
+
if(isSuccessState(state)){
|
|
114
|
+
return state.body
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
errorHandled = false;
|
|
120
|
+
onError( cb: (error: HttpErrorResponse) => void) {
|
|
121
|
+
this.errorHandled = true;
|
|
122
|
+
this.effectOnState('error', cb);
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
onErrorResponse( cb: (error: HttpErrorResponse, ...requestParams: [...TParam]) => void) {
|
|
127
|
+
this.errorHandled = true;
|
|
128
|
+
this.effectOnState('error', (e, p) => cb(e, ...p));
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
onErrorRequest( cb: ( ...requestParams: [...TParam]) => void) {
|
|
133
|
+
this.errorHandled = true;
|
|
134
|
+
this.effectOnState('error', (_, p) => cb(...p));
|
|
135
|
+
return this;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
onErrorWithRequest(
|
|
139
|
+
func: (state: { error: HttpErrorResponse; requestParams: [...TParam]; }) => void
|
|
140
|
+
) {
|
|
141
|
+
this.errorHandled = true;
|
|
142
|
+
this.effectOnState('error', (error, requestParams) => func({ requestParams, error }));
|
|
143
|
+
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#onDefaultErrorHandling = (e: any) => {
|
|
148
|
+
if(this.defaultErrorHandling) this.defaultErrorHandling(e)
|
|
149
|
+
else console.error(e)
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* if no handler was provided will call `console.error`
|
|
153
|
+
*/
|
|
154
|
+
useDefaultErrorHandler(){
|
|
155
|
+
this.effectOnState('error', this.#onDefaultErrorHandling);
|
|
156
|
+
return this;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* if no handler was provided will call `console.log` with 'Success'
|
|
162
|
+
*/
|
|
163
|
+
useDefaultSuccessHandler(){
|
|
164
|
+
this.effectOnState('success', this.defaultSuccessHandling || (() => console.log('Success')));
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
onSuccess( cb: (response?: T) => void) {
|
|
169
|
+
this.effectOnState('success only', cb);
|
|
170
|
+
return this;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
onSuccessResponse( cb: (response: T, ...requestParams: [...TParam]) => void) {
|
|
174
|
+
this.effectOnState('success', (response, requestParams) => cb(response, ...requestParams));
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
onSuccessWithRequest(
|
|
179
|
+
func: (state: { requestParams: [...TParam]; body: T; }) => void
|
|
180
|
+
) {
|
|
181
|
+
this.effectOnState('success', (body, requestParams) => func({ requestParams, body }));
|
|
182
|
+
return this;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
onSuccessRequest( cb: ( ...requestParams: [...TParam]) => void) {
|
|
186
|
+
this.effectOnState('success', (_, requestParams) => cb(...requestParams));
|
|
187
|
+
return this;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
onSuccessOrError(cb: () => void){
|
|
191
|
+
this.effectOnState('both', cb);
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private createRequest(...params: [...TParam]): Observable<RequestResponse<TParam, T>> {
|
|
196
|
+
return this.req(...params).pipe(
|
|
197
|
+
map(re => createSuccess((this.project ? this.project(re) : re) as T)),
|
|
198
|
+
mapError(createFailure),
|
|
199
|
+
startWith(inProgress),
|
|
200
|
+
map( state => ({ requestParams: params, response: state })),
|
|
201
|
+
defaultShareReplay()
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private effectOnState(onState: 'success only', func: (obj: T) => void): void;
|
|
206
|
+
private effectOnState(onState: 'success', func: (obj: T, params: [...TParam]) => void): void;
|
|
207
|
+
private effectOnState(onState: 'error', func: (obj: any, params: [...TParam]) => void): void;
|
|
208
|
+
private effectOnState(onState: 'both', func: () => void): void;
|
|
209
|
+
private effectOnState(
|
|
210
|
+
onState: 'error' | 'success' | 'both' | 'success only',
|
|
211
|
+
func: any): void {
|
|
212
|
+
effect(() => {
|
|
213
|
+
const { requestParams, response } = this.state();
|
|
214
|
+
switch (onState) {
|
|
215
|
+
case 'error':
|
|
216
|
+
if(isErrorState(response)) untracked(() => (func as (obj: any, params?: [...TParam]) => void)(response.error, requestParams));
|
|
217
|
+
break;
|
|
218
|
+
case 'success':
|
|
219
|
+
if(isSuccessState(response)) untracked(() => (func as (obj: T, params?: [...TParam]) => void)(response.body, requestParams));
|
|
220
|
+
break;
|
|
221
|
+
case 'success only':
|
|
222
|
+
if(isSuccessState(response)) untracked(() => (func as (obj: T) => void)(response.body));
|
|
223
|
+
break;
|
|
224
|
+
case 'both':
|
|
225
|
+
if(isSuccessOrErrorState(response)) untracked(() => (func as () => void)());
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}, { injector: this.injector })
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* @deprecated use the standalone subscriber helper function
|
|
233
|
+
*/
|
|
234
|
+
on = <V>(srcObservable: Observable<V>, func: (obj: V) => void): Subscription => {
|
|
235
|
+
return srcObservable.pipe(tap(func), takeUntil(this.destroy$)).subscribe();
|
|
236
|
+
}
|
|
237
|
+
request: (...value: [...TParam] ) => Subscription;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* @param params An observable or signal that returns an array to be spread into the request function.
|
|
241
|
+
* If undefined is returned, the request will not be made.
|
|
242
|
+
*/
|
|
243
|
+
requestWith = (params: Observable<[...TParam]> | Signal<[...TParam]>) => {
|
|
244
|
+
if(isSignal(params)){
|
|
245
|
+
params = toObservable(params, { injector: this.injector }).pipe(notNull());
|
|
246
|
+
}
|
|
247
|
+
params.pipe(takeUntil(this.destroy$)).subscribe((p) => this.request(...p));
|
|
248
|
+
return this;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @param params The parameters to be passed to the request function.
|
|
253
|
+
* The parameters can be either actual values or wrapped in a signal.
|
|
254
|
+
* If any of the parameters are a signal, it will be unwrapped and passed to the request.
|
|
255
|
+
* It will be tracked and the request will be made whenever its value changes.
|
|
256
|
+
* If any of the signals return undefined, the request will not be made.
|
|
257
|
+
*/
|
|
258
|
+
requestOn = (...params: SignalOrValArr<TParam>) => {
|
|
259
|
+
const paramsArr = computed(() => {
|
|
260
|
+
const p = params.map(param =>
|
|
261
|
+
isSignal(param)
|
|
262
|
+
? ({ param, isSignal: true })
|
|
263
|
+
: ({ param, isSignal: false })
|
|
264
|
+
);
|
|
265
|
+
const allVals = p.every(({ param, isSignal }) => !isSignal || param() != undefined);
|
|
266
|
+
if(allVals) return p.map(({ param, isSignal }) => isSignal ? param() : param);
|
|
267
|
+
return undefined;
|
|
268
|
+
});
|
|
269
|
+
effect(() => {
|
|
270
|
+
const vals = paramsArr();
|
|
271
|
+
untracked(() => {
|
|
272
|
+
if(vals) this.request(...vals as [...TParam]);
|
|
273
|
+
});
|
|
274
|
+
}, { injector: this.injector });
|
|
275
|
+
return this;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
private assertInjectionContext(){
|
|
279
|
+
try {
|
|
280
|
+
assertInInjectionContext(RequestStateStore);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
throw new Error('RequestStateStore must be created in an Injection Context');
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
destroy() {
|
|
287
|
+
this.destroy$.next();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
state$ = toObservable(this.state);
|
|
291
|
+
selectRequestState$ = this.state$.pipe(map(s => s.response));
|
|
292
|
+
selectResponse$: Observable<T> = this.selectRequestState$.pipe(
|
|
293
|
+
filter(isSuccessState),
|
|
294
|
+
map( state => state.body)
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* @deprecated use selectRequestState$ instead
|
|
299
|
+
*/
|
|
300
|
+
selectHttpState$ = this.selectRequestState$;
|
|
301
|
+
selectStatus$ = this.selectRequestState$.pipe(map(s => s.status));
|
|
302
|
+
selectError$ = this.selectRequestState$.pipe(
|
|
303
|
+
filter(isErrorState),
|
|
304
|
+
map(state => state.error)
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
refresh(){
|
|
308
|
+
if(this.$isNotStarted()) {
|
|
309
|
+
throw new Error("Cannot refresh a request that has not been started. Call request() first.");
|
|
310
|
+
}
|
|
311
|
+
if(this.$isError()){
|
|
312
|
+
throw new Error("Cannot refresh a request that is in error state. Call reset() and request() first.");
|
|
313
|
+
}
|
|
314
|
+
const params = this.state().requestParams;
|
|
315
|
+
this.request(...params);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
export class CancellationToken {}
|
|
319
|
+
|
|
320
|
+
export function wrapInArr<T extends [Signal<any>, ...Signal<any>[]]>(...sigs: T) : Signal<{ [K in keyof T]: T[K] extends Signal<infer U> ? U : never }>;
|
|
321
|
+
export function wrapInArr<T extends [Observable<any>, ...Observable<any>[]]>(...obs: T): Observable<{ [K in keyof T]: T[K] extends Observable<infer U> ? U : never }>;
|
|
322
|
+
/**
|
|
323
|
+
* @param sigOrObs An array of signals or observables to be wrapped in an array to be spread into the request function.
|
|
324
|
+
* If any values from the array of signals or observables are null or undefined, all values will be filtered
|
|
325
|
+
*/
|
|
326
|
+
export function wrapInArr<T extends Signal<any>[] | Observable<any>[]>(...sigOrObs: T){
|
|
327
|
+
if(isSignal(sigOrObs[0])){
|
|
328
|
+
return computed(() => {
|
|
329
|
+
const arr = (sigOrObs as Signal<T>[]).map(s => s()) as { [K in keyof T]: T[K] extends Signal<infer U> ? U : never };
|
|
330
|
+
if(arr.some(s => s == undefined)) return undefined;
|
|
331
|
+
return arr;
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
return combineLatest(sigOrObs as Observable<any>[]).pipe(filter(vals => vals.every(val => val != undefined))) as Observable<{ [K in keyof T]: T[K] extends Observable<infer U> ? U : never }>;
|
|
336
|
+
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export interface RequestStateStoreConfig {
|
|
341
|
+
/**
|
|
342
|
+
* if `useDefaultErrorHandler` is `true` this will run on each error if no other handler provided.
|
|
343
|
+
* else can be used by call `.useDefaultHandler`
|
|
344
|
+
*/
|
|
345
|
+
defaultErrorHandling?: (e?: any) => void;
|
|
346
|
+
/**
|
|
347
|
+
* if `useDefaultErrorHandler` is `true` `defaultErrorHandling` will run on each error if no other handler provided.
|
|
348
|
+
*/
|
|
349
|
+
useDefaultErrorHandler?: boolean;
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* will be called if `useDefaultSuccess` is used
|
|
353
|
+
*/
|
|
354
|
+
defaultSuccessHandling?: <T>(e?: T) => void;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export const RequestStateStoreConfigToken = new InjectionToken<RequestStateStoreConfig>('RequestStateConfig');
|
|
358
|
+
|
|
359
|
+
type SignalOrVal<T> = Signal<T> | T;
|
|
360
|
+
type SignalOrValArr<T extends readonly any[]> = { [K in keyof T]: SignalOrVal<T[K]> };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated use RequestStateFactory
|
|
3
|
+
*/
|
|
4
|
+
export { RequestStateFactory as HttpRequestStateFactory } from './RequestStateFactory';
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated use RequestStatus
|
|
7
|
+
*/
|
|
8
|
+
export { RequestStatus as HttpRequestStatus } from './types';
|
|
9
|
+
/**
|
|
10
|
+
* @deprecated use RequestState
|
|
11
|
+
*/
|
|
12
|
+
export type { RequestState as HttpRequestState } from './types'
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated use RequestStateStore
|
|
15
|
+
*/
|
|
16
|
+
export { RequestStateStore as HttpRequestStateStore } from './RequestStateStore';
|
|
17
|
+
/**
|
|
18
|
+
* @deprecated use RequestStrategy
|
|
19
|
+
*/
|
|
20
|
+
export { RequestStrategy as HttpRequestStrategy } from './types';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Directive, inject, Injector, TemplateRef, ViewContainerRef, OnInit } from '@angular/core';
|
|
2
|
+
import { RequestState } from '../types';
|
|
3
|
+
import { HttpRequestStateDirective } from './request-state-directive';
|
|
4
|
+
|
|
5
|
+
@Directive()
|
|
6
|
+
export abstract class HttpStateDirectiveBase<V, R = null> implements OnInit {
|
|
7
|
+
hasView = false;
|
|
8
|
+
private injector = inject(Injector);
|
|
9
|
+
protected templateRef = inject(TemplateRef<{ $implicit: R extends null ? V : R }>);
|
|
10
|
+
protected viewContainer = inject(ViewContainerRef);
|
|
11
|
+
ngOnInit() {
|
|
12
|
+
const parent = this.injector.get(HttpRequestStateDirective);
|
|
13
|
+
if(parent == null) {
|
|
14
|
+
throw new Error('You can only use an http state directive as a child of the httpRequestState directive.');
|
|
15
|
+
}
|
|
16
|
+
parent.hooks.push(this.baseRender);
|
|
17
|
+
this.baseRender(parent.ViewContext.state as RequestState);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private baseRender = (state: RequestState): void => {
|
|
21
|
+
if(this.hasView) {
|
|
22
|
+
this.viewContainer.clear();
|
|
23
|
+
this.hasView = false;
|
|
24
|
+
}
|
|
25
|
+
this.hasView = this.render(state);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
abstract render: (state: RequestState) => boolean;
|
|
29
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Directive, OnInit } from '@angular/core';
|
|
2
|
+
import { isErrorState } from '../helpers';
|
|
3
|
+
import { RequestState } from '../types';
|
|
4
|
+
import { HttpStateDirectiveBase } from './HttpStateDirectiveBase';
|
|
5
|
+
|
|
6
|
+
@Directive({ selector: '[httpErrorState]', })
|
|
7
|
+
export class HttpErrorStateDirective<V, R = null> extends HttpStateDirectiveBase<V, R> implements OnInit {
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
render = (state: RequestState<any>) => {
|
|
11
|
+
if(isErrorState(state)) {
|
|
12
|
+
this.viewContainer.createEmbeddedView(this.templateRef, { $implicit: state.error });
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
ngOnInit() {
|
|
19
|
+
super.ngOnInit();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Directive, OnInit } from '@angular/core';
|
|
2
|
+
import { HttpStateDirectiveBase } from './HttpStateDirectiveBase';
|
|
3
|
+
import { RequestState, RequestStatus } from '../types'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@Directive({ selector: '[httpInProgressState]', })
|
|
7
|
+
export class HttpInProgressStateDirective<V, R = null> extends HttpStateDirectiveBase<V, R> implements OnInit {
|
|
8
|
+
render = (state: RequestState<any>) => {
|
|
9
|
+
if(state.status === RequestStatus.inProgress){
|
|
10
|
+
this.viewContainer.createEmbeddedView(this.templateRef );
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
ngOnInit() {
|
|
17
|
+
super.ngOnInit();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Directive, OnInit } from '@angular/core';
|
|
2
|
+
import { RequestState, RequestStatus } from '../types';
|
|
3
|
+
import { HttpStateDirectiveBase } from './HttpStateDirectiveBase';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@Directive({ selector: '[httpNotStartedState]', })
|
|
7
|
+
export class HttpNotStartedStateDirective<V, R = null> extends HttpStateDirectiveBase<V, R> implements OnInit {
|
|
8
|
+
render = (state: RequestState<any>) => {
|
|
9
|
+
if(state.status === RequestStatus.notStarted){
|
|
10
|
+
this.viewContainer.createEmbeddedView(this.templateRef );
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
ngOnInit() {
|
|
17
|
+
super.ngOnInit();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Directive, Input, OnInit } from '@angular/core';
|
|
2
|
+
import { isSuccessState } from '../helpers';
|
|
3
|
+
import { HttpStateDirectiveBase } from './HttpStateDirectiveBase';
|
|
4
|
+
import { RequestState } from '../types';
|
|
5
|
+
import { RequestStateStore } from '../RequestStateStore';
|
|
6
|
+
import { Observable } from 'rxjs';
|
|
7
|
+
|
|
8
|
+
export interface HttpSuccessStateViewContext<T> {
|
|
9
|
+
$implicit: T;
|
|
10
|
+
}
|
|
11
|
+
@Directive({ selector: '[httpSuccessState]', })
|
|
12
|
+
export class HttpSuccessStateDirective<V, R = null> extends HttpStateDirectiveBase<V, R> implements OnInit {
|
|
13
|
+
@Input() httpSuccessStateTypeSafety?: RequestStateStore<any, V, R> | Observable<RequestState<V>>;
|
|
14
|
+
render = (state: RequestState<R extends null ? V : R>) => {
|
|
15
|
+
if(isSuccessState(state)) {
|
|
16
|
+
this.viewContainer.createEmbeddedView(this.templateRef, { $implicit: state.body });
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
ngOnInit() {
|
|
23
|
+
super.ngOnInit();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static ngTemplateContextGuard<V, R>(dir: HttpSuccessStateDirective<V, R>, ctx: any): ctx is HttpSuccessStateViewContext<R extends null ? V : R> {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
import { Component, inject } from '@angular/core';
|
|
3
|
+
import { HttpRequestModule } from '../http-state-module';
|
|
4
|
+
import { RequestStateStore } from '../RequestStateStore';
|
|
5
|
+
import { Subject } from 'rxjs';
|
|
6
|
+
import { RequestStateOptions } from '../types';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@Component({
|
|
10
|
+
template: `
|
|
11
|
+
<div *httpRequestState='requestState;let state;'>
|
|
12
|
+
<ng-container *httpNotStartedState>not started</ng-container>
|
|
13
|
+
<ng-container *httpInProgressState>in progress</ng-container>
|
|
14
|
+
<ng-container *httpSuccessState='let body' >success {{body}}</ng-container>
|
|
15
|
+
<ng-container *httpErrorState='let error' >error {{error}}</ng-container>
|
|
16
|
+
</div>
|
|
17
|
+
`,
|
|
18
|
+
}) export class TestHttpRequestStateDirectiveComponent {
|
|
19
|
+
requestState = inject<RequestStateStore<any, any>>(RequestStateStore);
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class MockHttpRequestState<T> extends RequestStateStore<[], T> {
|
|
24
|
+
subject = new Subject<T>();
|
|
25
|
+
constructor(options?: RequestStateOptions) {
|
|
26
|
+
super(() => {
|
|
27
|
+
return this.subject;
|
|
28
|
+
}, options );
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let requestState: MockHttpRequestState<string>;
|
|
33
|
+
let fixture: ComponentFixture<TestHttpRequestStateDirectiveComponent>;
|
|
34
|
+
|
|
35
|
+
describe('Request State Directive', () => {
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
requestState = new MockHttpRequestState<string>();
|
|
39
|
+
fixture = TestBed.configureTestingModule({
|
|
40
|
+
declarations: [TestHttpRequestStateDirectiveComponent],
|
|
41
|
+
providers: [{ provide: RequestStateStore, useValue: requestState }],
|
|
42
|
+
imports: [HttpRequestModule]
|
|
43
|
+
}).createComponent(TestHttpRequestStateDirectiveComponent);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should begin with the state of not started', () => {
|
|
47
|
+
verifyText('not started');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should be in progress while in progress', () => {
|
|
51
|
+
requestState.request();
|
|
52
|
+
verifyText('in progress');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should be success when success', () => {
|
|
56
|
+
requestState.request();
|
|
57
|
+
requestState.subject.next('yay')
|
|
58
|
+
verifyText('success yay');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should be error when error', () => {
|
|
62
|
+
requestState.request();
|
|
63
|
+
requestState.subject.error('ouch');
|
|
64
|
+
verifyText('error ouch');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
function verifyText(text:string) {
|
|
68
|
+
fixture.detectChanges();
|
|
69
|
+
const node: HTMLElement = fixture.nativeElement;
|
|
70
|
+
expect(node.innerText).toBe(text);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Directive, Input, TemplateRef, ViewContainerRef, OnDestroy } from '@angular/core';
|
|
2
|
+
import { defaultShareReplay } from '../../rxjs/defaultShareReplay';
|
|
3
|
+
import { isObservable, Observable, Subject, Unsubscribable } from 'rxjs';
|
|
4
|
+
import { map, switchAll } from 'rxjs/operators';
|
|
5
|
+
import { RequestStateStore } from '../RequestStateStore';
|
|
6
|
+
import { RequestState, RequestStatus } from '../types';
|
|
7
|
+
|
|
8
|
+
export interface HttpRequestStateViewContext<T> {
|
|
9
|
+
$implicit?: HttpRequestStateAny<T>;
|
|
10
|
+
state?: HttpRequestStateAny<T>;
|
|
11
|
+
status: {
|
|
12
|
+
inProgress: boolean;
|
|
13
|
+
notStarted: boolean;
|
|
14
|
+
success: boolean;
|
|
15
|
+
error: boolean;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@Directive({ selector: '[httpRequestState]', })
|
|
20
|
+
export class HttpRequestStateDirective<TParam extends any[], V, R, T = R extends null ? V : R> implements OnDestroy {
|
|
21
|
+
private viewContainer: ViewContainerRef;
|
|
22
|
+
|
|
23
|
+
readonly ViewContext: HttpRequestStateViewContext<T> = {
|
|
24
|
+
$implicit: undefined,
|
|
25
|
+
state: undefined,
|
|
26
|
+
status: {
|
|
27
|
+
inProgress: false,
|
|
28
|
+
notStarted: true,
|
|
29
|
+
success: false,
|
|
30
|
+
error: false,
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
subject = new Subject<Observable<RequestState<T>>>();
|
|
36
|
+
state = this.subject.pipe(switchAll(), defaultShareReplay());
|
|
37
|
+
subscription: Unsubscribable;
|
|
38
|
+
hooks: ((state: RequestState<T>) => void)[] = [];
|
|
39
|
+
|
|
40
|
+
@Input('httpRequestState') set stateStore(store: RequestStateStore<TParam, V, R, T> | Observable<RequestState<T>>) {
|
|
41
|
+
if(isObservable(store)) {
|
|
42
|
+
this.subject.next(store);
|
|
43
|
+
} else {
|
|
44
|
+
this.subject.next(store.state$.pipe(map(state => state.response)));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
constructor(templateRef: TemplateRef<any>, viewContainer: ViewContainerRef) {
|
|
49
|
+
this.viewContainer = viewContainer;
|
|
50
|
+
this.viewContainer.createEmbeddedView(templateRef, this.ViewContext);
|
|
51
|
+
this.subscription = this.state.subscribe(state => {
|
|
52
|
+
this.ViewContext.$implicit = state;
|
|
53
|
+
this.ViewContext.state = state;
|
|
54
|
+
this.ViewContext.status.inProgress = state.status === RequestStatus.inProgress;
|
|
55
|
+
this.ViewContext.status.notStarted = state.status === RequestStatus.notStarted;
|
|
56
|
+
this.ViewContext.status.success = state.status === RequestStatus.success;
|
|
57
|
+
this.ViewContext.status.error = state.status === RequestStatus.fail;
|
|
58
|
+
this.hooks.forEach(hook => hook(state));
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
ngOnDestroy() {
|
|
64
|
+
this.viewContainer.clear();
|
|
65
|
+
this.subscription.unsubscribe();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static ngTemplateContextGuard<V, R>(dir: HttpRequestStateDirective<any, V, R>, ctx: any): ctx is HttpRequestStateViewContext<R extends null ? V : R> {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type HttpRequestStateAny<T> = {
|
|
75
|
+
status: RequestStatus;
|
|
76
|
+
body?: T;
|
|
77
|
+
error?: any;
|
|
78
|
+
}
|