Skip to content

Commit 286d02b

Browse files
FrancescoMolinaroAndrea Barbasso
authored andcommitted
Merged in task/dspace-cris-2025_02_x/DSC-2599 (pull request DSpace#4340)
Task/dspace cris 2025 02 x/DSC-2599 Approved-by: Andrea Barbasso
2 parents 487da26 + efb88bd commit 286d02b

3 files changed

Lines changed: 102 additions & 61 deletions

File tree

src/app/shared/form/form.component.ts

Lines changed: 65 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -237,55 +237,74 @@ export class FormComponent implements OnDestroy, OnInit {
237237
this.formService.getForm(this.formId).pipe(
238238
filter((formState: FormEntry) => !!formState && (isNotEmpty(formState.errors) || isNotEmpty(this.formErrors))),
239239
map((formState) => formState.errors),
240-
distinctUntilChanged())
241-
.subscribe((errors: FormError[]) => {
242-
const { formGroup, formModel } = this;
243-
errors
244-
.filter((error: FormError) => findIndex(this.formErrors, {
245-
fieldId: error.fieldId,
246-
fieldIndex: error.fieldIndex,
247-
}) === -1)
248-
.forEach((error: FormError) => {
249-
const { fieldId } = error;
250-
const { fieldIndex } = error;
251-
let field: AbstractControl;
252-
if (this.parentFormModel) {
253-
field = this.formBuilderService.getFormControlById(fieldId, formGroup.parent as UntypedFormGroup, formModel, fieldIndex);
254-
} else {
255-
field = this.formBuilderService.getFormControlById(fieldId, formGroup, formModel, fieldIndex);
256-
}
240+
distinctUntilChanged(),
241+
).subscribe((errors: FormError[]) => {
242+
const { formGroup, formModel } = this;
243+
244+
const prevMap = new Map<string, FormError>(
245+
this.formErrors.map(e => [`${e.fieldId}:${e.fieldIndex}`, e]),
246+
);
247+
const nextMap = new Map<string, FormError>(
248+
errors.map(e => [`${e.fieldId}:${e.fieldIndex}`, e]),
249+
);
250+
251+
if (isEqual(prevMap, nextMap)) {
252+
return;
253+
}
257254

258-
if (field) {
259-
const modelArrayIndex = fieldIndex > 0 ? fieldIndex : null;
260-
const model: DynamicFormControlModel = this.formBuilderService.findById(fieldId, formModel, modelArrayIndex);
261-
this.formService.addErrorToField(field, model, error.message);
262-
this.changeDetectorRef.detectChanges();
263-
}
264-
});
265-
266-
this.formErrors
267-
.filter((error: FormError) => findIndex(errors, {
268-
fieldId: error.fieldId,
269-
fieldIndex: error.fieldIndex,
270-
}) === -1)
271-
.forEach((error: FormError) => {
272-
const { fieldId } = error;
273-
const { fieldIndex } = error;
274-
let field: AbstractControl;
275-
if (this.parentFormModel) {
276-
field = this.formBuilderService.getFormControlById(fieldId, formGroup.parent as UntypedFormGroup, formModel, fieldIndex);
277-
} else {
278-
field = this.formBuilderService.getFormControlById(fieldId, formGroup, formModel, fieldIndex);
255+
const getControl = (err: FormError): AbstractControl | null => {
256+
return this.parentFormModel
257+
? this.formBuilderService.getFormControlById(err.fieldId, formGroup.parent as UntypedFormGroup, formModel, err.fieldIndex)
258+
: this.formBuilderService.getFormControlById(err.fieldId, formGroup, formModel, err.fieldIndex);
259+
};
260+
261+
const getModel = (err: FormError): DynamicFormControlModel => {
262+
const modelArrayIndex = err.fieldIndex > 0 ? err.fieldIndex : null;
263+
return this.formBuilderService.findById(err.fieldId, formModel, modelArrayIndex);
264+
};
265+
// Add or change (including revert) errors
266+
errors.forEach(next => {
267+
const key = `${next.fieldId}:${next.fieldIndex}`;
268+
const prev = prevMap.get(key);
269+
if (!prev || prev.message !== next.message) {
270+
// Remove old message if changed
271+
if (prev) {
272+
const prevControl = getControl(prev);
273+
if (prevControl) {
274+
const prevModel = getModel(prev);
275+
this.formService.removeErrorFromField(prevControl, prevModel, prev.message);
276+
this.formService.removeError(this.formId, prev.fieldId, prev.fieldIndex);
277+
this.formErrors.splice(findIndex(this.formErrors, prev), 1);
279278
}
279+
}
280+
// Add new message
281+
const control = getControl(next);
282+
if (control) {
283+
const model = getModel(next);
284+
this.formService.addErrorToField(control, model, next.message);
285+
this.formErrors.push(next);
286+
}
287+
}
288+
});
289+
290+
const removedErrors: FormError[] = [];
291+
// Remove errors for fields no longer present
292+
this.formErrors.forEach(prev => {
293+
const key = `${prev.fieldId}:${prev.fieldIndex}`;
294+
if (!nextMap.has(key) && prevMap.has(key)) {
295+
const control = getControl(prev);
296+
if (control) {
297+
const model = getModel(prev);
298+
this.formService.removeErrorFromField(control, model, prev.message);
299+
this.formService.removeError(this.formId, prev.fieldId, prev.fieldIndex);
300+
removedErrors.push(prev);
301+
}
302+
}
303+
});
280304

281-
if (field) {
282-
const model: DynamicFormControlModel = this.formBuilderService.findById(fieldId, formModel, fieldIndex);
283-
this.formService.removeErrorFromField(field, model, error.message);
284-
}
285-
});
286-
this.formErrors = errors;
287-
this.changeDetectorRef.detectChanges();
288-
}),
305+
this.formErrors = this.formErrors.filter(error => !removedErrors.includes(error));
306+
this.changeDetectorRef.detectChanges();
307+
}),
289308
);
290309
}
291310

