@smallpearl/ngx-helper 0.33.26 → 0.33.29

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.
@@ -1,7 +1,12 @@
1
1
  import * as i1 from '@angular/common/http';
2
- import { HttpContextToken, HttpParams, HttpContext, HttpClient } from '@angular/common/http';
2
+ import { HttpContext, HttpClient, HttpParams, HttpContextToken } from '@angular/common/http';
3
3
  import * as i0 from '@angular/core';
4
- import { InjectionToken, inject, input, computed, signal, viewChild, ViewContainerRef, Component, ChangeDetectionStrategy, viewChildren, EventEmitter, effect, ContentChildren, Output, ChangeDetectorRef } from '@angular/core';
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,17 +26,405 @@ 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
+ /**
37
+ * Converts array of HttpContextToken key, value pairs to HttpContext
38
+ * object in argument 'context'.
39
+ * @param context HTTP context to which the key, value pairs are added
40
+ * @param reqContext HttpContextToken key, value pairs array
41
+ * @returns HttpContext object, with the key, value pairs added. This is
42
+ * the same object as the 'context' argument.
43
+ */
44
+ function convertHttpContextInputToHttpContext(context, reqContext) {
45
+ if (reqContext instanceof HttpContext) {
46
+ // reqContext is already an HttpContext object.
47
+ for (const k of reqContext.keys()) {
48
+ context.set(k, reqContext.get(k));
49
+ }
50
+ }
51
+ else if (Array.isArray(reqContext) && reqContext.length == 2 && !Array.isArray(reqContext[0])) {
52
+ // one dimensional array of a key, value pair.
53
+ context.set(reqContext[0], reqContext[1]);
54
+ }
55
+ else {
56
+ reqContext.forEach(([k, v]) => context.set(k, v));
57
+ }
58
+ return context;
59
+ }
60
+
61
+ /**
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.
181
+ */
182
+ class SPMatEntityCrudFormBase {
183
+ entity = input.required();
184
+ bridge = input.required();
185
+ params = input();
186
+ // --- BEGIN inputs used when `bridge` input is undefined
187
+ // Entity name, which is used to parse sideloaded entity responses
188
+ entityName = input();
189
+ // Base CRUD URL, which is the GET-list-of-entities/POST-to-create
190
+ // URL. Update URL will be derived from this ias `baseUrl()/${TEntity[IdKey]}`
191
+ baseUrl = input();
192
+ // Additional request context to be passed to the request
193
+ httpReqContext = input();
194
+ // ID key, defaults to 'id'
195
+ idKey = input('id');
196
+ // -- END inputs used when `bridge` input is undefined
197
+ // IMPLEMENTATION
198
+ loadEntity$;
199
+ _entity = signal(undefined);
200
+ sub$ = new Subscription();
201
+ // Store for internal form signal. form() is computed from this.
202
+ _form = signal(undefined);
203
+ // Force typecast to TFormGroup so that we can use it in the template
204
+ // without having to use the non-nullable operator ! with every reference
205
+ // of form(). In any case the form() signal is always set in ngOnInit()
206
+ // method after the form is created. And if form() is not set, then there
207
+ // will be errors while loading the form in the template.
208
+ form = computed(() => this._form());
209
+ transloco = inject(TranslocoService);
210
+ cdr = inject(ChangeDetectorRef);
211
+ http = inject(HttpClient);
212
+ canCancelEdit = () => {
213
+ return this._canCancelEdit();
214
+ };
215
+ _canCancelEdit() {
216
+ const form = this._form();
217
+ if (form && form.touched) {
218
+ return window.confirm(this.transloco.translate('spMatEntityCrud.loseChangesConfirm'));
219
+ }
220
+ return true;
221
+ }
222
+ ngOnInit() {
223
+ // validate inputs. Either bridge or (baseUrl and entityName) must be
224
+ // defined.
225
+ if (!this.bridge() && (!this.baseUrl() || !this.entityName())) {
226
+ throw new Error('SPMatEntityCrudFormBase: baseUrl and entityName inputs must be defined in standalone mode.');
227
+ }
228
+ this.loadEntity$ = (typeof this.entity() === 'object' || this.entity() === undefined
229
+ ? new Observable((subscriber) => {
230
+ subscriber.next(this.entity());
231
+ subscriber.complete();
232
+ })
233
+ : this.load(this.entity())).pipe(map((resp) => {
234
+ const compositeEntity = this.getEntityFromLoadResponse(resp);
235
+ this._entity.set(compositeEntity);
236
+ this._form.set(this.createForm(compositeEntity));
237
+ const bridge = this.bridge();
238
+ if (bridge && bridge.registerCanCancelEditCallback) {
239
+ bridge.registerCanCancelEditCallback(this.canCancelEdit);
240
+ }
241
+ return true;
242
+ }));
243
+ }
244
+ ngOnDestroy() {
245
+ this.sub$.unsubscribe();
246
+ }
247
+ /**
248
+ * Additional parameters for loading the entity, in case this.entity() value
249
+ * is of type TEntity[IdKey].
250
+ * @returns
251
+ */
252
+ getLoadEntityParams() {
253
+ return '';
254
+ }
255
+ /**
256
+ * Return the TEntity object from the response returned by the
257
+ * load() method. Typically entity load returns the actual
258
+ * entity object itself. In some cases, where response is sideloaded, the
259
+ * default implementation here uses the `sideloadToComposite()` utility to
260
+ * extract the entity from the response after merging (inplace) the
261
+ * sideloaded data into a composite.
262
+ *
263
+ * If you have a different response shape, or if your sideloaded object
264
+ * response requires custom custom `sideloadDataMap`, override this method
265
+ * and implement your custom logic to extract the TEntity object from the
266
+ * response.
267
+ * @param resp
268
+ * @returns
269
+ */
270
+ getEntityFromLoadResponse(resp) {
271
+ if (!resp || typeof resp !== 'object') {
272
+ return undefined;
273
+ }
274
+ const entityName = this.entityName();
275
+ if (resp.hasOwnProperty(this.getIdKey())) {
276
+ return resp;
277
+ }
278
+ else if (entityName && resp.hasOwnProperty(entityName)) {
279
+ // const sideloadDataMap = this.sideloadDataMap();
280
+ return sideloadToComposite(resp, this.entityName(), this.getIdKey());
281
+ }
282
+ return undefined;
283
+ }
284
+ /**
285
+ * Override to customize the id key name if it's not 'id'
286
+ * @returns The name of the unique identifier key that will be used to
287
+ * extract the entity's id for UPDATE operation.
288
+ */
289
+ getIdKey() {
290
+ const bridge = this.bridge();
291
+ if (bridge) {
292
+ return bridge.getIdKey();
293
+ }
294
+ return this.idKey();
295
+ }
296
+ /**
297
+ * Return the form's value to be sent to server as Create/Update CRUD
298
+ * operation data.
299
+ * @returns
300
+ */
301
+ getFormValue() {
302
+ const form = this.form();
303
+ return form ? form.value : undefined;
304
+ }
305
+ onSubmit() {
306
+ const value = this.getFormValue();
307
+ const obs = !this._entity()
308
+ ? this.create(value)
309
+ : this.update(this._entity()[this.getIdKey()], value);
310
+ this.sub$.add(obs
311
+ ?.pipe(tap(entity => this._entity() ? this.onPostUpdate(entity) : this.onPostCreate(entity)), setServerErrorsAsFormErrors(this._form(), this.cdr))
312
+ .subscribe());
313
+ }
314
+ onPostCreate(entity) {
315
+ /* empty */
316
+ }
317
+ onPostUpdate(entity) {
318
+ /* empty */
319
+ }
320
+ /**
321
+ * Loads the entity if `this.entity()` is of type TEntity[IdKey]. If `bridge`
322
+ * input is defined, then it's `loadEntity()` method is used to load the
323
+ * entity. Otherwise, then this method attempts to load the entity using
324
+ * HTTP GET from the URL derived from `baseUrl` input.
325
+ * @param entityId
326
+ * @param params
327
+ * @returns
328
+ */
329
+ load(entityId) {
330
+ const bridge = this.bridge();
331
+ const params = this.getLoadEntityParams();
332
+ if (bridge) {
333
+ return bridge.loadEntity(entityId, params);
334
+ }
335
+ // Try to load using baseUrl.
336
+ if (!this.baseUrl()) {
337
+ console.warn(`SPMatEntityCrudFormBase.load: No bridge defined, baseUrl input is undefined. Returning undefined.`);
338
+ return new Observable((subscriber) => {
339
+ subscriber.next(undefined);
340
+ subscriber.complete();
341
+ });
342
+ }
343
+ const url = this.getEntityUrl(entityId);
344
+ return this.http
345
+ .get(this.getEntityUrl(entityId), {
346
+ params: typeof params === 'string'
347
+ ? new HttpParams({ fromString: params })
348
+ : params,
349
+ context: this.getRequestContext(),
350
+ })
351
+ .pipe(map((resp) => this.getEntityFromLoadResponse(resp)));
352
+ }
353
+ /**
354
+ * Create a new entity using the bridge if defined, otherwise using HTTP
355
+ * POST to the `baseUrl`.
356
+ * @param values
357
+ * @returns
358
+ */
359
+ create(values) {
360
+ const bridge = this.bridge();
361
+ if (bridge) {
362
+ return bridge.create(values);
363
+ }
364
+ const url = this.baseUrl();
365
+ if (!url) {
366
+ console.warn('SPMatEntityCrudFormBase.create: Cannot create entity as neither bridge nor baseUrl inputs are provided.');
367
+ return of(undefined);
368
+ }
369
+ return this.http
370
+ .post(url, values, { context: this.getRequestContext() })
371
+ .pipe(map((resp) => this.getEntityFromLoadResponse(resp)));
372
+ }
373
+ /**
374
+ * Update an existing entity using the bridge if defined, otherwise using HTTP
375
+ * PATCH to the URL derived from `baseUrl` and the entity id.
376
+ * @param id
377
+ * @param values
378
+ * @returns
379
+ */
380
+ update(id, values) {
381
+ const bridge = this.bridge();
382
+ if (bridge) {
383
+ return bridge.update(id, values);
384
+ }
385
+ const url = this.baseUrl();
386
+ if (!url) {
387
+ console.warn('SPMatEntityCrudFormBase.update: Cannot update entity as neither bridge nor baseUrl inputs are provided.');
388
+ return of(undefined);
389
+ }
390
+ return this.http
391
+ .patch(this.getEntityUrl(id), values, {
392
+ context: this.getRequestContext(),
393
+ })
394
+ .pipe(map((resp) => this.getEntityFromLoadResponse(resp)));
395
+ }
396
+ getEntityUrl(entityId) {
397
+ const bridge = this.bridge();
398
+ if (bridge) {
399
+ return bridge.getEntityUrl(entityId);
400
+ }
401
+ const baseUrl = this.baseUrl();
402
+ if (baseUrl) {
403
+ const urlParts = baseUrl.split('?');
404
+ return `${urlParts[0]}${String(entityId)}/${urlParts[1] ? '?' + urlParts[1] : ''}`;
405
+ }
406
+ console.warn('SPMatEntityCrudFormBase.getEntityUrl: Cannot determine entity URL as neither baseUrl nor bridge inputs are provided.');
407
+ return '';
408
+ }
409
+ getRequestContext() {
410
+ let context = new HttpContext();
411
+ const httpReqContext = this.httpReqContext();
412
+ if (httpReqContext) {
413
+ context = convertHttpContextInputToHttpContext(context, httpReqContext);
414
+ }
415
+ return context;
416
+ }
417
+ /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: SPMatEntityCrudFormBase, deps: [], target: i0.ɵɵFactoryTarget.Component });
418
+ /** @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 });
419
+ }
420
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: SPMatEntityCrudFormBase, decorators: [{
421
+ type: Component,
422
+ args: [{
423
+ selector: '_#_sp-mat-entity-crud-form-base_#_',
424
+ template: ``,
425
+ standalone: false,
426
+ }]
427
+ }] });
35
428
 
36
429
  const SP_MAT_ENTITY_CRUD_HTTP_CONTEXT = new HttpContextToken(() => ({
37
430
  entityName: '',
@@ -1084,30 +1477,6 @@ class SPMatEntityCrudComponent extends SPMatEntityListComponent {
1084
1477
  }
1085
1478
  }
1086
1479
  getCrudReqHttpContext(op) {
1087
- /**
1088
- * Converts array of HttpContextToken key, value pairs to HttpContext
1089
- * object in argument 'context'.
1090
- * @param context HTTP context to which the key, value pairs are added
1091
- * @param reqContext HttpContextToken key, value pairs array
1092
- * @returns HttpContext object, with the key, value pairs added. This is
1093
- * the same object as the 'context' argument.
1094
- */
1095
- const contextParamToHttpContext = (context, reqContext) => {
1096
- if (reqContext instanceof HttpContext) {
1097
- // reqContext is already an HttpContext object.
1098
- for (const k of reqContext.keys()) {
1099
- context.set(k, reqContext.get(k));
1100
- }
1101
- }
1102
- else if (reqContext.length == 2 && !Array.isArray(reqContext[0])) {
1103
- // one dimensional array of a key, value pair.
1104
- context.set(reqContext[0], reqContext[1]);
1105
- }
1106
- else {
1107
- reqContext.forEach(([k, v]) => context.set(k, v));
1108
- }
1109
- return context;
1110
- };
1111
1480
  let context = new HttpContext();
