@smallpearl/ngx-helper 0.33.28 → 0.33.30
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/fesm2022/smallpearl-ngx-helper-mat-entity-crud.mjs +474 -450
- package/fesm2022/smallpearl-ngx-helper-mat-entity-crud.mjs.map +1 -1
- package/mat-entity-crud/index.d.ts +3 -2
- package/mat-entity-crud/src/mat-entity-crud-form-base.d.ts +28 -5
- package/package.json +7 -7
- /package/mat-entity-crud/src/{context-param-to-http-context.d.ts → convert-context-input-to-http-context.d.ts} +0 -0
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import * as i1 from '@angular/common/http';
|
|
2
|
-
import {
|
|
2
|
+
import { HttpContext, HttpClient, HttpParams, HttpContextToken } from '@angular/common/http';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import {
|
|
4
|
+
import { input, signal, computed, inject, ChangeDetectorRef, Component, InjectionToken, viewChild, ViewContainerRef, ChangeDetectionStrategy, viewChildren, EventEmitter, effect, ContentChildren, Output } from '@angular/core';
|
|
5
|
+
import * as i4$1 from '@jsverse/transloco';
|
|
6
|
+
import { TranslocoService, TranslocoModule, provideTranslocoScope } from '@jsverse/transloco';
|
|
7
|
+
import { setServerErrorsAsFormErrors } from '@smallpearl/ngx-helper/forms';
|
|
8
|
+
import { Subscription, Observable, map, tap, of, switchMap, firstValueFrom, catchError, EMPTY, throwError } from 'rxjs';
|
|
9
|
+
import { sideloadToComposite } from '@smallpearl/ngx-helper/sideload';
|
|
5
10
|
import * as i4 from '@angular/common';
|
|
6
11
|
import { CommonModule } from '@angular/common';
|
|
7
12
|
import * as i2 from '@angular/material/button';
|
|
@@ -21,26 +26,12 @@ import { MatIconModule } from '@angular/material/icon';
|
|
|
21
26
|
import * as i8 from '@angular/material/menu';
|
|
22
27
|
import { MatMenuModule } from '@angular/material/menu';
|
|
23
28
|
import * as i3$1 from '@angular/platform-browser';
|
|
24
|
-
import * as i4$1 from '@jsverse/transloco';
|
|
25
|
-
import { TranslocoService, TranslocoModule, provideTranslocoScope } from '@jsverse/transloco';
|
|
26
29
|
import * as i11 from 'angular-split';
|
|
27
30
|
import { AngularSplitModule } from 'angular-split';
|
|
28
31
|
import { startCase, clone } from 'lodash';
|
|
29
32
|
import { plural } from 'pluralize';
|
|
30
|
-
import { Observable, of, Subscription, tap, switchMap, firstValueFrom, map, catchError, EMPTY, throwError } from 'rxjs';
|
|
31
33
|
import * as i1$1 from '@angular/material/toolbar';
|
|
32
34
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
|
33
|
-
import { setServerErrorsAsFormErrors } from '@smallpearl/ngx-helper/forms';
|
|
34
|
-
import { sideloadToComposite } from '@smallpearl/ngx-helper/sideload';
|
|
35
|
-
|
|
36
|
-
const SP_MAT_ENTITY_CRUD_HTTP_CONTEXT = new HttpContextToken(() => ({
|
|
37
|
-
entityName: '',
|
|
38
|
-
entityNamePlural: '',
|
|
39
|
-
endpoint: '',
|
|
40
|
-
op: undefined,
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
|
-
const SP_MAT_ENTITY_CRUD_CONFIG = new InjectionToken('SPMatEntityCrudConfig');
|
|
44
35
|
|
|
45
36
|
/**
|
|
46
37
|
* Converts array of HttpContextToken key, value pairs to HttpContext
|
|
@@ -67,80 +58,480 @@ function convertHttpContextInputToHttpContext(context, reqContext) {
|
|
|
67
58
|
return context;
|
|
68
59
|
}
|
|
69
60
|
|
|
70
|
-
function defaultCrudResponseParser(entityName, idKey, method, // 'create' | 'retrieve' | 'update' | 'delete',
|
|
71
|
-
resp) {
|
|
72
|
-
// If the response is an object with a property '<idKey>', return it as
|
|
73
|
-
// TEntity.
|
|
74
|
-
if (resp.hasOwnProperty(idKey)) {
|
|
75
|
-
return resp;
|
|
76
|
-
}
|
|
77
|
-
// If the response has an object indexed at '<entityName>' and it has
|
|
78
|
-
// the property '<idKey>', return it as TEntity.
|
|
79
|
-
if (resp.hasOwnProperty(entityName)) {
|
|
80
|
-
const obj = resp[entityName];
|
|
81
|
-
if (obj.hasOwnProperty(idKey)) {
|
|
82
|
-
return obj;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
// Return undefined, indicating that we could't parse the response.
|
|
86
|
-
return undefined;
|
|
87
|
-
}
|
|
88
|
-
const DefaultSPMatEntityCrudConfig = {
|
|
89
|
-
crudOpResponseParser: defaultCrudResponseParser
|
|
90
|
-
};
|
|
91
61
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
62
|
+
* This is a convenience base class that clients can derive from to implement
|
|
63
|
+
* their CRUD form component. Particularly this class registers the change
|
|
64
|
+
* detection hook which will be called when the user attempts to close the
|
|
65
|
+
* form's parent container pane via the Close button on the top right.
|
|
66
|
+
*
|
|
67
|
+
* This button behaves like a Cancel button in a desktop app and therefore if
|
|
68
|
+
* the user has entered any data in the form's controls, (determined by
|
|
69
|
+
* checking form.touched), then a 'Lose Changes' prompt is displayed allowing
|
|
70
|
+
* the user to cancel the closure.
|
|
71
|
+
*
|
|
72
|
+
* The `@Component` decorator is fake to keep the VSCode angular linter quiet.
|
|
73
|
+
*
|
|
74
|
+
* This class can be used in two modes:
|
|
75
|
+
*
|
|
76
|
+
* I. SPMatEntityCrudComponent mode
|
|
77
|
+
* This mode relies on a bridge interface that implements the
|
|
78
|
+
* SPMatEntityCrudCreateEditBridge interface to perform the entity
|
|
79
|
+
* load/create/update operations. This is the intended mode when the
|
|
80
|
+
* component is used as a part of the SPMatEntityCrudComponent to
|
|
81
|
+
* create/update an entity. This mode requires the following properties
|
|
82
|
+
* to be set:
|
|
83
|
+
* - entity: TEntity | TEntity[IdKey] | undefined (for create)
|
|
84
|
+
* - bridge: SPMatEntityCrudCreateEditBridge
|
|
85
|
+
*
|
|
86
|
+
* II. Standalone mode
|
|
87
|
+
* This mode does not rely on the bridge interface and the component
|
|
88
|
+
* itself performs the entity load/create/update operations.
|
|
89
|
+
* This mode requires the following properties to be set:
|
|
90
|
+
* - entity: TEntity | TEntity[IdKey] | undefined (for create)
|
|
91
|
+
* - baseUrl: string - Base URL for CRUD operations. This URL does not
|
|
92
|
+
* include the entity id. The entity id will be appended to this URL
|
|
93
|
+
* for entity load and update operations. For create operation, this
|
|
94
|
+
* URL is used as is.
|
|
95
|
+
* - entityName: string - Name of the entity, used to parse sideloaded
|
|
96
|
+
* entity responses.
|
|
97
|
+
* - httpReqContext?: HttpContextInput - Optional HTTP context to be
|
|
98
|
+
* passed to the HTTP requests. For instance, if your app has a HTTP
|
|
99
|
+
* interceptor that adds authentication tokens to the requests based
|
|
100
|
+
* on a HttpContextToken, then you can pass that token here.
|
|
101
|
+
*
|
|
102
|
+
* I. SPMatEntityCrudComponent mode:
|
|
103
|
+
*
|
|
104
|
+
* 1. Declare a FormGroup<> type as
|
|
105
|
+
*
|
|
106
|
+
* ```
|
|
107
|
+
* type MyForm = FormGroup<{
|
|
108
|
+
* name: FormControl<string>;
|
|
109
|
+
* type: FormControl<string>;
|
|
110
|
+
* notes: FormControl<string>;
|
|
111
|
+
* }>;
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* 2. Derive your form's component class from this and implement the
|
|
115
|
+
* createForm() method returing the FormGroup<> instance that matches
|
|
116
|
+
* the FormGroup concrete type above.
|
|
117
|
+
*
|
|
118
|
+
* ```
|
|
119
|
+
* class MyFormComponent extends SPMatEntityCrudFormBase<MyForm, MyEntity> {
|
|
120
|
+
* constructor() {
|
|
121
|
+
* super()
|
|
122
|
+
* }
|
|
123
|
+
* createForm() {
|
|
124
|
+
* return new FormGroup([...])
|
|
125
|
+
* }
|
|
126
|
+
* }
|
|
127
|
+
* ```
|
|
128
|
+
*
|
|
129
|
+
* 3. If your form's value requires manipulation before being sent to the
|
|
130
|
+
* server, override `getFormValue()` method and do it there before returning
|
|
131
|
+
* the modified values.
|
|
132
|
+
*
|
|
133
|
+
* 4. Wire up the form in the template as below
|
|
134
|
+
*
|
|
135
|
+
* ```html
|
|
136
|
+
* @if (loadEntity$ | async) {
|
|
137
|
+
* <form [formGroup]='form'.. (ngSubmit)="onSubmit()">
|
|
138
|
+
* <button type="submit">Submit</button>
|
|
139
|
+
* </form>
|
|
140
|
+
* } @else {
|
|
141
|
+
* <div>Loading...</div>
|
|
142
|
+
* }
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* Here `loadEntity$` is an Observable<boolean> that upon emission of `true`
|
|
146
|
+
* indicates that the entity has been loaded from server (in case of edit)
|
|
147
|
+
* and the form is ready to be displayed. Note that if the full entity was
|
|
148
|
+
* passed in the `entity` input property, then no server load is necessary
|
|
149
|
+
* and the form will be created immediately.
|
|
150
|
+
*
|
|
151
|
+
* 5. In the parent component that hosts the SPMatEntityCrudComponent, set
|
|
152
|
+
* the `entity` and `bridge` input properties of this component to
|
|
153
|
+
* appropriate values. For instance, if your form component has the
|
|
154
|
+
* selector `app-my-entity-form`, then the parent component's template
|
|
155
|
+
* will have:
|
|
156
|
+
*
|
|
157
|
+
* ```html
|
|
158
|
+
* <sp-mat-entity-crud
|
|
159
|
+
* ...
|
|
160
|
+
* createEditFormTemplate="entityFormTemplate"
|
|
161
|
+
* ></sp-mat-entity-crud>
|
|
162
|
+
* <ng-template #entityFormTemplate let-data="data">
|
|
163
|
+
* <app-my-entity-form
|
|
164
|
+
* [entity]="data.entity"
|
|
165
|
+
* [bridge]="data.bridge"
|
|
166
|
+
* ></app-my-entity-form>
|
|
167
|
+
* </ng-template>
|
|
168
|
+
* ```
|
|
169
|
+
*
|
|
170
|
+
* II. Standalone mode
|
|
171
|
+
*
|
|
172
|
+
* 1..4. Same as above, except set the required `bridge` input to `undefined`.
|
|
173
|
+
* 5. Initialize the component's inputs `baseUrl` and `entityName` with the
|
|
174
|
+
* appropriate values. If you would like to pass additional HTTP context to
|
|
175
|
+
* the HTTP requests, then set the `httpReqContext` input as well.
|
|
176
|
+
* If the entity uses an id key other than 'id', then set the `idKey` input
|
|
177
|
+
* to the appropriate id key name.
|
|
178
|
+
* 6. If you want to retrieve the created/updated entity after the create/update
|
|
179
|
+
* operation, override the `onPostCreate()` and/or `onPostUpdate()` methods
|
|
180
|
+
* respectively.
|
|
96
181
|
*/
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
});
|
|
118
|
-
entity = signal(undefined);
|
|
119
|
-
title = signal(undefined);
|
|
120
|
-
params = signal(undefined);
|
|
121
|
-
clientFormView;
|
|
122
|
-
vc = viewChild('clientFormContainer', { read: ViewContainerRef });
|
|
123
|
-
config;
|
|
182
|
+
class SPMatEntityCrudFormBase {
|
|
183
|
+
// bridge mode inputs
|
|
184
|
+
entity = input();
|
|
185
|
+
bridge = input();
|
|
186
|
+
params = input();
|
|
187
|
+
// END bridge mode inputs
|
|
188
|
+
// standalone mode inputs
|
|
189
|
+
// Entity name, which is used to parse sideloaded entity responses
|
|
190
|
+
entityName = input();
|
|
191
|
+
// Base CRUD URL, which is the GET-list-of-entities/POST-to-create
|
|
192
|
+
// URL. Update URL will be derived from this ias `baseUrl()/${TEntity[IdKey]}`
|
|
193
|
+
baseUrl = input();
|
|
194
|
+
// Additional request context to be passed to the request
|
|
195
|
+
httpReqContext = input();
|
|
196
|
+
// ID key, defaults to 'id'
|
|
197
|
+
idKey = input('id');
|
|
198
|
+
// END standalone mode inputs
|
|
199
|
+
// IMPLEMENTATION
|
|
200
|
+
loadEntity$;
|
|
201
|
+
_entity = signal(undefined);
|
|
124
202
|
sub$ = new Subscription();
|
|
203
|
+
// Store for internal form signal. form() is computed from this.
|
|
204
|
+
_form = signal(undefined);
|
|
205
|
+
// Force typecast to TFormGroup so that we can use it in the template
|
|
206
|
+
// without having to use the non-nullable operator ! with every reference
|
|
207
|
+
// of form(). In any case the form() signal is always set in ngOnInit()
|
|
208
|
+
// method after the form is created. And if form() is not set, then there
|
|
209
|
+
// will be errors while loading the form in the template.
|
|
210
|
+
form = computed(() => this._form());
|
|
125
211
|
transloco = inject(TranslocoService);
|
|
126
|
-
|
|
127
|
-
|
|
212
|
+
cdr = inject(ChangeDetectorRef);
|
|
213
|
+
http = inject(HttpClient);
|
|
214
|
+
// This is really not necessary. We can check for this.bridge() directly.
|
|
215
|
+
mode = computed(() => {
|
|
216
|
+
return this.bridge() ? 'bridge' : 'standalone';
|
|
217
|
+
});
|
|
218
|
+
canCancelEdit = () => {
|
|
219
|
+
return this._canCancelEdit();
|
|
220
|
+
};
|
|
221
|
+
_canCancelEdit() {
|
|
222
|
+
const form = this._form();
|
|
223
|
+
if (form && form.touched) {
|
|
224
|
+
return window.confirm(this.transloco.translate('spMatEntityCrud.loseChangesConfirm'));
|
|
225
|
+
}
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
ngOnInit() {
|
|
229
|
+
// Validate inputs. Either bridge or (baseUrl and entityName) must be
|
|
230
|
+
// defined.
|
|
231
|
+
if (this.mode() === 'standalone' &&
|
|
232
|
+
(!this.getBaseUrl() || !this.getEntityName())) {
|
|
233
|
+
throw new Error('SPMatEntityCrudFormBase: baseUrl and entityName inputs must be defined in standalone mode.');
|
|
234
|
+
}
|
|
235
|
+
this.loadEntity$ = (typeof this.entity() === 'object' || this.entity() === undefined
|
|
236
|
+
? new Observable((subscriber) => {
|
|
237
|
+
subscriber.next(this.entity());
|
|
238
|
+
subscriber.complete();
|
|
239
|
+
})
|
|
240
|
+
: this.load(this.entity())).pipe(map((resp) => {
|
|
241
|
+
const compositeEntity = this.getEntityFromLoadResponse(resp);
|
|
242
|
+
this._entity.set(compositeEntity);
|
|
243
|
+
this._form.set(this.createForm(compositeEntity));
|
|
244
|
+
const bridge = this.bridge();
|
|
245
|
+
if (bridge && bridge.registerCanCancelEditCallback) {
|
|
246
|
+
bridge.registerCanCancelEditCallback(this.canCancelEdit);
|
|
247
|
+
}
|
|
248
|
+
return true;
|
|
249
|
+
}));
|
|
128
250
|
}
|
|
129
|
-
ngOnInit() { }
|
|
130
251
|
ngOnDestroy() {
|
|
131
252
|
this.sub$.unsubscribe();
|
|
132
253
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
254
|
+
/**
|
|
255
|
+
* Additional parameters for loading the entity, in case this.entity() value
|
|
256
|
+
* is of type TEntity[IdKey].
|
|
257
|
+
* @returns
|
|
258
|
+
*/
|
|
259
|
+
getLoadEntityParams() {
|
|
260
|
+
return '';
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Return the TEntity object from the response returned by the
|
|
264
|
+
* load() method. Typically entity load returns the actual
|
|
265
|
+
* entity object itself. In some cases, where response is sideloaded, the
|
|
266
|
+
* default implementation here uses the `sideloadToComposite()` utility to
|
|
267
|
+
* extract the entity from the response after merging (inplace) the
|
|
268
|
+
* sideloaded data into a composite.
|
|
269
|
+
*
|
|
270
|
+
* If you have a different response shape, or if your sideloaded object
|
|
271
|
+
* response requires custom custom `sideloadDataMap`, override this method
|
|
272
|
+
* and implement your custom logic to extract the TEntity object from the
|
|
273
|
+
* response.
|
|
274
|
+
* @param resp
|
|
275
|
+
* @returns
|
|
276
|
+
*/
|
|
277
|
+
getEntityFromLoadResponse(resp) {
|
|
278
|
+
if (!resp || typeof resp !== 'object') {
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
const entityName = this.getEntityName();
|
|
282
|
+
if (resp.hasOwnProperty(this.getIdKey())) {
|
|
283
|
+
return resp;
|
|
284
|
+
}
|
|
285
|
+
else if (entityName && resp.hasOwnProperty(entityName)) {
|
|
286
|
+
// const sideloadDataMap = this.sideloadDataMap();
|
|
287
|
+
return sideloadToComposite(resp, entityName, this.getIdKey());
|
|
288
|
+
}
|
|
289
|
+
return undefined;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Override to customize the id key name if it's not 'id'
|
|
293
|
+
* @returns The name of the unique identifier key that will be used to
|
|
294
|
+
* extract the entity's id for UPDATE operation.
|
|
295
|
+
*/
|
|
296
|
+
getIdKey() {
|
|
297
|
+
const bridge = this.bridge();
|
|
298
|
+
if (bridge) {
|
|
299
|
+
return bridge.getIdKey();
|
|
300
|
+
}
|
|
301
|
+
return this.idKey();
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Return the form's value to be sent to server as Create/Update CRUD
|
|
305
|
+
* operation data.
|
|
306
|
+
* @returns
|
|
307
|
+
*/
|
|
308
|
+
getFormValue() {
|
|
309
|
+
const form = this.form();
|
|
310
|
+
return form ? form.value : undefined;
|
|
311
|
+
}
|
|
312
|
+
onSubmit() {
|
|
313
|
+
const value = this.getFormValue();
|
|
314
|
+
const obs = !this._entity()
|
|
315
|
+
? this.create(value)
|
|
316
|
+
: this.update(this._entity()[this.getIdKey()], value);
|
|
317
|
+
this.sub$.add(obs
|
|
318
|
+
?.pipe(tap((entity) => this._entity()
|
|
319
|
+
? this.onPostUpdate(entity)
|
|
320
|
+
: this.onPostCreate(entity)), setServerErrorsAsFormErrors(this._form(), this.cdr))
|
|
321
|
+
.subscribe());
|
|
322
|
+
}
|
|
323
|
+
onPostCreate(entity) {
|
|
324
|
+
/* empty */
|
|
325
|
+
}
|
|
326
|
+
onPostUpdate(entity) {
|
|
327
|
+
/* empty */
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Loads the entity if `this.entity()` is of type TEntity[IdKey]. If `bridge`
|
|
331
|
+
* input is defined, then it's `loadEntity()` method is used to load the
|
|
332
|
+
* entity. Otherwise, then this method attempts to load the entity using
|
|
333
|
+
* HTTP GET from the URL derived from `baseUrl` input.
|
|
334
|
+
* @param entityId
|
|
335
|
+
* @param params
|
|
336
|
+
* @returns
|
|
337
|
+
*/
|
|
338
|
+
load(entityId) {
|
|
339
|
+
const bridge = this.bridge();
|
|
340
|
+
const params = this.getLoadEntityParams();
|
|
341
|
+
if (bridge) {
|
|
342
|
+
return bridge.loadEntity(entityId, params);
|
|
343
|
+
}
|
|
344
|
+
// Try to load using baseUrl.
|
|
345
|
+
const url = this.getEntityUrl(entityId);
|
|
346
|
+
return this.http
|
|
347
|
+
.get(this.getEntityUrl(entityId), {
|
|
348
|
+
params: typeof params === 'string'
|
|
349
|
+
? new HttpParams({ fromString: params })
|
|
350
|
+
: params,
|
|
351
|
+
context: this.getRequestContext(),
|
|
352
|
+
})
|
|
353
|
+
.pipe(map((resp) => this.getEntityFromLoadResponse(resp)));
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Create a new entity using the bridge if defined, otherwise using HTTP
|
|
357
|
+
* POST to the `baseUrl`.
|
|
358
|
+
* @param values
|
|
359
|
+
* @returns
|
|
360
|
+
*/
|
|
361
|
+
create(values) {
|
|
362
|
+
const bridge = this.bridge();
|
|
363
|
+
if (bridge) {
|
|
364
|
+
return bridge.create(values);
|
|
365
|
+
}
|
|
366
|
+
return this.http
|
|
367
|
+
.post(this.getBaseUrl(), values, {
|
|
368
|
+
context: this.getRequestContext(),
|
|
369
|
+
})
|
|
370
|
+
.pipe(map((resp) => this.getEntityFromLoadResponse(resp)));
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Update an existing entity using the bridge if defined, otherwise using HTTP
|
|
374
|
+
* PATCH to the URL derived from `baseUrl` and the entity id.
|
|
375
|
+
* @param id
|
|
376
|
+
* @param values
|
|
377
|
+
* @returns
|
|
378
|
+
*/
|
|
379
|
+
update(id, values) {
|
|
380
|
+
const bridge = this.bridge();
|
|
381
|
+
if (bridge) {
|
|
382
|
+
return bridge.update(id, values);
|
|
383
|
+
}
|
|
384
|
+
return this.http
|
|
385
|
+
.patch(this.getEntityUrl(id), values, {
|
|
386
|
+
context: this.getRequestContext(),
|
|
387
|
+
})
|
|
388
|
+
.pipe(map((resp) => this.getEntityFromLoadResponse(resp)));
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Wrapper around entityName input to get the entity name. If `bridge` input
|
|
392
|
+
* is defined, then its `getEntityName()` method is used. This allows
|
|
393
|
+
* derived classes to override this method to provide custom logic to
|
|
394
|
+
* determine the entity name.
|
|
395
|
+
* @returns
|
|
396
|
+
*/
|
|
397
|
+
getEntityName() {
|
|
398
|
+
const bridge = this.bridge();
|
|
399
|
+
if (bridge) {
|
|
400
|
+
return bridge.getEntityName();
|
|
401
|
+
}
|
|
402
|
+
return this.entityName();
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Returns the baseUrl. Derived classes can override this to provide custom
|
|
406
|
+
* logic to determine the baseUrl.
|
|
407
|
+
* @returns
|
|
408
|
+
*/
|
|
409
|
+
getBaseUrl() {
|
|
410
|
+
return this.baseUrl();
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Returns the entity URL for the given entity id. If `bridge` input is
|
|
414
|
+
* defined, then its `getEntityUrl()` method is used. Otherwise, the URL is
|
|
415
|
+
* derived from `baseUrl` input.
|
|
416
|
+
* @param entityId
|
|
417
|
+
* @returns
|
|
418
|
+
*/
|
|
419
|
+
getEntityUrl(entityId) {
|
|
420
|
+
const bridge = this.bridge();
|
|
421
|
+
if (bridge) {
|
|
422
|
+
return bridge.getEntityUrl(entityId);
|
|
423
|
+
}
|
|
424
|
+
const baseUrl = this.getBaseUrl();
|
|
425
|
+
if (baseUrl) {
|
|
426
|
+
const urlParts = baseUrl.split('?');
|
|
427
|
+
return `${urlParts[0]}${String(entityId)}/${urlParts[1] ? '?' + urlParts[1] : ''}`;
|
|
428
|
+
}
|
|
429
|
+
console.warn('SPMatEntityCrudFormBase.getEntityUrl: Cannot determine entity URL as neither baseUrl nor bridge inputs are provided.');
|
|
430
|
+
return '';
|
|
431
|
+
}
|
|
432
|
+
getRequestContext() {
|
|
433
|
+
let context = new HttpContext();
|
|
434
|
+
const httpReqContext = this.httpReqContext();
|
|
435
|
+
if (httpReqContext) {
|
|
436
|
+
context = convertHttpContextInputToHttpContext(context, httpReqContext);
|
|
437
|
+
}
|
|
438
|
+
return context;
|
|
439
|
+
}
|
|
440
|
+
/** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: SPMatEntityCrudFormBase, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
441
|
+
/** @nocollapse */ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.1.6", type: SPMatEntityCrudFormBase, isStandalone: false, selector: "_#_sp-mat-entity-crud-form-base_#_", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: false, transformFunction: null }, bridge: { classPropertyName: "bridge", publicName: "bridge", isSignal: true, isRequired: false, transformFunction: null }, params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null }, entityName: { classPropertyName: "entityName", publicName: "entityName", isSignal: true, isRequired: false, transformFunction: null }, baseUrl: { classPropertyName: "baseUrl", publicName: "baseUrl", isSignal: true, isRequired: false, transformFunction: null }, httpReqContext: { classPropertyName: "httpReqContext", publicName: "httpReqContext", isSignal: true, isRequired: false, transformFunction: null }, idKey: { classPropertyName: "idKey", publicName: "idKey", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: ``, isInline: true });
|
|
442
|
+
}
|
|
443
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: SPMatEntityCrudFormBase, decorators: [{
|
|
444
|
+
type: Component,
|
|
445
|
+
args: [{
|
|
446
|
+
selector: '_#_sp-mat-entity-crud-form-base_#_',
|
|
447
|
+
template: ``,
|
|
448
|
+
standalone: false,
|
|
449
|
+
}]
|
|
450
|
+
}] });
|
|
451
|
+
|
|
452
|
+
const SP_MAT_ENTITY_CRUD_HTTP_CONTEXT = new HttpContextToken(() => ({
|
|
453
|
+
entityName: '',
|
|
454
|
+
entityNamePlural: '',
|
|
455
|
+
endpoint: '',
|
|
456
|
+
op: undefined,
|
|
457
|
+
}));
|
|
458
|
+
|
|
459
|
+
const SP_MAT_ENTITY_CRUD_CONFIG = new InjectionToken('SPMatEntityCrudConfig');
|
|
460
|
+
|
|
461
|
+
function defaultCrudResponseParser(entityName, idKey, method, // 'create' | 'retrieve' | 'update' | 'delete',
|
|
462
|
+
resp) {
|
|
463
|
+
// If the response is an object with a property '<idKey>', return it as
|
|
464
|
+
// TEntity.
|
|
465
|
+
if (resp.hasOwnProperty(idKey)) {
|
|
466
|
+
return resp;
|
|
467
|
+
}
|
|
468
|
+
// If the response has an object indexed at '<entityName>' and it has
|
|
469
|
+
// the property '<idKey>', return it as TEntity.
|
|
470
|
+
if (resp.hasOwnProperty(entityName)) {
|
|
471
|
+
const obj = resp[entityName];
|
|
472
|
+
if (obj.hasOwnProperty(idKey)) {
|
|
473
|
+
return obj;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
// Return undefined, indicating that we could't parse the response.
|
|
477
|
+
return undefined;
|
|
478
|
+
}
|
|
479
|
+
const DefaultSPMatEntityCrudConfig = {
|
|
480
|
+
crudOpResponseParser: defaultCrudResponseParser
|
|
481
|
+
};
|
|
482
|
+
/**
|
|
483
|
+
* To be called from an object constructor as it internally calls Angular's
|
|
484
|
+
* inject() API.
|
|
485
|
+
* @param userConfig
|
|
486
|
+
* @returns
|
|
487
|
+
*/
|
|
488
|
+
function getEntityCrudConfig() {
|
|
489
|
+
const userCrudConfig = inject(SP_MAT_ENTITY_CRUD_CONFIG, {
|
|
490
|
+
optional: true,
|
|
491
|
+
});
|
|
492
|
+
return {
|
|
493
|
+
...DefaultSPMatEntityCrudConfig,
|
|
494
|
+
...(userCrudConfig ?? {}),
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
class FormViewHostComponent {
|
|
499
|
+
entityCrudComponentBase = input.required();
|
|
500
|
+
clientViewTemplate = input(null);
|
|
501
|
+
_itemLabel = computed(() => {
|
|
502
|
+
const label = this.entityCrudComponentBase().getItemLabel();
|
|
503
|
+
return label instanceof Observable ? label : of(label);
|
|
504
|
+
});
|
|
505
|
+
_itemLabelPlural = computed(() => {
|
|
506
|
+
const label = this.entityCrudComponentBase().getItemLabelPlural();
|
|
507
|
+
return label instanceof Observable ? label : of(label);
|
|
508
|
+
});
|
|
509
|
+
entity = signal(undefined);
|
|
510
|
+
title = signal(undefined);
|
|
511
|
+
params = signal(undefined);
|
|
512
|
+
clientFormView;
|
|
513
|
+
vc = viewChild('clientFormContainer', { read: ViewContainerRef });
|
|
514
|
+
config;
|
|
515
|
+
sub$ = new Subscription();
|
|
516
|
+
transloco = inject(TranslocoService);
|
|
517
|
+
constructor() {
|
|
518
|
+
this.config = getEntityCrudConfig();
|
|
519
|
+
}
|
|
520
|
+
ngOnInit() { }
|
|
521
|
+
ngOnDestroy() {
|
|
522
|
+
this.sub$.unsubscribe();
|
|
523
|
+
}
|
|
524
|
+
show(entity, params) {
|
|
525
|
+
this.entity.set(entity);
|
|
526
|
+
if (params && params?.title) {
|
|
527
|
+
this.title.set(params.title instanceof Observable ? params.title : of(params.title));
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
// this.title.set(entity ? this.config.i18n.editItemLabel(this.itemLabel()) : this.config.i18n.newItemLabel(this.itemLabel()));
|
|
531
|
+
// this.title.set(
|
|
532
|
+
// this.transloco.translate(entity ? 'editItem' : 'newItem', {
|
|
533
|
+
// item: this.itemLabel(),
|
|
534
|
+
// })
|
|
144
535
|
// );
|
|
145
536
|
}
|
|
146
537
|
this.params.set(params);
|
|
@@ -1713,376 +2104,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImpor
|
|
|
1713
2104
|
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".preview-wrapper{display:flex;flex-direction:column;height:100%!important;width:100%!important}mat-toolbar{background-color:var(--mat-sys-surface-variant)}.spacer{flex:1 1 auto}.preview-content{padding:.4em;flex-grow:1;overflow:scroll}\n"] }]
|
|
1714
2105
|
}] });
|
|
1715
2106
|
|
|
1716
|
-
/**
|
|
1717
|
-
* This is a convenience base class that clients can derive from to implement
|
|
1718
|
-
* their CRUD form component. Particularly this class registers the change
|
|
1719
|
-
* detection hook which will be called when the user attempts to close the
|
|
1720
|
-
* form's parent container pane via the Close button on the top right.
|
|
1721
|
-
*
|
|
1722
|
-
* This button behaves like a Cancel button in a desktop app and therefore if
|
|
1723
|
-
* the user has entered any data in the form's controls, (determined by
|
|
1724
|
-
* checking form.touched), then a 'Lose Changes' prompt is displayed allowing
|
|
1725
|
-
* the user to cancel the closure.
|
|
1726
|
-
*
|
|
1727
|
-
* The `@Component` decorator is fake to keep the VSCode angular linter quiet.
|
|
1728
|
-
*
|
|
1729
|
-
* This class can be used in two modes:
|
|
1730
|
-
*
|
|
1731
|
-
* I. SPMatEntityCrudComponent mode
|
|
1732
|
-
* This mode relies on a bridge interface that implements the
|
|
1733
|
-
* SPMatEntityCrudCreateEditBridge interface to perform the entity
|
|
1734
|
-
* load/create/update operations. This is the intended mode when the
|
|
1735
|
-
* component is used as a part of the SPMatEntityCrudComponent to
|
|
1736
|
-
* create/update an entity. This mode requires the following properties
|
|
1737
|
-
* to be set:
|
|
1738
|
-
* - entity: TEntity | TEntity[IdKey] | undefined (for create)
|
|
1739
|
-
* - bridge: SPMatEntityCrudCreateEditBridge
|
|
1740
|
-
*
|
|
1741
|
-
* II. Standalone mode
|
|
1742
|
-
* This mode does not rely on the bridge interface and the component
|
|
1743
|
-
* itself performs the entity load/create/update operations.
|
|
1744
|
-
* This mode requires the following properties to be set:
|
|
1745
|
-
* - entity: TEntity | TEntity[IdKey] | undefined (for create)
|
|
1746
|
-
* - baseUrl: string - Base URL for CRUD operations. This URL does not
|
|
1747
|
-
* include the entity id. The entity id will be appended to this URL
|
|
1748
|
-
* for entity load and update operations. For create operation, this
|
|
1749
|
-
* URL is used as is.
|
|
1750
|
-
* - entityName: string - Name of the entity, used to parse sideloaded
|
|
1751
|
-
* entity responses.
|
|
1752
|
-
* - httpReqContext?: HttpContextInput - Optional HTTP context to be
|
|
1753
|
-
* passed to the HTTP requests. For instance, if your app has a HTTP
|
|
1754
|
-
* interceptor that adds authentication tokens to the requests based
|
|
1755
|
-
* on a HttpContextToken, then you can pass that token here.
|
|
1756
|
-
*
|
|
1757
|
-
* I. SPMatEntityCrudComponent mode:
|
|
1758
|
-
*
|
|
1759
|
-
* 1. Declare a FormGroup<> type as
|
|
1760
|
-
*
|
|
1761
|
-
* ```
|
|
1762
|
-
* type MyForm = FormGroup<{
|
|
1763
|
-
* name: FormControl<string>;
|
|
1764
|
-
* type: FormControl<string>;
|
|
1765
|
-
* notes: FormControl<string>;
|
|
1766
|
-
* }>;
|
|
1767
|
-
* ```
|
|
1768
|
-
*
|
|
1769
|
-
* 2. Derive your form's component class from this and implement the
|
|
1770
|
-
* createForm() method returing the FormGroup<> instance that matches
|
|
1771
|
-
* the FormGroup concrete type above.
|
|
1772
|
-
*
|
|
1773
|
-
* ```
|
|
1774
|
-
* class MyFormComponent extends SPMatEntityCrudFormBase<MyForm, MyEntity> {
|
|
1775
|
-
* constructor() {
|
|
1776
|
-
* super()
|
|
1777
|
-
* }
|
|
1778
|
-
* createForm() {
|
|
1779
|
-
* return new FormGroup([...])
|
|
1780
|
-
* }
|
|
1781
|
-
* }
|
|
1782
|
-
* ```
|
|
1783
|
-
*
|
|
1784
|
-
* 3. If your form's value requires manipulation before being sent to the
|
|
1785
|
-
* server, override `getFormValue()` method and do it there before returning
|
|
1786
|
-
* the modified values.
|
|
1787
|
-
*
|
|
1788
|
-
* 4. Wire up the form in the template as below
|
|
1789
|
-
*
|
|
1790
|
-
* ```html
|
|
1791
|
-
* @if (loadEntity$ | async) {
|
|
1792
|
-
* <form [formGroup]='form'.. (ngSubmit)="onSubmit()">
|
|
1793
|
-
* <button type="submit">Submit</button>
|
|
1794
|
-
* </form>
|
|
1795
|
-
* } @else {
|
|
1796
|
-
* <div>Loading...</div>
|
|
1797
|
-
* }
|
|
1798
|
-
* ```
|
|
1799
|
-
*
|
|
1800
|
-
* Here `loadEntity$` is an Observable<boolean> that upon emission of `true`
|
|
1801
|
-
* indicates that the entity has been loaded from server (in case of edit)
|
|
1802
|
-
* and the form is ready to be displayed. Note that if the full entity was
|
|
1803
|
-
* passed in the `entity` input property, then no server load is necessary
|
|
1804
|
-
* and the form will be created immediately.
|
|
1805
|
-
*
|
|
1806
|
-
* 5. In the parent component that hosts the SPMatEntityCrudComponent, set
|
|
1807
|
-
* the `entity` and `bridge` input properties of this component to
|
|
1808
|
-
* appropriate values. For instance, if your form component has the
|
|
1809
|
-
* selector `app-my-entity-form`, then the parent component's template
|
|
1810
|
-
* will have:
|
|
1811
|
-
*
|
|
1812
|
-
* ```html
|
|
1813
|
-
* <sp-mat-entity-crud
|
|
1814
|
-
* ...
|
|
1815
|
-
* createEditFormTemplate="entityFormTemplate"
|
|
1816
|
-
* ></sp-mat-entity-crud>
|
|
1817
|
-
* <ng-template #entityFormTemplate let-data="data">
|
|
1818
|
-
* <app-my-entity-form
|
|
1819
|
-
* [entity]="data.entity"
|
|
1820
|
-
* [bridge]="data.bridge"
|
|
1821
|
-
* ></app-my-entity-form>
|
|
1822
|
-
* </ng-template>
|
|
1823
|
-
* ```
|
|
1824
|
-
*
|
|
1825
|
-
* II. Standalone mode
|
|
1826
|
-
*
|
|
1827
|
-
* 1..4. Same as above, except set the required `bridge` input to `undefined`.
|
|
1828
|
-
* 5. Initialize the component's inputs `baseUrl` and `entityName` with the
|
|
1829
|
-
* appropriate values. If you would like to pass additional HTTP context to
|
|
1830
|
-
* the HTTP requests, then set the `httpReqContext` input as well.
|
|
1831
|
-
* If the entity uses an id key other than 'id', then set the `idKey` input
|
|
1832
|
-
* to the appropriate id key name.
|
|
1833
|
-
* 6. If you want to retrieve the created/updated entity after the create/update
|
|
1834
|
-
* operation, override the `onPostCreate()` and/or `onPostUpdate()` methods
|
|
1835
|
-
* respectively.
|
|
1836
|
-
*/
|
|
1837
|
-
class SPMatEntityCrudFormBase {
|
|
1838
|
-
entity = input.required();
|
|
1839
|
-
bridge = input.required();
|
|
1840
|
-
params = input();
|
|
1841
|
-
// --- BEGIN inputs used when `bridge` input is undefined
|
|
1842
|
-
// Entity name, which is used to parse sideloaded entity responses
|
|
1843
|
-
entityName = input();
|
|
1844
|
-
// Base CRUD URL, which is the GET-list-of-entities/POST-to-create
|
|
1845
|
-
// URL. Update URL will be derived from this ias `baseUrl()/${TEntity[IdKey]}`
|
|
1846
|
-
baseUrl = input();
|
|
1847
|
-
// Additional request context to be passed to the request
|
|
1848
|
-
httpReqContext = input();
|
|
1849
|
-
// ID key, defaults to 'id'
|
|
1850
|
-
idKey = input('id');
|
|
1851
|
-
// -- END inputs used when `bridge` input is undefined
|
|
1852
|
-
// IMPLEMENTATION
|
|
1853
|
-
loadEntity$;
|
|
1854
|
-
_entity = signal(undefined);
|
|
1855
|
-
sub$ = new Subscription();
|
|
1856
|
-
// Store for internal form signal. form() is computed from this.
|
|
1857
|
-
_form = signal(undefined);
|
|
1858
|
-
// Force typecast to TFormGroup so that we can use it in the template
|
|
1859
|
-
// without having to use the non-nullable operator ! with every reference
|
|
1860
|
-
// of form(). In any case the form() signal is always set in ngOnInit()
|
|
1861
|
-
// method after the form is created. And if form() is not set, then there
|
|
1862
|
-
// will be errors while loading the form in the template.
|
|
1863
|
-
form = computed(() => this._form());
|
|
1864
|
-
transloco = inject(TranslocoService);
|
|
1865
|
-
cdr = inject(ChangeDetectorRef);
|
|
1866
|
-
http = inject(HttpClient);
|
|
1867
|
-
canCancelEdit = () => {
|
|
1868
|
-
return this._canCancelEdit();
|
|
1869
|
-
};
|
|
1870
|
-
_canCancelEdit() {
|
|
1871
|
-
const form = this._form();
|
|
1872
|
-
if (form && form.touched) {
|
|
1873
|
-
return window.confirm(this.transloco.translate('spMatEntityCrud.loseChangesConfirm'));
|
|
1874
|
-
}
|
|
1875
|
-
return true;
|
|
1876
|
-
}
|
|
1877
|
-
ngOnInit() {
|
|
1878
|
-
// validate inputs. Either bridge or (baseUrl and entityName) must be
|
|
1879
|
-
// defined.
|
|
1880
|
-
if (!this.bridge() && (!this.baseUrl() || !this.entityName())) {
|
|
1881
|
-
throw new Error('SPMatEntityCrudFormBase: baseUrl and entityName inputs must be defined in standalone mode.');
|
|
1882
|
-
}
|
|
1883
|
-
this.loadEntity$ = (typeof this.entity() === 'object' || this.entity() === undefined
|
|
1884
|
-
? new Observable((subscriber) => {
|
|
1885
|
-
subscriber.next(this.entity());
|
|
1886
|
-
subscriber.complete();
|
|
1887
|
-
})
|
|
1888
|
-
: this.load(this.entity())).pipe(map((resp) => {
|
|
1889
|
-
const compositeEntity = this.getEntityFromLoadResponse(resp);
|
|
1890
|
-
this._entity.set(compositeEntity);
|
|
1891
|
-
this._form.set(this.createForm(compositeEntity));
|
|
1892
|
-
const bridge = this.bridge();
|
|
1893
|
-
if (bridge && bridge.registerCanCancelEditCallback) {
|
|
1894
|
-
bridge.registerCanCancelEditCallback(this.canCancelEdit);
|
|
1895
|
-
}
|
|
1896
|
-
return true;
|
|
1897
|
-
}));
|
|
1898
|
-
}
|
|
1899
|
-
ngOnDestroy() {
|
|
1900
|
-
this.sub$.unsubscribe();
|
|
1901
|
-
}
|
|
1902
|
-
/**
|
|
1903
|
-
* Additional parameters for loading the entity, in case this.entity() value
|
|
1904
|
-
* is of type TEntity[IdKey].
|
|
1905
|
-
* @returns
|
|
1906
|
-
*/
|
|
1907
|
-
getLoadEntityParams() {
|
|
1908
|
-
return '';
|
|
1909
|
-
}
|
|
1910
|
-
/**
|
|
1911
|
-
* Return the TEntity object from the response returned by the
|
|
1912
|
-
* load() method. Typically entity load returns the actual
|
|
1913
|
-
* entity object itself. In some cases, where response is sideloaded, the
|
|
1914
|
-
* default implementation here uses the `sideloadToComposite()` utility to
|
|
1915
|
-
* extract the entity from the response after merging (inplace) the
|
|
1916
|
-
* sideloaded data into a composite.
|
|
1917
|
-
*
|
|
1918
|
-
* If you have a different response shape, or if your sideloaded object
|
|
1919
|
-
* response requires custom custom `sideloadDataMap`, override this method
|
|
1920
|
-
* and implement your custom logic to extract the TEntity object from the
|
|
1921
|
-
* response.
|
|
1922
|
-
* @param resp
|
|
1923
|
-
* @returns
|
|
1924
|
-
*/
|
|
1925
|
-
getEntityFromLoadResponse(resp) {
|
|
1926
|
-
if (!resp || typeof resp !== 'object') {
|
|
1927
|
-
return undefined;
|
|
1928
|
-
}
|
|
1929
|
-
const entityName = this.entityName();
|
|
1930
|
-
if (resp.hasOwnProperty(this.getIdKey())) {
|
|
1931
|
-
return resp;
|
|
1932
|
-
}
|
|
1933
|
-
else if (entityName && resp.hasOwnProperty(entityName)) {
|
|
1934
|
-
// const sideloadDataMap = this.sideloadDataMap();
|
|
1935
|
-
return sideloadToComposite(resp, this.entityName(), this.getIdKey());
|
|
1936
|
-
}
|
|
1937
|
-
return undefined;
|
|
1938
|
-
}
|
|
1939
|
-
/**
|
|
1940
|
-
* Override to customize the id key name if it's not 'id'
|
|
1941
|
-
* @returns The name of the unique identifier key that will be used to
|
|
1942
|
-
* extract the entity's id for UPDATE operation.
|
|
1943
|
-
*/
|
|
1944
|
-
getIdKey() {
|
|
1945
|
-
const bridge = this.bridge();
|
|
1946
|
-
if (bridge) {
|
|
1947
|
-
return bridge.getIdKey();
|
|
1948
|
-
}
|
|
1949
|
-
return this.idKey();
|
|
1950
|
-
}
|
|
1951
|
-
/**
|
|
1952
|
-
* Return the form's value to be sent to server as Create/Update CRUD
|
|
1953
|
-
* operation data.
|
|
1954
|
-
* @returns
|
|
1955
|
-
*/
|
|
1956
|
-
getFormValue() {
|
|
1957
|
-
const form = this.form();
|
|
1958
|
-
return form ? form.value : undefined;
|
|
1959
|
-
}
|
|
1960
|
-
onSubmit() {
|
|
1961
|
-
const value = this.getFormValue();
|
|
1962
|
-
const obs = !this._entity()
|
|
1963
|
-
? this.create(value)
|
|
1964
|
-
: this.update(this._entity()[this.getIdKey()], value);
|
|
1965
|
-
this.sub$.add(obs
|
|
1966
|
-
?.pipe(tap(entity => this._entity() ? this.onPostUpdate(entity) : this.onPostCreate(entity)), setServerErrorsAsFormErrors(this._form(), this.cdr))
|
|
1967
|
-
.subscribe());
|
|
1968
|
-
}
|
|
1969
|
-
onPostCreate(entity) {
|
|
1970
|
-
/* empty */
|
|
1971
|
-
}
|
|
1972
|
-
onPostUpdate(entity) {
|
|
1973
|
-
/* empty */
|
|
1974
|
-
}
|
|
1975
|
-
/**
|
|
1976
|
-
* Loads the entity if `this.entity()` is of type TEntity[IdKey]. If `bridge`
|
|
1977
|
-
* input is defined, then it's `loadEntity()` method is used to load the
|
|
1978
|
-
* entity. Otherwise, then this method attempts to load the entity using
|
|
1979
|
-
* HTTP GET from the URL derived from `baseUrl` input.
|
|
1980
|
-
* @param entityId
|
|
1981
|
-
* @param params
|
|
1982
|
-
* @returns
|
|
1983
|
-
*/
|
|
1984
|
-
load(entityId) {
|
|
1985
|
-
const bridge = this.bridge();
|
|
1986
|
-
const params = this.getLoadEntityParams();
|
|
1987
|
-
if (bridge) {
|
|
1988
|
-
return bridge.loadEntity(entityId, params);
|
|
1989
|
-
}
|
|
1990
|
-
// Try to load using baseUrl.
|
|
1991
|
-
if (!this.baseUrl()) {
|
|
1992
|
-
console.warn(`SPMatEntityCrudFormBase.load: No bridge defined, baseUrl input is undefined. Returning undefined.`);
|
|
1993
|
-
return new Observable((subscriber) => {
|
|
1994
|
-
subscriber.next(undefined);
|
|
1995
|
-
subscriber.complete();
|
|
1996
|
-
});
|
|
1997
|
-
}
|
|
1998
|
-
let context = new HttpContext();
|
|
1999
|
-
if (this.httpReqContext()) {
|
|
2000
|
-
context = convertHttpContextInputToHttpContext(context, this.httpReqContext());
|
|
2001
|
-
}
|
|
2002
|
-
const url = this.getEntityUrl(entityId);
|
|
2003
|
-
return this.http
|
|
2004
|
-
.get(this.getEntityUrl(entityId), {
|
|
2005
|
-
params: typeof params === 'string'
|
|
2006
|
-
? new HttpParams({ fromString: params })
|
|
2007
|
-
: params,
|
|
2008
|
-
context: context,
|
|
2009
|
-
})
|
|
2010
|
-
.pipe(map((resp) => this.getEntityFromLoadResponse(resp)));
|
|
2011
|
-
}
|
|
2012
|
-
/**
|
|
2013
|
-
* Create a new entity using the bridge if defined, otherwise using HTTP
|
|
2014
|
-
* POST to the `baseUrl`.
|
|
2015
|
-
* @param values
|
|
2016
|
-
* @returns
|
|
2017
|
-
*/
|
|
2018
|
-
create(values) {
|
|
2019
|
-
const bridge = this.bridge();
|
|
2020
|
-
if (bridge) {
|
|
2021
|
-
return bridge.create(values);
|
|
2022
|
-
}
|
|
2023
|
-
const url = this.baseUrl();
|
|
2024
|
-
if (!url) {
|
|
2025
|
-
console.warn('SPMatEntityCrudFormBase.create: Cannot create entity as neither bridge nor baseUrl inputs are provided.');
|
|
2026
|
-
return of(undefined);
|
|
2027
|
-
}
|
|
2028
|
-
const httpReqContext = this.httpReqContext();
|
|
2029
|
-
let context = new HttpContext();
|
|
2030
|
-
if (httpReqContext) {
|
|
2031
|
-
context = convertHttpContextInputToHttpContext(context, httpReqContext);
|
|
2032
|
-
}
|
|
2033
|
-
return this.http
|
|
2034
|
-
.post(url, values, { context: context })
|
|
2035
|
-
.pipe(map((resp) => this.getEntityFromLoadResponse(resp)));
|
|
2036
|
-
}
|
|
2037
|
-
/**
|
|
2038
|
-
* Update an existing entity using the bridge if defined, otherwise using HTTP
|
|
2039
|
-
* PATCH to the URL derived from `baseUrl` and the entity id.
|
|
2040
|
-
* @param id
|
|
2041
|
-
* @param values
|
|
2042
|
-
* @returns
|
|
2043
|
-
*/
|
|
2044
|
-
update(id, values) {
|
|
2045
|
-
const bridge = this.bridge();
|
|
2046
|
-
if (bridge) {
|
|
2047
|
-
return bridge.update(id, values);
|
|
2048
|
-
}
|
|
2049
|
-
const url = this.baseUrl();
|
|
2050
|
-
if (!url) {
|
|
2051
|
-
console.warn('SPMatEntityCrudFormBase.update: Cannot update entity as neither bridge nor baseUrl inputs are provided.');
|
|
2052
|
-
return of(undefined);
|
|
2053
|
-
}
|
|
2054
|
-
return this.http
|
|
2055
|
-
.patch(this.getEntityUrl(id), values)
|
|
2056
|
-
.pipe(map((resp) => this.getEntityFromLoadResponse(resp)));
|
|
2057
|
-
}
|
|
2058
|
-
getEntityUrl(entityId) {
|
|
2059
|
-
const bridge = this.bridge();
|
|
2060
|
-
if (bridge) {
|
|
2061
|
-
return bridge.getEntityUrl(entityId);
|
|
2062
|
-
}
|
|
2063
|
-
const baseUrl = this.baseUrl();
|
|
2064
|
-
if (baseUrl) {
|
|
2065
|
-
const urlParts = baseUrl.split('?');
|
|
2066
|
-
return `${urlParts[0]}${String(entityId)}/${urlParts[1] ? '?' + urlParts[1] : ''}`;
|
|
2067
|
-
}
|
|
2068
|
-
console.warn('SPMatEntityCrudFormBase.getEntityUrl: Cannot determine entity URL as neither baseUrl nor bridge inputs are provided.');
|
|
2069
|
-
return '';
|
|
2070
|
-
}
|
|
2071
|
-
/** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: SPMatEntityCrudFormBase, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2072
|
-
/** @nocollapse */ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.1.6", type: SPMatEntityCrudFormBase, isStandalone: false, selector: "_#_sp-mat-entity-crud-form-base_#_", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, bridge: { classPropertyName: "bridge", publicName: "bridge", isSignal: true, isRequired: true, transformFunction: null }, params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null }, entityName: { classPropertyName: "entityName", publicName: "entityName", isSignal: true, isRequired: false, transformFunction: null }, baseUrl: { classPropertyName: "baseUrl", publicName: "baseUrl", isSignal: true, isRequired: false, transformFunction: null }, httpReqContext: { classPropertyName: "httpReqContext", publicName: "httpReqContext", isSignal: true, isRequired: false, transformFunction: null }, idKey: { classPropertyName: "idKey", publicName: "idKey", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: ``, isInline: true });
|
|
2073
|
-
}
|
|
2074
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: SPMatEntityCrudFormBase, decorators: [{
|
|
2075
|
-
type: Component,
|
|
2076
|
-
args: [{
|
|
2077
|
-
selector: '_#_sp-mat-entity-crud-form-base_#_',
|
|
2078
|
-
template: ``,
|
|
2079
|
-
standalone: false,
|
|
2080
|
-
}]
|
|
2081
|
-
}] });
|
|
2082
|
-
|
|
2083
2107
|
/**
|
|
2084
2108
|
* Generated bundle index. Do not edit.
|
|
2085
2109
|
*/
|
|
2086
2110
|
|
|
2087
|
-
export { SPMatEntityCrudComponent, SPMatEntityCrudFormBase, SPMatEntityCrudPreviewPaneComponent, SP_MAT_ENTITY_CRUD_CONFIG, SP_MAT_ENTITY_CRUD_HTTP_CONTEXT };
|
|
2111
|
+
export { SPMatEntityCrudComponent, SPMatEntityCrudFormBase, SPMatEntityCrudPreviewPaneComponent, SP_MAT_ENTITY_CRUD_CONFIG, SP_MAT_ENTITY_CRUD_HTTP_CONTEXT, convertHttpContextInputToHttpContext };
|
|
2088
2112
|
//# sourceMappingURL=smallpearl-ngx-helper-mat-entity-crud.mjs.map
|