@smallpearl/ngx-helper 0.33.24 → 0.33.28
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 +394 -72
- package/fesm2022/smallpearl-ngx-helper-mat-entity-crud.mjs.map +1 -1
- package/fesm2022/smallpearl-ngx-helper-mat-entity-list.mjs.map +1 -1
- package/mat-entity-crud/src/context-param-to-http-context.d.ts +11 -0
- package/mat-entity-crud/src/form-view-host.component.d.ts +5 -0
- package/mat-entity-crud/src/mat-entity-crud-form-base.d.ts +152 -26
- package/mat-entity-crud/src/mat-entity-crud-internal-types.d.ts +23 -0
- package/mat-entity-crud/src/mat-entity-crud-types.d.ts +29 -4
- package/mat-entity-crud/src/mat-entity-crud.component.d.ts +4 -0
- package/mat-entity-list/src/mat-entity-list.component.d.ts +1 -1
- package/package.json +9 -9
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i1 from '@angular/common/http';
|
|
2
|
-
import { HttpContextToken, HttpParams,
|
|
2
|
+
import { HttpContextToken, HttpContext, HttpParams, HttpClient } from '@angular/common/http';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
4
|
import { InjectionToken, inject, input, computed, signal, viewChild, ViewContainerRef, Component, ChangeDetectionStrategy, viewChildren, EventEmitter, effect, ContentChildren, Output, ChangeDetectorRef } from '@angular/core';
|
|
5
5
|
import * as i4 from '@angular/common';
|
|
@@ -31,6 +31,7 @@ import { Observable, of, Subscription, tap, switchMap, firstValueFrom, map, catc
|
|
|
31
31
|
import * as i1$1 from '@angular/material/toolbar';
|
|
32
32
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
|
33
33
|
import { setServerErrorsAsFormErrors } from '@smallpearl/ngx-helper/forms';
|
|
34
|
+
import { sideloadToComposite } from '@smallpearl/ngx-helper/sideload';
|
|
34
35
|
|
|
35
36
|
const SP_MAT_ENTITY_CRUD_HTTP_CONTEXT = new HttpContextToken(() => ({
|
|
36
37
|
entityName: '',
|
|
@@ -41,6 +42,31 @@ const SP_MAT_ENTITY_CRUD_HTTP_CONTEXT = new HttpContextToken(() => ({
|
|
|
41
42
|
|
|
42
43
|
const SP_MAT_ENTITY_CRUD_CONFIG = new InjectionToken('SPMatEntityCrudConfig');
|
|
43
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Converts array of HttpContextToken key, value pairs to HttpContext
|
|
47
|
+
* object in argument 'context'.
|
|
48
|
+
* @param context HTTP context to which the key, value pairs are added
|
|
49
|
+
* @param reqContext HttpContextToken key, value pairs array
|
|
50
|
+
* @returns HttpContext object, with the key, value pairs added. This is
|
|
51
|
+
* the same object as the 'context' argument.
|
|
52
|
+
*/
|
|
53
|
+
function convertHttpContextInputToHttpContext(context, reqContext) {
|
|
54
|
+
if (reqContext instanceof HttpContext) {
|
|
55
|
+
// reqContext is already an HttpContext object.
|
|
56
|
+
for (const k of reqContext.keys()) {
|
|
57
|
+
context.set(k, reqContext.get(k));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else if (Array.isArray(reqContext) && reqContext.length == 2 && !Array.isArray(reqContext[0])) {
|
|
61
|
+
// one dimensional array of a key, value pair.
|
|
62
|
+
context.set(reqContext[0], reqContext[1]);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
reqContext.forEach(([k, v]) => context.set(k, v));
|
|
66
|
+
}
|
|
67
|
+
return context;
|
|
68
|
+
}
|
|
69
|
+
|
|
44
70
|
function defaultCrudResponseParser(entityName, idKey, method, // 'create' | 'retrieve' | 'update' | 'delete',
|
|
45
71
|
resp) {
|
|
46
72
|
// If the response is an object with a property '<idKey>', return it as
|
|
@@ -120,6 +146,16 @@ class FormViewHostComponent {
|
|
|
120
146
|
this.params.set(params);
|
|
121
147
|
this.createClientView();
|
|
122
148
|
}
|
|
149
|
+
// BEGIN SPMatEntityCrudCreateEditBridge METHODS //
|
|
150
|
+
getEntityName() {
|
|
151
|
+
return this.entityCrudComponentBase().getEntityName();
|
|
152
|
+
}
|
|
153
|
+
getIdKey() {
|
|
154
|
+
return this.entityCrudComponentBase().getIdKey();
|
|
155
|
+
}
|
|
156
|
+
getEntityUrl(entityId) {
|
|
157
|
+
return this.entityCrudComponentBase().getEntityUrl(entityId);
|
|
158
|
+
}
|
|
123
159
|
close(cancel) {
|
|
124
160
|
this.entityCrudComponentBase().closeCreateEdit(cancel);
|
|
125
161
|
// destroy the client's form component
|
|
@@ -150,6 +186,10 @@ class FormViewHostComponent {
|
|
|
150
186
|
?.update(id, entityValue)
|
|
151
187
|
.pipe(tap(() => this.close(false)));
|
|
152
188
|
}
|
|
189
|
+
loadEntity(id, params) {
|
|
190
|
+
return this.entityCrudComponentBase().loadEntity(id, params);
|
|
191
|
+
}
|
|
192
|
+
// END SPMatEntityCrudCreateEditBridge METHODS //
|
|
153
193
|
/**
|
|
154
194
|
* Creates the client view provided via template
|
|
155
195
|
*/
|
|
@@ -184,14 +224,30 @@ class FormViewHostComponent {
|
|
|
184
224
|
}
|
|
185
225
|
/** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: FormViewHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
186
226
|
/** @nocollapse */ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.6", type: FormViewHostComponent, isStandalone: true, selector: "sp-create-edit-entity-host", inputs: { entityCrudComponentBase: { classPropertyName: "entityCrudComponentBase", publicName: "entityCrudComponentBase", isSignal: true, isRequired: true, transformFunction: null }, clientViewTemplate: { classPropertyName: "clientViewTemplate", publicName: "clientViewTemplate", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "vc", first: true, predicate: ["clientFormContainer"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: `
|
|
187
|
-
<div
|
|
188
|
-
|
|
227
|
+
<div
|
|
228
|
+
[class]="
|
|
229
|
+
'sp-mat-crud-form-wrapper ' +
|
|
230
|
+
entityCrudComponentBase().getFormPaneWrapperClass()
|
|
231
|
+
"
|
|
232
|
+
spHostBusyWheel="formBusyWheel"
|
|
233
|
+
*transloco="let t"
|
|
234
|
+
>
|
|
235
|
+
<div
|
|
236
|
+
[class]="
|
|
237
|
+
'sp-mat-crud-form-content ' +
|
|
238
|
+
entityCrudComponentBase().getFormPaneContentClass()
|
|
239
|
+
"
|
|
240
|
+
>
|
|
189
241
|
<div class="create-edit-topbar">
|
|
190
242
|
<div class="title">
|
|
191
243
|
@if (title()) {
|
|
192
|
-
|
|
244
|
+
{{ title() | async }}
|
|
193
245
|
} @else {
|
|
194
|
-
|
|
246
|
+
{{
|
|
247
|
+
t('spMatEntityCrud.' + (entity() ? 'editItem' : 'newItem'), {
|
|
248
|
+
item: (this._itemLabel() | async)
|
|
249
|
+
})
|
|
250
|
+
}}
|
|
195
251
|
}
|
|
196
252
|
</div>
|
|
197
253
|
<div class="spacer"></div>
|
|
@@ -217,14 +273,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImpor
|
|
|
217
273
|
TranslocoModule,
|
|
218
274
|
SPMatHostBusyWheelDirective,
|
|
219
275
|
], selector: 'sp-create-edit-entity-host', template: `
|
|
220
|
-
<div
|
|
221
|
-
|
|
276
|
+
<div
|
|
277
|
+
[class]="
|
|
278
|
+
'sp-mat-crud-form-wrapper ' +
|
|
279
|
+
entityCrudComponentBase().getFormPaneWrapperClass()
|
|
280
|
+
"
|
|
281
|
+
spHostBusyWheel="formBusyWheel"
|
|
282
|
+
*transloco="let t"
|
|
283
|
+
>
|
|
284
|
+
<div
|
|
285
|
+
[class]="
|
|
286
|
+
'sp-mat-crud-form-content ' +
|
|
287
|
+
entityCrudComponentBase().getFormPaneContentClass()
|
|
288
|
+
"
|
|
289
|
+
>
|
|
222
290
|
<div class="create-edit-topbar">
|
|
223
291
|
<div class="title">
|
|
224
292
|
@if (title()) {
|
|
225
|
-
|
|
293
|
+
{{ title() | async }}
|
|
226
294
|
} @else {
|
|
227
|
-
|
|
295
|
+
{{
|
|
296
|
+
t('spMatEntityCrud.' + (entity() ? 'editItem' : 'newItem'), {
|
|
297
|
+
item: (this._itemLabel() | async)
|
|
298
|
+
})
|
|
299
|
+
}}
|
|
228
300
|
}
|
|
229
301
|
</div>
|
|
230
302
|
<div class="spacer"></div>
|
|
@@ -662,6 +734,16 @@ class SPMatEntityCrudComponent extends SPMatEntityListComponent {
|
|
|
662
734
|
refresh(force = false) {
|
|
663
735
|
this.spEntitiesList()?.refresh(force);
|
|
664
736
|
}
|
|
737
|
+
// BEGIN SPMatEntityCrudComponentBase METHODS //
|
|
738
|
+
getEntityName() {
|
|
739
|
+
return this.entityName();
|
|
740
|
+
}
|
|
741
|
+
getEntityNamePlural() {
|
|
742
|
+
return this._entityNamePlural();
|
|
743
|
+
}
|
|
744
|
+
getIdKey() {
|
|
745
|
+
return this.idKey();
|
|
746
|
+
}
|
|
665
747
|
closeCreateEdit(cancelled) {
|
|
666
748
|
this.createEditViewActive.set(false);
|
|
667
749
|
this.entityViewPaneActivated.emit({
|
|
@@ -733,6 +815,22 @@ class SPMatEntityCrudComponent extends SPMatEntityListComponent {
|
|
|
733
815
|
}
|
|
734
816
|
}));
|
|
735
817
|
}
|
|
818
|
+
loadEntity(id, params) {
|
|
819
|
+
const crudOpFn = this.crudOpFn();
|
|
820
|
+
if (crudOpFn) {
|
|
821
|
+
return crudOpFn('get', id, undefined, this);
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
const httpParams = params instanceof HttpParams
|
|
825
|
+
? params
|
|
826
|
+
: new HttpParams({ fromString: params });
|
|
827
|
+
return this.http.get(this.getEntityUrl(id), {
|
|
828
|
+
context: this.getCrudReqHttpContext('retrieve'),
|
|
829
|
+
params: httpParams,
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
// END SPMatEntityCrudComponentBase METHODS //
|
|
736
834
|
/**
|
|
737
835
|
* Thunk these methods to the internal <sp-mat-entity-list> component.
|
|
738
836
|
*/
|
|
@@ -834,10 +932,11 @@ class SPMatEntityCrudComponent extends SPMatEntityListComponent {
|
|
|
834
932
|
return;
|
|
835
933
|
}
|
|
836
934
|
}
|
|
837
|
-
this.doEntityAction(entity[this.idKey()], verb, httpRequestParameters.params || new HttpParams(), httpRequestParameters.body)
|
|
935
|
+
this.doEntityAction(entity[this.idKey()], verb, httpRequestParameters.params || new HttpParams(), httpRequestParameters.body)
|
|
936
|
+
.pipe(tap((response) => {
|
|
838
937
|
const successMessage = actionItem?.successMessage ||
|
|
839
938
|
this.transloco.translate('spMatEntityCrud.done');
|
|
840
|
-
this.snackBar.open(successMessage ||
|
|
939
|
+
this.snackBar.open(successMessage || 'Done');
|
|
841
940
|
}), catchError((error) => {
|
|
842
941
|
/**
|
|
843
942
|
* If an errorMessage is specified in the actionItem, display it.
|
|
@@ -849,7 +948,8 @@ class SPMatEntityCrudComponent extends SPMatEntityListComponent {
|
|
|
849
948
|
return EMPTY;
|
|
850
949
|
}
|
|
851
950
|
return throwError(() => error);
|
|
852
|
-
}))
|
|
951
|
+
}))
|
|
952
|
+
.subscribe();
|
|
853
953
|
}
|
|
854
954
|
onCreate(event) {
|
|
855
955
|
// If newItemLink() has not been provided, check if createEditFormTemplate
|
|
@@ -1009,30 +1109,6 @@ class SPMatEntityCrudComponent extends SPMatEntityListComponent {
|
|
|
1009
1109
|
}
|
|
1010
1110
|
}
|
|
1011
1111
|
getCrudReqHttpContext(op) {
|
|
1012
|
-
/**
|
|
1013
|
-
* Converts array of HttpContextToken key, value pairs to HttpContext
|
|
1014
|
-
* object in argument 'context'.
|
|
1015
|
-
* @param context HTTP context to which the key, value pairs are added
|
|
1016
|
-
* @param reqContext HttpContextToken key, value pairs array
|
|
1017
|
-
* @returns HttpContext object, with the key, value pairs added. This is
|
|
1018
|
-
* the same object as the 'context' argument.
|
|
1019
|
-
*/
|
|
1020
|
-
const contextParamToHttpContext = (context, reqContext) => {
|
|
1021
|
-
if (reqContext instanceof HttpContext) {
|
|
1022
|
-
// reqContext is already an HttpContext object.
|
|
1023
|
-
for (const k of reqContext.keys()) {
|
|
1024
|
-
context.set(k, reqContext.get(k));
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
else if (reqContext.length == 2 && !Array.isArray(reqContext[0])) {
|
|
1028
|
-
// one dimensional array of a key, value pair.
|
|
1029
|
-
context.set(reqContext[0], reqContext[1]);
|
|
1030
|
-
}
|
|
1031
|
-
else {
|
|
1032
|
-
reqContext.forEach(([k, v]) => context.set(k, v));
|
|
1033
|
-
}
|
|
1034
|
-
return context;
|
|
1035
|
-
};
|
|
1036
1112
|
let context = new HttpContext();
|
|
1037
1113
|
// HttpContext for crud operations are taken from either the global httpReqContext
|
|
1038
1114
|
// or from the crudHttpReqContext, with the latter taking precedence.
|
|
@@ -1051,12 +1127,13 @@ class SPMatEntityCrudComponent extends SPMatEntityListComponent {
|
|
|
1051
1127
|
if (Array.isArray(crudHttpReqContext)) {
|
|
1052
1128
|
// Same HttpContext for all crud requests. Being an array, it must
|
|
1053
1129
|
// be an array of HttpContextToken key, value pairs.
|
|
1054
|
-
|
|
1130
|
+
convertHttpContextInputToHttpContext(context, crudHttpReqContext);
|
|
1055
1131
|
}
|
|
1056
|
-
else if (typeof crudHttpReqContext === 'object' &&
|
|
1132
|
+
else if (typeof crudHttpReqContext === 'object' &&
|
|
1133
|
+
op &&
|
|
1057
1134
|
Object.keys(crudHttpReqContext).find((k) => k === op)) {
|
|
1058
1135
|
// HttpContext specific to this crud operation, 'create'|'retrieve'|'update'|'delete'
|
|
1059
|
-
|
|
1136
|
+
convertHttpContextInputToHttpContext(context, crudHttpReqContext[op]);
|
|
1060
1137
|
}
|
|
1061
1138
|
}
|
|
1062
1139
|
}
|
|
@@ -1647,11 +1724,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImpor
|
|
|
1647
1724
|
* checking form.touched), then a 'Lose Changes' prompt is displayed allowing
|
|
1648
1725
|
* the user to cancel the closure.
|
|
1649
1726
|
*
|
|
1650
|
-
* The
|
|
1727
|
+
* The `@Component` decorator is fake to keep the VSCode angular linter quiet.
|
|
1651
1728
|
*
|
|
1652
|
-
*
|
|
1729
|
+
* This class can be used in two modes:
|
|
1653
1730
|
*
|
|
1654
|
-
*
|
|
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
|
|
1655
1760
|
*
|
|
1656
1761
|
* ```
|
|
1657
1762
|
* type MyForm = FormGroup<{
|
|
@@ -1661,49 +1766,104 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImpor
|
|
|
1661
1766
|
* }>;
|
|
1662
1767
|
* ```
|
|
1663
1768
|
*
|
|
1664
|
-
*
|
|
1769
|
+
* 2. Derive your form's component class from this and implement the
|
|
1665
1770
|
* createForm() method returing the FormGroup<> instance that matches
|
|
1666
1771
|
* the FormGroup concrete type above.
|
|
1667
1772
|
*
|
|
1668
|
-
*
|
|
1669
|
-
*
|
|
1670
|
-
*
|
|
1671
|
-
*
|
|
1773
|
+
* ```
|
|
1774
|
+
* class MyFormComponent extends SPMatEntityCrudFormBase<MyForm, MyEntity> {
|
|
1775
|
+
* constructor() {
|
|
1776
|
+
* super()
|
|
1777
|
+
* }
|
|
1778
|
+
* createForm() {
|
|
1779
|
+
* return new FormGroup([...])
|
|
1780
|
+
* }
|
|
1672
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
|
|
1673
1789
|
*
|
|
1674
|
-
*
|
|
1675
|
-
*
|
|
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>
|
|
1676
1797
|
* }
|
|
1677
|
-
*
|
|
1678
|
-
* ```
|
|
1798
|
+
* ```
|
|
1679
1799
|
*
|
|
1680
|
-
*
|
|
1681
|
-
*
|
|
1682
|
-
*
|
|
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
|
+
* ```
|
|
1683
1824
|
*
|
|
1684
|
-
*
|
|
1825
|
+
* II. Standalone mode
|
|
1685
1826
|
*
|
|
1686
|
-
*
|
|
1687
|
-
*
|
|
1688
|
-
*
|
|
1689
|
-
*
|
|
1690
|
-
*
|
|
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.
|
|
1691
1836
|
*/
|
|
1692
1837
|
class SPMatEntityCrudFormBase {
|
|
1693
|
-
_form = signal(undefined);
|
|
1694
1838
|
entity = input.required();
|
|
1695
1839
|
bridge = input.required();
|
|
1696
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);
|
|
1697
1855
|
sub$ = new Subscription();
|
|
1856
|
+
// Store for internal form signal. form() is computed from this.
|
|
1857
|
+
_form = signal(undefined);
|
|
1698
1858
|
// Force typecast to TFormGroup so that we can use it in the template
|
|
1699
1859
|
// without having to use the non-nullable operator ! with every reference
|
|
1700
1860
|
// of form(). In any case the form() signal is always set in ngOnInit()
|
|
1701
1861
|
// method after the form is created. And if form() is not set, then there
|
|
1702
1862
|
// will be errors while loading the form in the template.
|
|
1703
1863
|
form = computed(() => this._form());
|
|
1704
|
-
crudConfig = getEntityCrudConfig();
|
|
1705
1864
|
transloco = inject(TranslocoService);
|
|
1706
1865
|
cdr = inject(ChangeDetectorRef);
|
|
1866
|
+
http = inject(HttpClient);
|
|
1707
1867
|
canCancelEdit = () => {
|
|
1708
1868
|
return this._canCancelEdit();
|
|
1709
1869
|
};
|
|
@@ -1715,19 +1875,78 @@ class SPMatEntityCrudFormBase {
|
|
|
1715
1875
|
return true;
|
|
1716
1876
|
}
|
|
1717
1877
|
ngOnInit() {
|
|
1718
|
-
|
|
1719
|
-
|
|
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
|
+
}));
|
|
1720
1898
|
}
|
|
1721
1899
|
ngOnDestroy() {
|
|
1722
1900
|
this.sub$.unsubscribe();
|
|
1723
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
|
+
}
|
|
1724
1939
|
/**
|
|
1725
1940
|
* Override to customize the id key name if it's not 'id'
|
|
1726
1941
|
* @returns The name of the unique identifier key that will be used to
|
|
1727
1942
|
* extract the entity's id for UPDATE operation.
|
|
1728
1943
|
*/
|
|
1729
1944
|
getIdKey() {
|
|
1730
|
-
|
|
1945
|
+
const bridge = this.bridge();
|
|
1946
|
+
if (bridge) {
|
|
1947
|
+
return bridge.getIdKey();
|
|
1948
|
+
}
|
|
1949
|
+
return this.idKey();
|
|
1731
1950
|
}
|
|
1732
1951
|
/**
|
|
1733
1952
|
* Return the form's value to be sent to server as Create/Update CRUD
|
|
@@ -1740,21 +1959,124 @@ class SPMatEntityCrudFormBase {
|
|
|
1740
1959
|
}
|
|
1741
1960
|
onSubmit() {
|
|
1742
1961
|
const value = this.getFormValue();
|
|
1743
|
-
const obs = !this.
|
|
1744
|
-
? this.
|
|
1745
|
-
: this.
|
|
1962
|
+
const obs = !this._entity()
|
|
1963
|
+
? this.create(value)
|
|
1964
|
+
: this.update(this._entity()[this.getIdKey()], value);
|
|
1746
1965
|
this.sub$.add(obs
|
|
1747
|
-
?.pipe(setServerErrorsAsFormErrors(this._form(), this.cdr))
|
|
1966
|
+
?.pipe(tap(entity => this._entity() ? this.onPostUpdate(entity) : this.onPostCreate(entity)), setServerErrorsAsFormErrors(this._form(), this.cdr))
|
|
1748
1967
|
.subscribe());
|
|
1749
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
|
+
}
|
|
1750
2071
|
/** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: SPMatEntityCrudFormBase, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1751
|
-
/** @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 });
|
|
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 });
|
|
1752
2073
|
}
|
|
1753
2074
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: SPMatEntityCrudFormBase, decorators: [{
|
|
1754
2075
|
type: Component,
|
|
1755
2076
|
args: [{
|
|
1756
|
-
selector: '_#_sp-mat-entity-crud-form-base_#_',
|
|
1757
|
-
|
|
2077
|
+
selector: '_#_sp-mat-entity-crud-form-base_#_',
|
|
2078
|
+
template: ``,
|
|
2079
|
+
standalone: false,
|
|
1758
2080
|
}]
|
|
1759
2081
|
}] });
|
|
1760
2082
|
|