@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,667 @@
|
|
|
1
|
+
# createRequestor Function Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`createRequestor` is a standalone function that creates a `RequestStateStore` instance (requestor) for managing HTTP request state in Angular applications. It provides a convenient way to create requestors outside of the Angular dependency injection context, making it ideal for use in components, services, and other contexts.
|
|
6
|
+
|
|
7
|
+
## Function Signature
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// Basic usage - no projection
|
|
11
|
+
function createRequestor<TParam extends any[], V>(
|
|
12
|
+
req: RequestFactory<TParam, V>,
|
|
13
|
+
options?: RequestCreatorOptions<TParam>
|
|
14
|
+
): RequestStateStore<TParam, V>
|
|
15
|
+
|
|
16
|
+
// With projection - transforms the response
|
|
17
|
+
function createRequestor<TParam extends any[], V, R, T = R>(
|
|
18
|
+
req: RequestFactory<TParam, V>,
|
|
19
|
+
project: (v: V) => R,
|
|
20
|
+
options?: RequestCreatorOptions<TParam>
|
|
21
|
+
): RequestStateStore<TParam, V, T>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Type Parameters
|
|
25
|
+
|
|
26
|
+
- **`TParam`**: Array type representing the parameters the request function accepts (e.g., `[number]`, `[string, number]`)
|
|
27
|
+
- **`V`**: The raw response type returned by the HTTP request (e.g., `UserDTO`)
|
|
28
|
+
- **`R`**: The projected/transformed response type (only when using projection)
|
|
29
|
+
- **`T`**: The final type stored in state (defaults to `V` without projection, or `R` with projection)
|
|
30
|
+
|
|
31
|
+
### Parameters
|
|
32
|
+
|
|
33
|
+
- **`req`**: A function that takes parameters and returns an `Observable`. Typically an HTTP request.
|
|
34
|
+
```typescript
|
|
35
|
+
RequestFactory<TParam, V> = (...params: [...TParam]) => Observable<V>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- **`project`** _(optional)_: A transformation function to map the raw response to a different type.
|
|
39
|
+
```typescript
|
|
40
|
+
(v: V) => R
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- **`options`** _(optional)_: Configuration options for the requestor.
|
|
44
|
+
```typescript
|
|
45
|
+
interface RequestCreatorOptions<TParam> {
|
|
46
|
+
strategy?: RequestStrategy;
|
|
47
|
+
autoRequest?: boolean; // Only if TParam is empty array
|
|
48
|
+
injector?: Injector; // Provide custom injector
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Return Value
|
|
53
|
+
|
|
54
|
+
Returns a `RequestStateStore<TParam, V, R, T>` instance configured with the provided request function and options.
|
|
55
|
+
|
|
56
|
+
## Injection Context
|
|
57
|
+
|
|
58
|
+
`createRequestor` must be called within an Angular injection context OR you must provide an `injector` in the options.
|
|
59
|
+
|
|
60
|
+
### Valid Contexts
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// ✅ In component/service constructor
|
|
64
|
+
export class UserComponent {
|
|
65
|
+
constructor() {
|
|
66
|
+
this.userRequestor = createRequestor(
|
|
67
|
+
(id: number) => this.http.get<User>(`/api/users/${id}`)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ✅ In component/service field initializer
|
|
73
|
+
export class UserComponent {
|
|
74
|
+
private http = inject(HttpClient);
|
|
75
|
+
|
|
76
|
+
userRequestor = createRequestor(
|
|
77
|
+
(id: number) => this.http.get<User>(`/api/users/${id}`)
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ✅ With custom injector
|
|
82
|
+
export class UserComponent {
|
|
83
|
+
private injector = inject(Injector);
|
|
84
|
+
|
|
85
|
+
createUserRequestor() {
|
|
86
|
+
return createRequestor(
|
|
87
|
+
(id: number) => this.http.get<User>(`/api/users/${id}`),
|
|
88
|
+
{ injector: this.injector }
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ✅ In runInInjectionContext
|
|
94
|
+
export class UserComponent {
|
|
95
|
+
private injector = inject(Injector);
|
|
96
|
+
|
|
97
|
+
createLater() {
|
|
98
|
+
runInInjectionContext(this.injector, () => {
|
|
99
|
+
return createRequestor(
|
|
100
|
+
(id: number) => this.http.get<User>(`/api/users/${id}`)
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Invalid Contexts
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// ❌ Outside injection context without injector
|
|
111
|
+
function createUserRequestor() {
|
|
112
|
+
// Error: createRequestor() must be used in an Injection Context
|
|
113
|
+
return createRequestor(
|
|
114
|
+
(id: number) => inject(HttpClient).get<User>(`/api/users/${id}`)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Basic Usage
|
|
120
|
+
|
|
121
|
+
### Simple Request (No Parameters)
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { createRequestor } from 'angular-utilities';
|
|
125
|
+
import { inject } from '@angular/core';
|
|
126
|
+
import { HttpClient } from '@angular/common/http';
|
|
127
|
+
|
|
128
|
+
export class ConfigService {
|
|
129
|
+
private http = inject(HttpClient);
|
|
130
|
+
|
|
131
|
+
// Request with no parameters
|
|
132
|
+
configRequestor = createRequestor(
|
|
133
|
+
() => this.http.get<AppConfig>('/api/config')
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
loadConfig() {
|
|
137
|
+
this.configRequestor.request();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Request with Single Parameter
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
export class UserService {
|
|
146
|
+
private http = inject(HttpClient);
|
|
147
|
+
|
|
148
|
+
userRequestor = createRequestor(
|
|
149
|
+
(userId: number) => this.http.get<User>(`/api/users/${userId}`)
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
loadUser(id: number) {
|
|
153
|
+
this.userRequestor.request(id);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Request with Multiple Parameters
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
export class SearchService {
|
|
162
|
+
private http = inject(HttpClient);
|
|
163
|
+
|
|
164
|
+
searchRequestor = createRequestor(
|
|
165
|
+
(query: string, page: number, pageSize: number) =>
|
|
166
|
+
this.http.get<SearchResults>(
|
|
167
|
+
`/api/search?q=${query}&page=${page}&size=${pageSize}`
|
|
168
|
+
)
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
search(query: string, page: number = 1, pageSize: number = 20) {
|
|
172
|
+
this.searchRequestor.request(query, page, pageSize);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## With Projection
|
|
178
|
+
|
|
179
|
+
Projection allows you to transform the response before it's stored in state.
|
|
180
|
+
|
|
181
|
+
### Basic Projection
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
interface UserDTO {
|
|
185
|
+
user_id: number;
|
|
186
|
+
first_name: string;
|
|
187
|
+
last_name: string;
|
|
188
|
+
email_address: string;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
interface User {
|
|
192
|
+
id: number;
|
|
193
|
+
name: string;
|
|
194
|
+
email: string;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export class UserService {
|
|
198
|
+
private http = inject(HttpClient);
|
|
199
|
+
|
|
200
|
+
userRequestor = createRequestor(
|
|
201
|
+
(id: number) => this.http.get<UserDTO>(`/api/users/${id}`),
|
|
202
|
+
(dto: UserDTO): User => ({
|
|
203
|
+
id: dto.user_id,
|
|
204
|
+
name: `${dto.first_name} ${dto.last_name}`,
|
|
205
|
+
email: dto.email_address
|
|
206
|
+
})
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Projection with Options
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
userRequestor = createRequestor(
|
|
215
|
+
(id: number) => this.http.get<UserDTO>(`/api/users/${id}`),
|
|
216
|
+
(dto: UserDTO): User => this.transformUser(dto),
|
|
217
|
+
{ strategy: RequestStrategy.cancelPrevious }
|
|
218
|
+
);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Request Strategies
|
|
222
|
+
|
|
223
|
+
### Cancel Previous (Default)
|
|
224
|
+
|
|
225
|
+
Cancels in-flight requests when a new request is made. Ideal for search/autocomplete.
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
const searchRequestor = createRequestor(
|
|
229
|
+
(query: string) => this.http.get(`/api/search?q=${query}`),
|
|
230
|
+
{ strategy: RequestStrategy.cancelPrevious }
|
|
231
|
+
);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Concurrent
|
|
235
|
+
|
|
236
|
+
Allows multiple requests to execute simultaneously.
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
const userRequestor = createRequestor(
|
|
240
|
+
(userId: number) => this.http.get(`/api/users/${userId}`),
|
|
241
|
+
{ strategy: RequestStrategy.concurrent }
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
// All requests execute in parallel
|
|
245
|
+
userRequestor.request(1);
|
|
246
|
+
userRequestor.request(2);
|
|
247
|
+
userRequestor.request(3);
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Sequential
|
|
251
|
+
|
|
252
|
+
Queues requests and executes them one at a time in order.
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
const uploadRequestor = createRequestor(
|
|
256
|
+
(file: File) => this.http.post('/api/upload', file),
|
|
257
|
+
{ strategy: RequestStrategy.sequential }
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
// Requests execute in order
|
|
261
|
+
uploadRequestor.request(file1);
|
|
262
|
+
uploadRequestor.request(file2); // Waits for file1
|
|
263
|
+
uploadRequestor.request(file3); // Waits for file2
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Single Use
|
|
267
|
+
|
|
268
|
+
Only allows one request for the lifetime of the requestor. Must call `reset()` to make another request.
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
const initRequestor = createRequestor(
|
|
272
|
+
() => this.http.post('/api/initialize', {}),
|
|
273
|
+
{ strategy: RequestStrategy.singleUse }
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
initRequestor.request(); // OK
|
|
277
|
+
initRequestor.request(); // Error!
|
|
278
|
+
initRequestor.reset(); // Reset state
|
|
279
|
+
initRequestor.request(); // OK again
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Auto Request
|
|
283
|
+
|
|
284
|
+
Automatically execute the request when the requestor is created. Only available for requests with no parameters.
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
const configRequestor = createRequestor(
|
|
288
|
+
() => this.http.get<Config>('/api/config'),
|
|
289
|
+
{ autoRequest: true }
|
|
290
|
+
);
|
|
291
|
+
// Request executes immediately upon creation
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Custom Injector
|
|
295
|
+
|
|
296
|
+
Provide a custom injector when creating the requestor outside an injection context.
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
export class DynamicRequestService {
|
|
300
|
+
private injector = inject(Injector);
|
|
301
|
+
|
|
302
|
+
createDynamicRequestor(endpoint: string) {
|
|
303
|
+
// Create requestor outside injection context
|
|
304
|
+
return createRequestor(
|
|
305
|
+
() => this.http.get(endpoint),
|
|
306
|
+
{ injector: this.injector }
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Comparison with RequestStateFactory
|
|
313
|
+
|
|
314
|
+
### Using `createRequestor` (Recommended)
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
export class UserComponent {
|
|
318
|
+
private http = inject(HttpClient);
|
|
319
|
+
|
|
320
|
+
// Direct creation - cleaner and more modern
|
|
321
|
+
userRequestor = createRequestor(
|
|
322
|
+
(id: number) => this.http.get<User>(`/api/users/${id}`)
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Using `RequestStateFactory`
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
@Component({
|
|
331
|
+
providers: [RequestStateFactory] // Must provide in component/module
|
|
332
|
+
})
|
|
333
|
+
export class UserComponent {
|
|
334
|
+
private factory = inject(RequestStateFactory);
|
|
335
|
+
private http = inject(HttpClient);
|
|
336
|
+
|
|
337
|
+
// Factory method - requires provider
|
|
338
|
+
userRequestor = this.factory.create(
|
|
339
|
+
(id: number) => this.http.get<User>(`/api/users/${id}`)
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**Benefits of `createRequestor`:**
|
|
345
|
+
- No need to provide `RequestStateFactory` in component/module
|
|
346
|
+
- More concise and functional
|
|
347
|
+
- Better tree-shaking
|
|
348
|
+
- Easier to test
|
|
349
|
+
|
|
350
|
+
**When to use `RequestStateFactory`:**
|
|
351
|
+
- Need centralized lifecycle management across multiple requestors
|
|
352
|
+
- Want to batch cleanup operations
|
|
353
|
+
- Working with older Angular patterns
|
|
354
|
+
|
|
355
|
+
## Common Patterns
|
|
356
|
+
|
|
357
|
+
### Component with Master-Detail
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
@Component({
|
|
361
|
+
selector: 'app-user-detail',
|
|
362
|
+
template: `
|
|
363
|
+
@if (user.$isInProgress()) {
|
|
364
|
+
<mat-spinner />
|
|
365
|
+
} @else if (user.$isSuccess()) {
|
|
366
|
+
<h1>{{ user.$selectResponse()?.name }}</h1>
|
|
367
|
+
<p>{{ user.$selectResponse()?.email }}</p>
|
|
368
|
+
}
|
|
369
|
+
`
|
|
370
|
+
})
|
|
371
|
+
export class UserDetailComponent {
|
|
372
|
+
private http = inject(HttpClient);
|
|
373
|
+
private route = inject(ActivatedRoute);
|
|
374
|
+
|
|
375
|
+
userId = toSignal(this.route.params.pipe(map(p => +p['id'])));
|
|
376
|
+
userIdArray = computed(() => [this.userId()]);
|
|
377
|
+
|
|
378
|
+
user = createRequestor(
|
|
379
|
+
(id: number) => this.http.get<User>(`/api/users/${id}`)
|
|
380
|
+
).requestWith(this.userIdArray);
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Search with Debounce
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
@Component({
|
|
388
|
+
selector: 'app-search',
|
|
389
|
+
template: `
|
|
390
|
+
<input [ngModel]="searchTerm()" (ngModelChange)="searchTerm.set($event)" />
|
|
391
|
+
|
|
392
|
+
@if (results.$isSuccess()) {
|
|
393
|
+
@for (item of results.$selectResponse(); track item.id) {
|
|
394
|
+
<div>{{ item.name }}</div>
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
`
|
|
398
|
+
})
|
|
399
|
+
export class SearchComponent {
|
|
400
|
+
private http = inject(HttpClient);
|
|
401
|
+
|
|
402
|
+
searchTerm = signal('');
|
|
403
|
+
|
|
404
|
+
results = createRequestor(
|
|
405
|
+
(term: string) => this.http.get<Item[]>(`/api/search?q=${term}`),
|
|
406
|
+
{ strategy: RequestStrategy.cancelPrevious }
|
|
407
|
+
).requestWith(
|
|
408
|
+
toObservable(this.searchTerm).pipe(
|
|
409
|
+
debounceTime(300),
|
|
410
|
+
filter(term => term.length >= 3),
|
|
411
|
+
map(term => [term])
|
|
412
|
+
)
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Form Submission with Error Handling
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
@Component({
|
|
421
|
+
selector: 'app-user-form',
|
|
422
|
+
template: `
|
|
423
|
+
<form (ngSubmit)="onSubmit()">
|
|
424
|
+
<!-- form fields -->
|
|
425
|
+
<button type="submit" [disabled]="saveUser.$isInProgress()">
|
|
426
|
+
@if (saveUser.$isInProgress()) {
|
|
427
|
+
Saving...
|
|
428
|
+
} @else {
|
|
429
|
+
Save User
|
|
430
|
+
}
|
|
431
|
+
</button>
|
|
432
|
+
</form>
|
|
433
|
+
`
|
|
434
|
+
})
|
|
435
|
+
export class UserFormComponent {
|
|
436
|
+
private http = inject(HttpClient);
|
|
437
|
+
private router = inject(Router);
|
|
438
|
+
private snackBar = inject(MatSnackBar);
|
|
439
|
+
|
|
440
|
+
saveUser = createRequestor(
|
|
441
|
+
(user: User) => this.http.post<User>('/api/users', user)
|
|
442
|
+
)
|
|
443
|
+
.onSuccess((savedUser) => {
|
|
444
|
+
this.snackBar.open('User saved successfully', 'Close', { duration: 3000 });
|
|
445
|
+
this.router.navigate(['/users', savedUser.id]);
|
|
446
|
+
})
|
|
447
|
+
.onError((error) => {
|
|
448
|
+
this.snackBar.open(`Error: ${error.message}`, 'Close', { duration: 5000 });
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
onSubmit() {
|
|
452
|
+
if (this.form.valid) {
|
|
453
|
+
this.saveUser.request(this.form.value);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Multiple Dependent Requests
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
export class UserDashboardComponent {
|
|
463
|
+
private http = inject(HttpClient);
|
|
464
|
+
|
|
465
|
+
userId = signal(1);
|
|
466
|
+
userIdArray = computed(() => [this.userId()]);
|
|
467
|
+
|
|
468
|
+
// First request
|
|
469
|
+
user = createRequestor(
|
|
470
|
+
(id: number) => this.http.get<User>(`/api/users/${id}`)
|
|
471
|
+
).requestWith(this.userIdArray);
|
|
472
|
+
|
|
473
|
+
// Second request depends on first
|
|
474
|
+
userPosts = createRequestor(
|
|
475
|
+
(userId: number) => this.http.get<Post[]>(`/api/users/${userId}/posts`)
|
|
476
|
+
).requestWith(this.userIdArray);
|
|
477
|
+
|
|
478
|
+
// Third request depends on first
|
|
479
|
+
userComments = createRequestor(
|
|
480
|
+
(userId: number) => this.http.get<Comment[]>(`/api/users/${userId}/comments`)
|
|
481
|
+
).requestWith(this.userIdArray);
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Combining Multiple Signals
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
export class ReportComponent {
|
|
489
|
+
private http = inject(HttpClient);
|
|
490
|
+
|
|
491
|
+
startDate = signal(new Date());
|
|
492
|
+
endDate = signal(new Date());
|
|
493
|
+
reportType = signal('summary');
|
|
494
|
+
|
|
495
|
+
// Combine multiple signals
|
|
496
|
+
report = createRequestor(
|
|
497
|
+
(start: Date, end: Date, type: string) =>
|
|
498
|
+
this.http.get(`/api/reports?start=${start}&end=${end}&type=${type}`)
|
|
499
|
+
).requestOn(this.startDate, this.endDate, this.reportType);
|
|
500
|
+
// Automatically makes a new request when any signal changes
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### Polling Pattern
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
export class StatusComponent implements OnInit {
|
|
508
|
+
private http = inject(HttpClient);
|
|
509
|
+
|
|
510
|
+
status = createRequestor(
|
|
511
|
+
() => this.http.get<Status>('/api/status'),
|
|
512
|
+
{ strategy: RequestStrategy.sequential }
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
ngOnInit() {
|
|
516
|
+
// Poll every 5 seconds
|
|
517
|
+
interval(5000).pipe(
|
|
518
|
+
takeUntilDestroyed()
|
|
519
|
+
).subscribe(() => {
|
|
520
|
+
this.status.request();
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
## Error Handling
|
|
527
|
+
|
|
528
|
+
The `createRequestor` function returns a requestor that can be chained with error handlers.
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
const userRequestor = createRequestor(
|
|
532
|
+
(id: number) => this.http.get<User>(`/api/users/${id}`)
|
|
533
|
+
)
|
|
534
|
+
.onError((error) => {
|
|
535
|
+
console.error('Failed to load user:', error);
|
|
536
|
+
})
|
|
537
|
+
.useDefaultErrorHandler(); // Use global error handler if configured
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
See the [Requestor Documentation](./REQUESTOR.md#error-handling) for complete error handling patterns.
|
|
541
|
+
|
|
542
|
+
## Success Handling
|
|
543
|
+
|
|
544
|
+
Chain success handlers for side effects after successful requests.
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
const deleteUserRequestor = createRequestor(
|
|
548
|
+
(id: number) => this.http.delete(`/api/users/${id}`)
|
|
549
|
+
)
|
|
550
|
+
.onSuccess(() => {
|
|
551
|
+
this.showSuccessMessage('User deleted');
|
|
552
|
+
this.refreshUserList();
|
|
553
|
+
});
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
See the [Requestor Documentation](./REQUESTOR.md#success-handling) for complete success handling patterns.
|
|
557
|
+
|
|
558
|
+
## Testing
|
|
559
|
+
|
|
560
|
+
### Testing Components with Requestors
|
|
561
|
+
|
|
562
|
+
```typescript
|
|
563
|
+
describe('UserComponent', () => {
|
|
564
|
+
let component: UserComponent;
|
|
565
|
+
let httpMock: HttpTestingController;
|
|
566
|
+
|
|
567
|
+
beforeEach(() => {
|
|
568
|
+
TestBed.configureTestingModule({
|
|
569
|
+
imports: [HttpClientTestingModule],
|
|
570
|
+
declarations: [UserComponent]
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
component = TestBed.createComponent(UserComponent).componentInstance;
|
|
574
|
+
httpMock = TestBed.inject(HttpTestingController);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it('should load user', () => {
|
|
578
|
+
const mockUser: User = { id: 1, name: 'John', email: 'john@test.com' };
|
|
579
|
+
|
|
580
|
+
component.userRequestor.request(1);
|
|
581
|
+
|
|
582
|
+
const req = httpMock.expectOne('/api/users/1');
|
|
583
|
+
expect(req.request.method).toBe('GET');
|
|
584
|
+
req.flush(mockUser);
|
|
585
|
+
|
|
586
|
+
expect(component.userRequestor.$isSuccess()).toBe(true);
|
|
587
|
+
expect(component.userRequestor.$selectResponse()).toEqual(mockUser);
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
it('should handle error', () => {
|
|
591
|
+
component.userRequestor.request(999);
|
|
592
|
+
|
|
593
|
+
const req = httpMock.expectOne('/api/users/999');
|
|
594
|
+
req.error(new ProgressEvent('error'), { status: 404, statusText: 'Not Found' });
|
|
595
|
+
|
|
596
|
+
expect(component.userRequestor.$isError()).toBe(true);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
afterEach(() => {
|
|
600
|
+
httpMock.verify();
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### Mocking Requestors in Tests
|
|
606
|
+
|
|
607
|
+
```typescript
|
|
608
|
+
describe('UserDashboardComponent', () => {
|
|
609
|
+
let component: UserDashboardComponent;
|
|
610
|
+
|
|
611
|
+
beforeEach(() => {
|
|
612
|
+
// Create a mock requestor
|
|
613
|
+
const mockRequestor = {
|
|
614
|
+
request: jasmine.createSpy('request'),
|
|
615
|
+
$isSuccess: signal(true),
|
|
616
|
+
$selectResponse: signal({ id: 1, name: 'Test User' }),
|
|
617
|
+
$isInProgress: signal(false),
|
|
618
|
+
$isError: signal(false)
|
|
619
|
+
} as any;
|
|
620
|
+
|
|
621
|
+
TestBed.configureTestingModule({
|
|
622
|
+
declarations: [UserDashboardComponent],
|
|
623
|
+
providers: [
|
|
624
|
+
{ provide: UserService, useValue: { userRequestor: mockRequestor } }
|
|
625
|
+
]
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
component = TestBed.createComponent(UserDashboardComponent).componentInstance;
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
it('should display user data', () => {
|
|
632
|
+
expect(component.userService.userRequestor.$selectResponse()?.name)
|
|
633
|
+
.toBe('Test User');
|
|
634
|
+
});
|
|
635
|
+
});
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
## Best Practices
|
|
639
|
+
|
|
640
|
+
### Using `createRequestor` over `RequestStateFactory`**: It's more modern and requires less boilerplate.
|
|
641
|
+
|
|
642
|
+
2. **Create requestors as class properties**: Define them at the class level for easy access.
|
|
643
|
+
```typescript
|
|
644
|
+
export class UserComponent {
|
|
645
|
+
userRequestor = createRequestor(...);
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
3. **Remember `requestWith` takes an array**: When using `requestWith`, ensure your observable/signal emits an array of parameters.
|
|
650
|
+
|
|
651
|
+
3. **Choose appropriate strategies**: Match the strategy to your use case (cancelPrevious for search, concurrent for independent operations).
|
|
652
|
+
|
|
653
|
+
4. **Use projection for transformation**: Keep transformation logic in the requestor rather than in components.
|
|
654
|
+
|
|
655
|
+
5. **Chain handlers fluently**: Take advantage of method chaining for clean code.
|
|
656
|
+
|
|
657
|
+
6. **Provide custom injector when needed**: If creating outside injection context, always provide an injector.
|
|
658
|
+
|
|
659
|
+
7. **Type your responses**: Always specify generic types for compile-time safety.
|
|
660
|
+
|
|
661
|
+
8. **Use signals for reactivity**: Prefer signal-based state access (`$selectResponse()`) over observables in templates.
|
|
662
|
+
|
|
663
|
+
## See Also
|
|
664
|
+
|
|
665
|
+
- [Requestor (RequestStateStore) Documentation](./REQUESTOR.md)
|
|
666
|
+
- [RequestStateFactory Documentation](./REQUEST-STATE-FACTORY.md)
|
|
667
|
+
- [Request State Types](./REQUEST-STATE-TYPES.md)
|