1112
1481
  // HttpContext for crud operations are taken from either the global httpReqContext
1113
1482
  // or from the crudHttpReqContext, with the latter taking precedence.
@@ -1126,13 +1495,13 @@ class SPMatEntityCrudComponent extends SPMatEntityListComponent {
1126
1495
  if (Array.isArray(crudHttpReqContext)) {
1127
1496
  // Same HttpContext for all crud requests. Being an array, it must
1128
1497
  // be an array of HttpContextToken key, value pairs.
1129
- contextParamToHttpContext(context, crudHttpReqContext);
1498
+ convertHttpContextInputToHttpContext(context, crudHttpReqContext);
1130
1499
  }
1131
1500
  else if (typeof crudHttpReqContext === 'object' &&
1132
1501
  op &&
1133
1502
  Object.keys(crudHttpReqContext).find((k) => k === op)) {
1134
1503
  // HttpContext specific to this crud operation, 'create'|'retrieve'|'update'|'delete'
1135
- contextParamToHttpContext(context, crudHttpReqContext[op]);
1504
+ convertHttpContextInputToHttpContext(context, crudHttpReqContext[op]);
1136
1505
  }
1137
1506
  }
1138
1507
  }
@@ -1712,188 +2081,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImpor
1712
2081
  `, 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"] }]
1713
2082
  }] });
1714
2083
 
1715
- /**
1716
- * This is a convenience base class that clients can derive from to implement
1717
- * their CRUD form component. Particularly this class registers the change
1718
- * detection hook which will be called when the user attempts to close the
1719
- * form's parent container pane via the Close button on the top right.
1720
- *
1721
- * This button behaves like a Cancel button in a desktop app and therefore if
1722
- * the user has entered any data in the form's controls, (determined by
1723
- * checking form.touched), then a 'Lose Changes' prompt is displayed allowing
1724
- * the user to cancel the closure.
1725
- *
1726
- * The @Component is fake just to keep the VSCode angular linter quiet.
1727
- *
1728
- * To use this class:-
1729
- *
1730
- * 1. Declare a FormGroup<> type as
1731
- *
1732
- * ```
1733
- * type MyForm = FormGroup<{
1734
- * name: FormControl<string>;
1735
- * type: FormControl<string>;
1736
- * notes: FormControl<string>;
1737
- * }>;
1738
- * ```
1739
- *
1740
- * 2. Derive your form's component class from this and implement the
1741
- * createForm() method returing the FormGroup<> instance that matches
1742
- * the FormGroup concrete type above.
1743
- *
1744
- * ```
1745
- * class MyFormComponent extends SPMatEntityCrudFormBase<MyForm, MyEntity> {
1746
- * constructor() {
1747
- * super()
1748
- * }
1749
- *
1750
- * createForm() {
1751
- * return new FormGroup([...])
1752
- * }
1753
- * }
1754
- * ```
1755
- *
1756
- * 3. If you form's value requires manipulation before being sent to the
1757
- * server, override `getFormValue()` method and do it there before returning
1758
- * the modified values.
1759
- *
1760
- * 4. Wire up the form in the template as:
1761
- *
1762
- * ```
1763
- * @if (loadEntity$ | async) {
1764
- * <form [formGroup]='form'.. (ngSubmit)="onSubmit()">
1765
- * <button type="submit">Submit</button>
1766
- * </form>
1767
- * } @else {
1768
- * <div>Loading...</div>
1769
- * }
1770
- * ```
1771
- * Here `loadEntity$` is an Observable<boolean> that upon emission of `true`
1772
- * indicates that the entity has been loaded from server (in case of edit)
1773
- * and the form is ready to be displayed. Note that if the full entity was
1774
- * passed in the `entity` input property, then no server load is necessary
1775
- * and the form will be created immediately.
1776
- *
1777
- * 5. If the entity shape required by the form requires additional parameters
1778
- * to be loaded from server, initialize `entity` property with it's id.
1779
- * Then override the `getLoadEntityParams()` method to return the additional
1780
- * load parameters. The parameters returned by this method will be
1781
- * passed to the `loadEntity()` method of the bridge interface.
1782
- */
1783
- class SPMatEntityCrudFormBase {
1784
- _form = signal(undefined);
1785
- entity = input.required();
1786
- bridge = input.required();
1787
- params = input();
1788
- loadEntity$;
1789
- _entity = signal(undefined);
1790
- sub$ = new Subscription();
1791
- // Force typecast to TFormGroup so that we can use it in the template
1792
- // without having to use the non-nullable operator ! with every reference
1793
- // of form(). In any case the form() signal is always set in ngOnInit()
1794
- // method after the form is created. And if form() is not set, then there
1795
- // will be errors while loading the form in the template.
1796
- form = computed(() => this._form());
1797
- // crudConfig = getEntityCrudConfig();
1798
- transloco = inject(TranslocoService);
1799
- cdr = inject(ChangeDetectorRef);
1800
- http = inject(HttpClient);
1801
- canCancelEdit = () => {
1802
- return this._canCancelEdit();
1803
- };
1804
- _canCancelEdit() {
1805
- const form = this._form();
1806
- if (form && form.touched) {
1807
- return window.confirm(this.transloco.translate('spMatEntityCrud.loseChangesConfirm'));
1808
- }
1809
- return true;
1810
- }
1811
- ngOnInit() {
1812
- this.loadEntity$ = (typeof this.entity() === 'object' || this.entity() === undefined
1813
- ? new Observable((subscriber) => {
1814
- subscriber.next(this.entity());
1815
- subscriber.complete();
1816
- })
1817
- : this.bridge()?.loadEntity(this.entity(), this.getLoadEntityParams())).pipe(map((resp) => {
1818
- const compositeEntity = this.getEntityFromLoadResponse(resp);
1819
- this._entity.set(compositeEntity);
1820
- this._form.set(this.createForm(compositeEntity));
1821
- this.bridge()?.registerCanCancelEditCallback(this.canCancelEdit);
1822
- return true;
1823
- }));
1824
- }
1825
- ngOnDestroy() {
1826
- this.sub$.unsubscribe();
1827
- }
1828
- /**
1829
- * Additional parameters for loading the entity, in case this.entity() value
1830
- * is of type TEntity[IdKey].
1831
- * @returns
1832
- */
1833
- getLoadEntityParams() {
1834
- return '';
1835
- }
1836
- /**
1837
- * Return the TEntity object from the response returned by the
1838
- * loadEntity() method of the bridge. Typically entity load return the actual
1839
- * entity object itself. In some cases, where response is sideloaded, the
1840
- * default implementation here uses the `sideloadToComposite()` utility to
1841
- * extract the entity from the response after merging (inplace) the
1842
- * sideloaded data into a composite.
1843
- *
1844
- * If you have a different response shape, override this method to
1845
- * extract the TEntity object from the response.
1846
- * @param resp
1847
- * @returns
1848
- */
1849
- getEntityFromLoadResponse(resp) {
1850
- if (!resp) {
1851
- return undefined;
1852
- }
1853
- const sideloaded = sideloadToComposite(resp, this.bridge().getEntityName(), this.bridge().getIdKey());
1854
- return sideloaded;
1855
- }
1856
- /**
1857
- * Override to customize the id key name if it's not 'id'
1858
- * @returns The name of the unique identifier key that will be used to
1859
- * extract the entity's id for UPDATE operation.
1860
- */
1861
- getIdKey() {
1862
- return 'id';
1863
- }
1864
- /**
1865
- * Return the form's value to be sent to server as Create/Update CRUD
1866
- * operation data.
1867
- * @returns
1868
- */
1869
- getFormValue() {
1870
- const form = this.form();
1871
- return form ? form.value : undefined;
1872
- }
1873
- onSubmit() {
1874
- const value = this.getFormValue();
1875
- const obs = !this.entity()
1876
- ? this.bridge()?.create(value)
1877
- : this.bridge()?.update(this.entity()[this.getIdKey()], value);
1878
- this.sub$.add(obs
1879
- ?.pipe(setServerErrorsAsFormErrors(this._form(), this.cdr))
1880
- .subscribe());
1881
- }
1882
- /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: SPMatEntityCrudFormBase, deps: [], target: i0.ɵɵFactoryTarget.Component });
1883
- /** @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 } }, ngImport: i0, template: ``, isInline: true });
1884
- }
1885
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: SPMatEntityCrudFormBase, decorators: [{
1886
- type: Component,
1887
- args: [{
1888
- selector: '_#_sp-mat-entity-crud-form-base_#_',
1889
- template: ``,
1890
- standalone: false,
1891
- }]
1892
- }] });
1893
-
1894
2084
  /**
1895
2085
  * Generated bundle index. Do not edit.
1896
2086
  */
1897
2087
 
1898
- export { SPMatEntityCrudComponent, SPMatEntityCrudFormBase, SPMatEntityCrudPreviewPaneComponent, SP_MAT_ENTITY_CRUD_CONFIG, SP_MAT_ENTITY_CRUD_HTTP_CONTEXT };
2088
+ export { SPMatEntityCrudComponent, SPMatEntityCrudFormBase, SPMatEntityCrudPreviewPaneComponent, SP_MAT_ENTITY_CRUD_CONFIG, SP_MAT_ENTITY_CRUD_HTTP_CONTEXT, convertHttpContextInputToHttpContext };
1899
2089
  //# sourceMappingURL=smallpearl-ngx-helper-mat-entity-crud.mjs.map