@hichchi/ngx-utils 0.0.1-alpha.1 → 0.0.1-alpha.2
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/hichchi-ngx-utils.mjs +501 -181
- package/fesm2022/hichchi-ngx-utils.mjs.map +1 -1
- package/lib/form/form.interfaces.d.ts +254 -0
- package/lib/form/form.utils.d.ts +369 -0
- package/lib/form/index.d.ts +2 -0
- package/lib/index.d.ts +2 -3
- package/lib/interceptors/api-url-interceptor.d.ts +92 -0
- package/lib/interceptors/index.d.ts +0 -1
- package/lib/interfaces/http-error.interface.d.ts +163 -1
- package/lib/services/hichchi-http.service.d.ts +5 -0
- package/lib/services/index.d.ts +1 -1
- package/package.json +4 -2
- package/README.md +0 -580
- package/lib/interceptors/auth.interceptor.d.ts +0 -4
- package/lib/services/auth.service.d.ts +0 -15
- package/lib/state/auth.state.d.ts +0 -40
- package/lib/state/index.d.ts +0 -1
- package/lib/utils/form.utils.d.ts +0 -1
- package/lib/utils/index.d.ts +0 -1
|
@@ -1,115 +1,503 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { inject, Injectable, computed } from '@angular/core';
|
|
3
|
-
import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
|
|
4
|
-
import { withStorageSync } from '@angular-architects/ngrx-toolkit';
|
|
5
|
-
import { take, map, tap, catchError, EMPTY, ReplaySubject, throwError, switchMap, filter } from 'rxjs';
|
|
6
|
-
import { HttpClient } from '@angular/common/http';
|
|
7
|
-
import { AuthEndpoint, AuthErrorResponseCode } from '@hichchi/nest-connector/auth';
|
|
8
|
-
import { Endpoint, HttpClientErrorStatus } from '@hichchi/nest-connector';
|
|
9
|
-
import { Router } from '@angular/router';
|
|
1
|
+
import { FormGroup, FormArray } from '@angular/forms';
|
|
10
2
|
|
|
11
3
|
// noinspection JSUnusedGlobalSymbols
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Recursively marks invalid form controls as dirty and touched
|
|
6
|
+
*
|
|
7
|
+
* This utility function traverses a form hierarchy and marks all invalid controls
|
|
8
|
+
* as dirty and touched, which triggers validation error display in the UI. It works
|
|
9
|
+
* recursively through nested FormGroups and FormArrays to ensure all invalid
|
|
10
|
+
* controls are properly marked for error display.
|
|
11
|
+
*
|
|
12
|
+
* The function is particularly useful when you want to show all validation errors
|
|
13
|
+
* at once, such as when a user attempts to submit a form. It only marks invalid
|
|
14
|
+
* controls, leaving valid controls unchanged.
|
|
15
|
+
*
|
|
16
|
+
* Key features:
|
|
17
|
+
* - Recursive traversal of form hierarchy
|
|
18
|
+
* - Only marks invalid controls as dirty/touched
|
|
19
|
+
* - Handles nested FormGroups and FormArrays
|
|
20
|
+
* - Updates validation state after marking controls
|
|
21
|
+
* - Non-destructive (doesn't affect valid controls)
|
|
22
|
+
*
|
|
23
|
+
* @param form - The FormGroup to process recursively
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* // Mark all invalid controls in a form for error display
|
|
28
|
+
* export class UserFormComponent {
|
|
29
|
+
* userForm = this.fb.group({
|
|
30
|
+
* name: ['', Validators.required],
|
|
31
|
+
* email: ['', [Validators.required, Validators.email]],
|
|
32
|
+
* address: this.fb.group({
|
|
33
|
+
* street: ['', Validators.required],
|
|
34
|
+
* city: ['', Validators.required]
|
|
35
|
+
* })
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* onSubmit() {
|
|
39
|
+
* if (this.userForm.invalid) {
|
|
40
|
+
* // Mark all invalid fields to show errors
|
|
41
|
+
* markFormDirty(this.userForm);
|
|
42
|
+
* return;
|
|
43
|
+
* }
|
|
44
|
+
* // Process valid form...
|
|
45
|
+
* }
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* // Using with form arrays
|
|
52
|
+
* export class DynamicFormComponent {
|
|
53
|
+
* form = this.fb.group({
|
|
54
|
+
* items: this.fb.array([
|
|
55
|
+
* this.fb.group({
|
|
56
|
+
* name: ['', Validators.required],
|
|
57
|
+
* quantity: [0, Validators.min(1)]
|
|
58
|
+
* })
|
|
59
|
+
* ])
|
|
60
|
+
* });
|
|
61
|
+
*
|
|
62
|
+
* validateAll() {
|
|
63
|
+
* markFormDirty(this.form);
|
|
64
|
+
* // All invalid controls in the array will be marked
|
|
65
|
+
* }
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @see {@link FormGroup} Angular reactive form group
|
|
70
|
+
* @see {@link FormArray} Angular reactive form array
|
|
71
|
+
* @see {@link validatedFormData} Function that uses this utility for validation
|
|
72
|
+
*/
|
|
73
|
+
function markFormDirty(form) {
|
|
74
|
+
const markDirty = (form) => {
|
|
75
|
+
Object.keys(form.controls).forEach(field => {
|
|
76
|
+
const control = form.get(field);
|
|
77
|
+
if (control?.invalid) {
|
|
78
|
+
control?.markAsDirty({ onlySelf: true });
|
|
79
|
+
control?.markAsTouched({ onlySelf: true });
|
|
80
|
+
control.updateValueAndValidity({ onlySelf: true });
|
|
81
|
+
}
|
|
82
|
+
if (control instanceof FormGroup) {
|
|
83
|
+
markDirty(control);
|
|
84
|
+
}
|
|
85
|
+
if (control instanceof FormArray) {
|
|
86
|
+
control.controls.forEach((control) => {
|
|
87
|
+
markDirty(control);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
return markDirty(form);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Removes null values from an object by deleting properties with null values
|
|
96
|
+
*
|
|
97
|
+
* This utility function creates a new object with all null properties removed.
|
|
98
|
+
* It's particularly useful when working with form data where null values should
|
|
99
|
+
* be omitted from API requests or when preparing data for processing that doesn't
|
|
100
|
+
* handle null values well.
|
|
101
|
+
*
|
|
102
|
+
* The function performs a shallow copy of the object and removes any properties
|
|
103
|
+
* that have null values. Properties with undefined values are preserved, as they
|
|
104
|
+
* represent different semantic meaning (missing vs explicitly null).
|
|
105
|
+
*
|
|
106
|
+
* @template T - The type of the object being processed
|
|
107
|
+
* @param obj - Object that may contain null values to be removed
|
|
108
|
+
* @returns A new object with null properties removed
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* // Remove null values from form data
|
|
113
|
+
* const formData = {
|
|
114
|
+
* name: 'John Doe',
|
|
115
|
+
* email: 'john@example.com',
|
|
116
|
+
* phone: null,
|
|
117
|
+
* address: null,
|
|
118
|
+
* age: 30
|
|
119
|
+
* };
|
|
120
|
+
*
|
|
121
|
+
* const cleanData = replaceNulls(formData);
|
|
122
|
+
* // Result: { name: 'John Doe', email: 'john@example.com', age: 30 }
|
|
123
|
+
* ```
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* // Using with API request data
|
|
128
|
+
* interface UserUpdate {
|
|
129
|
+
* name?: string | null;
|
|
130
|
+
* email?: string | null;
|
|
131
|
+
* phone?: string | null;
|
|
132
|
+
* }
|
|
133
|
+
*
|
|
134
|
+
* const updateData: UserUpdate = {
|
|
135
|
+
* name: 'Jane Smith',
|
|
136
|
+
* email: null, // Don't update email
|
|
137
|
+
* phone: '555-1234'
|
|
138
|
+
* };
|
|
139
|
+
*
|
|
140
|
+
* const apiPayload = replaceNulls(updateData);
|
|
141
|
+
* // Result: { name: 'Jane Smith', phone: '555-1234' }
|
|
142
|
+
* // API receives only the fields to update
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```typescript
|
|
147
|
+
* // Difference between null and undefined
|
|
148
|
+
* const data = {
|
|
149
|
+
* field1: 'value',
|
|
150
|
+
* field2: null, // Will be removed
|
|
151
|
+
* field3: undefined // Will be preserved
|
|
152
|
+
* };
|
|
153
|
+
*
|
|
154
|
+
* const result = replaceNulls(data);
|
|
155
|
+
* // Result: { field1: 'value', field3: undefined }
|
|
156
|
+
* ```
|
|
157
|
+
*
|
|
158
|
+
* @see {@link validatedFormData} Function that uses this utility to clean form data
|
|
159
|
+
*/
|
|
160
|
+
function replaceNulls(obj) {
|
|
161
|
+
const result = { ...obj };
|
|
162
|
+
for (const key in result) {
|
|
163
|
+
if (result[key] === null) {
|
|
164
|
+
delete result[key];
|
|
165
|
+
}
|
|
45
166
|
}
|
|
46
|
-
|
|
47
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AuthService, providedIn: "root" });
|
|
167
|
+
return result;
|
|
48
168
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Validates a form and returns clean data if valid, or null if invalid
|
|
171
|
+
*
|
|
172
|
+
* This utility function combines form validation with data cleaning in a single operation.
|
|
173
|
+
* It first marks all invalid controls as dirty (to show validation errors), then checks
|
|
174
|
+
* if the form is valid. If valid, it returns the form data with null values removed.
|
|
175
|
+
* If invalid, it returns null.
|
|
176
|
+
*
|
|
177
|
+
* This is particularly useful for form submission handlers where you want to:
|
|
178
|
+
* 1. Show all validation errors if the form is invalid
|
|
179
|
+
* 2. Get clean, validated data if the form is valid
|
|
180
|
+
* 3. Handle both cases with a simple null check
|
|
181
|
+
*
|
|
182
|
+
* The function uses the DataFormGroup type to ensure type safety between the form
|
|
183
|
+
* structure and the returned data type.
|
|
184
|
+
*
|
|
185
|
+
* @template T - The type of the data structure represented by the form
|
|
186
|
+
* @param form - The DataFormGroup to validate and extract data from
|
|
187
|
+
* @returns The validated and cleaned form data, or null if the form is invalid
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```typescript
|
|
191
|
+
* // Basic form validation and submission
|
|
192
|
+
* export class UserFormComponent {
|
|
193
|
+
* userForm: DataFormGroup<UserData> = this.fb.group({
|
|
194
|
+
* name: ['', Validators.required],
|
|
195
|
+
* email: ['', [Validators.required, Validators.email]],
|
|
196
|
+
* phone: [null], // Optional field
|
|
197
|
+
* age: [null, Validators.min(18)]
|
|
198
|
+
* });
|
|
199
|
+
*
|
|
200
|
+
* onSubmit() {
|
|
201
|
+
* const validData = validatedFormData(this.userForm);
|
|
202
|
+
* if (validData) {
|
|
203
|
+
* // Form is valid, submit clean data
|
|
204
|
+
* this.userService.createUser(validData);
|
|
205
|
+
* } else {
|
|
206
|
+
* // Form is invalid, errors are now visible
|
|
207
|
+
* console.log('Please fix form errors');
|
|
208
|
+
* }
|
|
209
|
+
* }
|
|
210
|
+
* }
|
|
211
|
+
* ```
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```typescript
|
|
215
|
+
* // Using with async operations
|
|
216
|
+
* export class ProfileComponent {
|
|
217
|
+
* profileForm: DataFormGroup<ProfileUpdate> = this.fb.group({
|
|
218
|
+
* firstName: ['', Validators.required],
|
|
219
|
+
* lastName: ['', Validators.required],
|
|
220
|
+
* bio: [null], // Optional
|
|
221
|
+
* website: [null, Validators.pattern(/^https?:\/\/.+/)]
|
|
222
|
+
* });
|
|
223
|
+
*
|
|
224
|
+
* async updateProfile() {
|
|
225
|
+
* const updateData = validatedFormData(this.profileForm);
|
|
226
|
+
* if (!updateData) {
|
|
227
|
+
* this.showErrorMessage('Please fix the form errors');
|
|
228
|
+
* return;
|
|
229
|
+
* }
|
|
230
|
+
*
|
|
231
|
+
* try {
|
|
232
|
+
* await this.profileService.update(updateData);
|
|
233
|
+
* this.showSuccessMessage('Profile updated successfully');
|
|
234
|
+
* } catch (error) {
|
|
235
|
+
* this.showErrorMessage('Failed to update profile');
|
|
236
|
+
* }
|
|
237
|
+
* }
|
|
238
|
+
* }
|
|
239
|
+
* ```
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```typescript
|
|
243
|
+
* // Type-safe form data extraction
|
|
244
|
+
* interface ContactForm {
|
|
245
|
+
* name: string;
|
|
246
|
+
* email: string;
|
|
247
|
+
* message: string;
|
|
248
|
+
* newsletter?: boolean;
|
|
249
|
+
* }
|
|
250
|
+
*
|
|
251
|
+
* const contactForm: DataFormGroup<ContactForm> = this.fb.group({
|
|
252
|
+
* name: ['', Validators.required],
|
|
253
|
+
* email: ['', [Validators.required, Validators.email]],
|
|
254
|
+
* message: ['', [Validators.required, Validators.minLength(10)]],
|
|
255
|
+
* newsletter: [null]
|
|
256
|
+
* });
|
|
257
|
+
*
|
|
258
|
+
* const formData = validatedFormData(contactForm);
|
|
259
|
+
* if (formData) {
|
|
260
|
+
* // TypeScript knows formData is ContactForm with null values removed
|
|
261
|
+
* console.log(formData.name); // string
|
|
262
|
+
* console.log(formData.email); // string
|
|
263
|
+
* console.log(formData.newsletter); // boolean | undefined (null was removed)
|
|
264
|
+
* }
|
|
265
|
+
* ```
|
|
266
|
+
*
|
|
267
|
+
* @see {@link DataFormGroup} Type-safe form group interface
|
|
268
|
+
* @see {@link markFormDirty} Function used internally to mark invalid controls
|
|
269
|
+
* @see {@link replaceNulls} Function used internally to clean the data
|
|
270
|
+
*/
|
|
271
|
+
function validatedFormData(form) {
|
|
272
|
+
markFormDirty(form);
|
|
273
|
+
if (form.invalid)
|
|
274
|
+
return null;
|
|
275
|
+
return replaceNulls(form.value);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Creates a FormData object from a plain JavaScript object
|
|
279
|
+
*
|
|
280
|
+
* This utility function converts a plain object into a FormData object, which is
|
|
281
|
+
* required for multipart/form-data HTTP requests, particularly when uploading files
|
|
282
|
+
* or sending form data that includes binary content. The function handles different
|
|
283
|
+
* data types appropriately, preserving file names for File objects and converting
|
|
284
|
+
* other values to strings.
|
|
285
|
+
*
|
|
286
|
+
* The function is particularly useful when working with Angular forms that need to
|
|
287
|
+
* submit both regular form fields and file uploads in a single request. It automatically
|
|
288
|
+
* handles the conversion of various data types to the format expected by FormData.
|
|
289
|
+
*
|
|
290
|
+
* Key features:
|
|
291
|
+
* - Preserves original file names for File objects
|
|
292
|
+
* - Converts primitive values to strings
|
|
293
|
+
* - Maintains type safety with generic constraints
|
|
294
|
+
* - Handles Blob objects correctly
|
|
295
|
+
* - Creates multipart/form-data compatible output
|
|
296
|
+
*
|
|
297
|
+
* @template T - Object type with string keys and values that can be converted to FormData
|
|
298
|
+
* @param data - Object containing the data to convert to FormData
|
|
299
|
+
* @returns A FormData object ready for HTTP submission
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```typescript
|
|
303
|
+
* // Basic usage with mixed data types
|
|
304
|
+
* const formData = createFormData({
|
|
305
|
+
* name: 'John Doe',
|
|
306
|
+
* age: 30,
|
|
307
|
+
* isActive: true,
|
|
308
|
+
* avatar: fileInput.files[0] // File object
|
|
309
|
+
* });
|
|
310
|
+
*
|
|
311
|
+
* // Submit via HTTP
|
|
312
|
+
* this.http.post('/api/users', formData).subscribe();
|
|
313
|
+
* ```
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```typescript
|
|
317
|
+
* // File upload with metadata
|
|
318
|
+
* export class FileUploadComponent {
|
|
319
|
+
* onFileUpload(file: File, description: string, isPublic: boolean) {
|
|
320
|
+
* const uploadData = createFormData({
|
|
321
|
+
* file: file,
|
|
322
|
+
* description: description,
|
|
323
|
+
* isPublic: isPublic,
|
|
324
|
+
* uploadedAt: new Date().toISOString()
|
|
325
|
+
* });
|
|
326
|
+
*
|
|
327
|
+
* this.fileService.upload(uploadData).subscribe({
|
|
328
|
+
* next: (response) => console.log('Upload successful'),
|
|
329
|
+
* error: (error) => console.error('Upload failed', error)
|
|
330
|
+
* });
|
|
331
|
+
* }
|
|
332
|
+
* }
|
|
333
|
+
* ```
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```typescript
|
|
337
|
+
* // Using with form validation
|
|
338
|
+
* export class ProfileFormComponent {
|
|
339
|
+
* profileForm = this.fb.group({
|
|
340
|
+
* name: ['', Validators.required],
|
|
341
|
+
* bio: [''],
|
|
342
|
+
* profilePicture: [null]
|
|
343
|
+
* });
|
|
344
|
+
*
|
|
345
|
+
* onSubmit() {
|
|
346
|
+
* const formValue = validatedFormData(this.profileForm);
|
|
347
|
+
* if (!formValue) return;
|
|
348
|
+
*
|
|
349
|
+
* // Convert to FormData for file upload
|
|
350
|
+
* const formData = createFormData({
|
|
351
|
+
* name: formValue.name,
|
|
352
|
+
* bio: formValue.bio || '',
|
|
353
|
+
* profilePicture: formValue.profilePicture,
|
|
354
|
+
* timestamp: Date.now()
|
|
355
|
+
* });
|
|
356
|
+
*
|
|
357
|
+
* this.profileService.updateProfile(formData);
|
|
358
|
+
* }
|
|
359
|
+
* }
|
|
360
|
+
* ```
|
|
361
|
+
*
|
|
362
|
+
* @example
|
|
363
|
+
* ```typescript
|
|
364
|
+
* // Multiple file upload
|
|
365
|
+
* interface UploadRequest {
|
|
366
|
+
* title: string;
|
|
367
|
+
* category: string;
|
|
368
|
+
* document: File;
|
|
369
|
+
* thumbnail: File;
|
|
370
|
+
* isPrivate: boolean;
|
|
371
|
+
* }
|
|
372
|
+
*
|
|
373
|
+
* const uploadRequest: UploadRequest = {
|
|
374
|
+
* title: 'My Document',
|
|
375
|
+
* category: 'reports',
|
|
376
|
+
* document: documentFile,
|
|
377
|
+
* thumbnail: thumbnailFile,
|
|
378
|
+
* isPrivate: false
|
|
379
|
+
* };
|
|
380
|
+
*
|
|
381
|
+
* const formData = createFormData(uploadRequest);
|
|
382
|
+
* // FormData will contain:
|
|
383
|
+
* // - title: "My Document"
|
|
384
|
+
* // - category: "reports"
|
|
385
|
+
* // - document: [File object with original name]
|
|
386
|
+
* // - thumbnail: [File object with original name]
|
|
387
|
+
* // - isPrivate: "false"
|
|
388
|
+
* ```
|
|
389
|
+
*
|
|
390
|
+
* @see {@link FormData} Web API interface for form data
|
|
391
|
+
* @see {@link File} Web API interface for file objects
|
|
392
|
+
* @see {@link Blob} Web API interface for binary data
|
|
393
|
+
* @see {@link validatedFormData} Function for getting validated form data to use with this utility
|
|
394
|
+
*/
|
|
395
|
+
function createFormData(data) {
|
|
396
|
+
const formData = new FormData();
|
|
397
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
398
|
+
if (value instanceof File) {
|
|
399
|
+
formData.append(key, value, value.name);
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
formData.append(key, String(value));
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
return formData;
|
|
110
406
|
}
|
|
111
407
|
|
|
112
408
|
// noinspection JSUnusedGlobalSymbols
|
|
409
|
+
/**
|
|
410
|
+
* Creates an HTTP interceptor that automatically prepends a base API URL to relative requests
|
|
411
|
+
*
|
|
412
|
+
* This interceptor factory function creates an HTTP interceptor that automatically adds a base API URL
|
|
413
|
+
* to requests that don't already have a complete URL. It's designed to simplify API calls by allowing
|
|
414
|
+
* developers to use relative URLs throughout their application while ensuring all requests are properly
|
|
415
|
+
* routed to the correct API base URL.
|
|
416
|
+
*
|
|
417
|
+
* The interceptor intelligently handles different URL formats:
|
|
418
|
+
* - Requests that already start with the provided API base URL are passed through unchanged
|
|
419
|
+
* - Requests that start with "http" (absolute URLs) are passed through unchanged
|
|
420
|
+
* - All other requests (relative URLs) have the API base URL prepended
|
|
421
|
+
*
|
|
422
|
+
* This is particularly useful in Angular applications where you want to centralize API URL management
|
|
423
|
+
* and avoid repeating the base URL in every HTTP service call.
|
|
424
|
+
*
|
|
425
|
+
* @param apiBase - The base API URL to prepend to relative requests (without trailing slash)
|
|
426
|
+
* @returns An HttpInterceptorFn that can be used in Angular HTTP interceptor configuration
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```typescript
|
|
430
|
+
* // Basic usage in app configuration
|
|
431
|
+
* export const appConfig: ApplicationConfig = {
|
|
432
|
+
* providers: [
|
|
433
|
+
* provideHttpClient(
|
|
434
|
+
* withInterceptors([
|
|
435
|
+
* apiUrlInterceptor('https://api.example.com')
|
|
436
|
+
* ])
|
|
437
|
+
* )
|
|
438
|
+
* ]
|
|
439
|
+
* };
|
|
440
|
+
* ```
|
|
441
|
+
*
|
|
442
|
+
* @example
|
|
443
|
+
* ```typescript
|
|
444
|
+
* // Using with environment configuration
|
|
445
|
+
* import { environment } from '../environments/environment';
|
|
446
|
+
*
|
|
447
|
+
* export const appConfig: ApplicationConfig = {
|
|
448
|
+
* providers: [
|
|
449
|
+
* provideHttpClient(
|
|
450
|
+
* withInterceptors([
|
|
451
|
+
* apiUrlInterceptor(environment.apiUrl)
|
|
452
|
+
* ])
|
|
453
|
+
* )
|
|
454
|
+
* ]
|
|
455
|
+
* };
|
|
456
|
+
* ```
|
|
457
|
+
*
|
|
458
|
+
* @example
|
|
459
|
+
* ```typescript
|
|
460
|
+
* // In a service, you can now use relative URLs
|
|
461
|
+
* @Injectable()
|
|
462
|
+
* export class UserService {
|
|
463
|
+
* constructor(private http: HttpClient) {}
|
|
464
|
+
*
|
|
465
|
+
* getUsers() {
|
|
466
|
+
* // This will become: https://api.example.com/users
|
|
467
|
+
* return this.http.get('users');
|
|
468
|
+
* }
|
|
469
|
+
*
|
|
470
|
+
* getUserById(id: string) {
|
|
471
|
+
* // This will become: https://api.example.com/users/123
|
|
472
|
+
* return this.http.get(`users/${id}`);
|
|
473
|
+
* }
|
|
474
|
+
*
|
|
475
|
+
* // Absolute URLs are not modified
|
|
476
|
+
* getExternalData() {
|
|
477
|
+
* // This remains: https://external-api.com/data
|
|
478
|
+
* return this.http.get('https://external-api.com/data');
|
|
479
|
+
* }
|
|
480
|
+
* }
|
|
481
|
+
* ```
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* ```typescript
|
|
485
|
+
* // In a module-based application
|
|
486
|
+
* @NgModule({
|
|
487
|
+
* providers: [
|
|
488
|
+
* {
|
|
489
|
+
* provide: HTTP_INTERCEPTORS,
|
|
490
|
+
* useValue: apiUrlInterceptor('https://api.myapp.com/v1'),
|
|
491
|
+
* multi: true
|
|
492
|
+
* }
|
|
493
|
+
* ]
|
|
494
|
+
* })
|
|
495
|
+
* export class AppModule {}
|
|
496
|
+
* ```
|
|
497
|
+
*
|
|
498
|
+
* @see {@link HttpInterceptorFn} Angular HTTP interceptor function type
|
|
499
|
+
* @see {@link HttpRequest} Angular HTTP request interface
|
|
500
|
+
*/
|
|
113
501
|
function apiUrlInterceptor(apiBase) {
|
|
114
502
|
return (req, next) => {
|
|
115
503
|
if (req.url.startsWith(apiBase) || req.url.startsWith("http")) {
|
|
@@ -120,86 +508,18 @@ function apiUrlInterceptor(apiBase) {
|
|
|
120
508
|
};
|
|
121
509
|
}
|
|
122
510
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
let tokenSubject = new ReplaySubject(1);
|
|
131
|
-
function authInterceptor(redirect, onRedirect) {
|
|
132
|
-
return (req, next) => {
|
|
133
|
-
const authState = inject(AuthState);
|
|
134
|
-
const authService = inject(AuthService);
|
|
135
|
-
const router = inject(Router);
|
|
136
|
-
const setAccessToken = (req, accessToken) => {
|
|
137
|
-
return req.clone({
|
|
138
|
-
headers: req.headers.set("Authorization", "Bearer " + accessToken),
|
|
139
|
-
});
|
|
140
|
-
};
|
|
141
|
-
const gotoSignIn = () => {
|
|
142
|
-
onRedirect?.();
|
|
143
|
-
authState.reset();
|
|
144
|
-
// eslint-disable-next-line no-void
|
|
145
|
-
void router.navigateByUrl(redirect);
|
|
146
|
-
};
|
|
147
|
-
const refreshToken = (req, next) => {
|
|
148
|
-
if (!refreshingInProgress) {
|
|
149
|
-
refreshingInProgress = true;
|
|
150
|
-
tokenSubject.next(null);
|
|
151
|
-
const refreshToken = authState.refreshToken();
|
|
152
|
-
if (!refreshToken) {
|
|
153
|
-
refreshingInProgress = false;
|
|
154
|
-
gotoSignIn();
|
|
155
|
-
return throwError(() => new Error("Refresh token not found."));
|
|
156
|
-
}
|
|
157
|
-
return authService.refreshToken(refreshToken).pipe(switchMap((tokenResponse) => {
|
|
158
|
-
authState.setTokens(tokenResponse);
|
|
159
|
-
tokenSubject.next(tokenResponse.accessToken);
|
|
160
|
-
tokenSubject.complete();
|
|
161
|
-
tokenSubject = new ReplaySubject(1);
|
|
162
|
-
refreshingInProgress = false;
|
|
163
|
-
return next(setAccessToken(req, tokenResponse.accessToken));
|
|
164
|
-
}), catchError((error) => {
|
|
165
|
-
refreshingInProgress = false;
|
|
166
|
-
tokenSubject.error(error);
|
|
167
|
-
tokenSubject.complete();
|
|
168
|
-
tokenSubject = new ReplaySubject(1);
|
|
169
|
-
gotoSignIn();
|
|
170
|
-
return throwError(() => error);
|
|
171
|
-
}));
|
|
172
|
-
}
|
|
173
|
-
return tokenSubject.pipe(filter(result => result !== null), take(1), switchMap(token => {
|
|
174
|
-
return next(setAccessToken(req, token));
|
|
175
|
-
}));
|
|
176
|
-
};
|
|
177
|
-
const handleRequest = (req, next) => {
|
|
178
|
-
return next(req).pipe(catchError((error) => {
|
|
179
|
-
if (error.status === HttpClientErrorStatus.UNAUTHORIZED &&
|
|
180
|
-
error.error?.code &&
|
|
181
|
-
SKIPPED_ERRORS.includes(error.error?.code) &&
|
|
182
|
-
!isRefreshTokenReq(req)) {
|
|
183
|
-
if (authState.signedIn()) {
|
|
184
|
-
return refreshToken(req, next);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return throwError(() => error);
|
|
188
|
-
}));
|
|
189
|
-
};
|
|
190
|
-
if (authState.accessToken()) {
|
|
191
|
-
const tokenizedRequest = req.clone({
|
|
192
|
-
headers: req.headers.set("Authorization", "Bearer " + authState.accessToken()),
|
|
193
|
-
});
|
|
194
|
-
return handleRequest(tokenizedRequest, next);
|
|
195
|
-
}
|
|
196
|
-
return handleRequest(req, next);
|
|
197
|
-
};
|
|
511
|
+
// export * from "./response.interceptor";
|
|
512
|
+
|
|
513
|
+
class HichchiHttpService {
|
|
514
|
+
http;
|
|
515
|
+
constructor(http) {
|
|
516
|
+
this.http = http;
|
|
517
|
+
}
|
|
198
518
|
}
|
|
199
519
|
|
|
200
520
|
/**
|
|
201
521
|
* Generated bundle index. Do not edit.
|
|
202
522
|
*/
|
|
203
523
|
|
|
204
|
-
export {
|
|
524
|
+
export { HichchiHttpService, apiUrlInterceptor, createFormData, markFormDirty, replaceNulls, validatedFormData };
|
|
205
525
|
//# sourceMappingURL=hichchi-ngx-utils.mjs.map
|