@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.
@@ -1,5 +1,5 @@
1
1
  import * as i1 from '@angular/common/http';
2
- import { HttpContextToken, HttpParams, HttpContext } from '@angular/common/http';
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 [class]="'sp-mat-crud-form-wrapper ' + entityCrudComponentBase().getFormPaneWrapperClass()" spHostBusyWheel="formBusyWheel" *transloco="let t">
188
- <div [class]="'sp-mat-crud-form-content ' + entityCrudComponentBase().getFormPaneContentClass()">
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
- {{ title() | async }}
244
+ {{ title() | async }}
193
245
  } @else {
194
- {{ t(('spMatEntityCrud.' + (entity() ? 'editItem' : 'newItem')), { item: (this._itemLabel() | async )}) }}
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 [class]="'sp-mat-crud-form-wrapper ' + entityCrudComponentBase().getFormPaneWrapperClass()" spHostBusyWheel="formBusyWheel" *transloco="let t">
221
- <div [class]="'sp-mat-crud-form-content ' + entityCrudComponentBase().getFormPaneContentClass()">
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
- {{ title() | async }}
293
+ {{ title() | async }}
226
294
  } @else {
227
- {{ t(('spMatEntityCrud.' + (entity() ? 'editItem' : 'newItem')), { item: (this._itemLabel() | async )}) }}
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).pipe(tap((response) => {
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 || "Done");
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
- })).subscribe();
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
- contextParamToHttpContext(context, crudHttpReqContext);
1130
+ convertHttpContextInputToHttpContext(context, crudHttpReqContext);
1055
1131
  }
1056
- else if (typeof crudHttpReqContext === 'object' && op &&
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
- contextParamToHttpContext(context, crudHttpReqContext[op]);
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 @Component is fake just to keep the VSCode angular linter quiet.
1727
+ * The `@Component` decorator is fake to keep the VSCode angular linter quiet.
1651
1728
  *
1652
- * To use this class:-
1729
+ * This class can be used in two modes:
1653
1730
  *
1654
- * 1. Declare a FormGroup<> type as
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
- * 2. Derive your form's component class from this and implement the
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
- * class MyFormComponent extends SPMatEntityCrudFormBase<MyForm, MyEntity> {
1670
- * constructor() {
1671
- * super()
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
- * createForm() {
1675
- * return new FormGroup([...])
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
- * 3. If you form's value requires manipulation before being sent to the
1681
- * server, override getFormValue() method and do it there before returning
1682
- * the modified values.
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
- * 4. Wire up the form in the template as:
1825
+ * II. Standalone mode
1685
1826
  *
1686
- * ```
1687
- * <form [formGroup]='form'.. (ngSubmit)="onSubmit()">
1688
- * <button type="submit">Submit</button>
1689
- * </form>
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
- this._form.set(this.createForm(this.entity()));
1719
- this.bridge()?.registerCanCancelEditCallback(this.canCancelEdit);
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
- return 'id';
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.entity()
1744
- ? this.bridge()?.create(value)
1745
- : this.bridge()?.update(this.entity()[this.getIdKey()], value);
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_#_', template: ``,
1757
- standalone: false
2077
+ selector: '_#_sp-mat-entity-crud-form-base_#_',
2078
+ template: ``,
2079
+ standalone: false,
1758
2080
  }]
1759
2081
  }] });
1760
2082