src/app/shared/form/form.service.spec.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222

2323
import { AppState } from '../../app.reducer';
2424
import { getMockFormBuilderService } from '../mocks/form-builder-service.mock';
25+
import { getMockTranslateService } from '../mocks/translate.service.mock';
2526
import { DynamicConcatModel } from './builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model';
2627
import { FormBuilderService } from './builder/form-builder.service';
2728
import { formReducer } from './form.reducer';
@@ -40,6 +41,7 @@ describe('FormService test suite', () => {
4041
let service: FormService;
4142
let builderService: FormBuilderService;
4243
let formGroup: UntypedFormGroup;
44+
const translateService = getMockTranslateService();
4345

4446
const formModel: DynamicFormControlModel[] = [
4547
new DynamicInputModel({ id: 'author', value: 'test' }),
@@ -147,7 +149,7 @@ describe('FormService test suite', () => {
147149

148150
formGroup = new UntypedFormGroup({ author, title, date, description, addressLocation, name });
149151
controls = { author, title, date, description , addressLocation, name };
150-
service = new FormService(builderService, store);
152+
service = new FormService(builderService, store, translateService);
151153
}),
152154
)
153155
;
@@ -242,6 +244,17 @@ describe('FormService test suite', () => {
242244

243245
});
244246

247+
it('should show correct error message if a different error is already present in the field and a new is added', () => {
248+
let control = controls.description;
249+
let model = formModel.find((mdl: DynamicFormControlModel) => mdl.id === 'description');
250+
251+
service.addErrorToField(control, model, 'error.test.message');
252+
service.addErrorToField(control, model, 'error.test.newMessage');
253+
service.removeErrorFromField(control, model, 'error.test.message');
254+
255+
expect(control.errors).toEqual({ newMessage: true });
256+
});
257+
245258
it('should remove error from field', () => {
246259
let control = controls.description;
247260
let model = formModel.find((mdl: DynamicFormControlModel) => mdl.id === 'description');
@@ -265,7 +278,7 @@ describe('FormService test suite', () => {
265278

266279
service.removeErrorFromField(control, model, 'error.required');
267280

268-
expect(control.errors).toBeNull();
281+
expect(control.errors).toEqual(null);
269282
});
270283

271284
it('should remove errors from fields of concat group', () => {
@@ -274,8 +287,8 @@ describe('FormService test suite', () => {
274287
let control = controls.name;
275288
let model = formModel.find((mdl: DynamicFormControlModel) => mdl.id === 'name_CONCAT_GROUP');
276289
let errorKeys: string[];
277-
278-
service.addErrorToField(control, model, 'Test error message');
290+
const messageKey = 'Test error message';
291+
service.addErrorToField(control, model, messageKey);
279292
errorKeys = Object.keys(control.errors);
280293

281294
service.removeErrorFromField(control, model, errorKeys[0]);
@@ -287,7 +300,7 @@ describe('FormService test suite', () => {
287300

288301
// the group's inputs should no longer have an error
289302
Object.values(control.controls).forEach((subControl: AbstractControl) => {
290-
expect(control.errors).toBeNull();
303+
expect(control.errors).toEqual(null);
291304
});
292305
});
293306

src/app/shared/form/form.service.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
select,
1515
Store,
1616
} from '@ngrx/store';
17+
import { TranslateService } from '@ngx-translate/core';
1718
import uniqueId from 'lodash/uniqueId';
1819
import { Observable } from 'rxjs';
1920
import {
@@ -25,6 +26,7 @@ import {
2526
import { environment } from '../../../environments/environment';
2627
import { AppState } from '../../app.reducer';
2728
import {
29+
hasValue,
2830
isEmpty,
2931
isNotUndefined,
3032
} from '../empty.util';
@@ -51,7 +53,8 @@ export class FormService {
5153

5254
constructor(
5355
private formBuilderService: FormBuilderService,
54-
private store: Store<AppState>) {
56+
private store: Store<AppState>,
57+
private translateService: TranslateService) {
5558
}
5659

5760
/**
@@ -159,15 +162,19 @@ export class FormService {
159162
}
160163
const errors: string[] = Object.keys(field.errors)
161164
.filter((errorKey) => field.errors[errorKey] === true)
162-
.map((errorKey) => `error.validation.${errorKey}`);
165+
.map((errorKey) => {
166+
const defaultErrorKey = `error.validation.${errorKey}`;
167+
const customErrorKey = `error.validation.${formId}.${errorKey}`;
168+
const hasDefaultLabel = this.translateService.instant(defaultErrorKey) !== defaultErrorKey;
169+
return hasDefaultLabel ? defaultErrorKey : customErrorKey;
170+
});
163171
errors.forEach((error) => this.addError(formId, fieldId, fieldIndex, error));
164172
}
165173

166174
public addErrorToField(field: AbstractControl, model: DynamicFormControlModel, message: string) {
167175

168176
const error = {}; // create the error object
169177
const errorKey = this.getValidatorNameFromMap(message);
170-
let errorMsg = message;
171178

172179
// if form control model has no errorMessages object, create it
173180
if (!model.errorMessages) {
@@ -178,11 +185,7 @@ export class FormService {
178185
if (isEmpty(model.errorMessages[errorKey])) {
179186
// put the error message in the form control model
180187
model.errorMessages[errorKey] = message;
181-
} else {
182-
// Use correct error messages from the model
183-
errorMsg = model.errorMessages[errorKey];
184188
}
185-
186189
if (!field.hasError(errorKey)) {
187190
error[errorKey] = true;
188191
// add the error in the form control
@@ -205,10 +208,10 @@ export class FormService {
205208
public removeErrorFromField(field: AbstractControl, model: DynamicFormControlModel, messageKey: string) {
206209
const error = {};
207210
const errorKey = this.getValidatorNameFromMap(messageKey);
208-
209211
if (field.hasError(errorKey)) {
210212
error[errorKey] = null;
211-
field.setErrors(error);
213+
const updatedError = { ...field.errors, ...error };
214+
field.setErrors(updatedError);
212215
field.clearValidators();
213216
field.updateValueAndValidity();
214217
}
@@ -222,7 +225,13 @@ export class FormService {
222225
});
223226
}
224227

225-
field.markAsUntouched();
228+
const currentErrors = field.errors;
229+
const hasDifferentErrors = hasValue(currentErrors) && Object.keys(currentErrors).filter((key) => currentErrors[key]).length > 0;
230+
231+
if (!hasDifferentErrors) {
232+
field.markAsUntouched();
233+
}
234+
226235
}
227236

228237
public resetForm(formGroup: UntypedFormGroup, groupModel: DynamicFormControlModel[], formId: string) {

0 commit comments

Comments
 (0)