diff --git a/cypress/e2e/app.cy.ts b/cypress/e2e/app.cy.ts index 5acbe3e3..044102de 100644 --- a/cypress/e2e/app.cy.ts +++ b/cypress/e2e/app.cy.ts @@ -174,6 +174,9 @@ context(`EJawa demo`, () => { }, crewMembers: { required: true, + crewMembers: { + minimumCrewMemberCount: 2 + }, }, wingCount: { required: true, @@ -194,80 +197,42 @@ context(`EJawa demo`, () => { DOM.form.elements.vehicleForm.addCrewMemberButton.click(); - if (id === 'old') { - DOM.form.errors.should($el => { - expect(extractErrors($el)).to.eql({ - vehicleProduct: { - spaceship: { - color: { - required: true, - }, - crewMembers: { - crewMembers: [ - { - firstName: { - required: true, - }, - lastName: { - required: true, - }, - }, - ], - }, - wingCount: { - required: true, - }, + DOM.form.errors.should($el => { + expect(extractErrors($el)).to.eql({ + vehicleProduct: { + spaceship: { + color: { + required: true, }, - }, - title: { - required: true, - }, - imageUrl: { - required: true, - }, - price: { - required: true, - }, - }); - }); - } else { - DOM.form.errors.should($el => { - expect(extractErrors($el)).to.eql({ - vehicleProduct: { - spaceship: { - color: { - required: true, - }, + crewMembers: { crewMembers: { - crewMembers: { - minimumCrewMemberCount: 2, - 0: { - firstName: { - required: true, - }, - lastName: { - required: true, - }, + minimumCrewMemberCount: 2, + 0: { + firstName: { + required: true, + }, + lastName: { + required: true, }, }, }, - wingCount: { - required: true, - }, + }, + wingCount: { + required: true, }, }, - title: { - required: true, - }, - imageUrl: { - required: true, - }, - price: { - required: true, - }, - }); + }, + title: { + required: true, + }, + imageUrl: { + required: true, + }, + price: { + required: true, + }, }); - } + }); DOM.form.elements.selectListingTypeByType(ListingType.DROID); @@ -378,6 +343,65 @@ context(`EJawa demo`, () => { }); }); }); + + it(`should display the (nested) errors from the form after enable/disable`, () => { + DOM.createNewButton.click(); + + DOM.form.elements.selectListingTypeByType(ListingType.VEHICLE); + + DOM.form.elements.vehicleForm.selectVehicleTypeByType(VehicleType.SPACESHIP); + + DOM.form.elements.vehicleForm.addCrewMemberButton.click(); + + const errorsToExpect = { + vehicleProduct: { + spaceship: { + color: { + required: true, + }, + crewMembers: { + crewMembers: { + minimumCrewMemberCount: 2, + 0: { + firstName: { + required: true, + }, + lastName: { + required: true, + }, + }, + }, + }, + wingCount: { + required: true, + }, + }, + }, + title: { + required: true, + }, + imageUrl: { + required: true, + }, + price: { + required: true, + }, + }; + + DOM.form.errors.should($el => { + expect(extractErrors($el)).to.eql(errorsToExpect); + }); + + DOM.readonlyToggle.click(); + + DOM.form.errors.should('not.exist'); + + DOM.readonlyToggle.click(); + + DOM.form.errors.should($el => { + expect(extractErrors($el)).to.eql(errorsToExpect); + }); + }); }); }); }); diff --git a/projects/ngx-sub-form/src/lib/create-form.ts b/projects/ngx-sub-form/src/lib/create-form.ts index 2f4ce39a..a0ff2da5 100644 --- a/projects/ngx-sub-form/src/lib/create-form.ts +++ b/projects/ngx-sub-form/src/lib/create-form.ts @@ -234,12 +234,13 @@ export function createForm( ), setDisabledState$: setDisabledState$.pipe( tap((shouldDisable: boolean) => { - shouldDisable ? formGroup.disable({ emitEvent: false }) : formGroup.enable({ emitEvent: false }); + // We have to emit to update and validate the value and propagate it to the parent + shouldDisable ? formGroup.disable({ emitEvent: true }) : formGroup.enable({ emitEvent: true }); }), ), updateValue$: updateValueAndValidity$.pipe( tap(() => { - formGroup.updateValueAndValidity({ emitEvent: false }); + formGroup.updateValueAndValidity({ emitEvent: true }); }), ), bindTouched$: combineLatest([componentHooks.registerOnTouched$, options.touched$ ?? EMPTY]).pipe( diff --git a/projects/ngx-sub-form/src/lib/deprecated/ngx-sub-form.component.ts b/projects/ngx-sub-form/src/lib/deprecated/ngx-sub-form.component.ts index 4472eb20..316cb32b 100644 --- a/projects/ngx-sub-form/src/lib/deprecated/ngx-sub-form.component.ts +++ b/projects/ngx-sub-form/src/lib/deprecated/ngx-sub-form.component.ts @@ -173,9 +173,20 @@ export abstract class NgxSubFormComponent 0 && values.some(x => !isNullOrUndefined(x))) { - controls[key] = values; + value = { + ...value, + ...values + } } + controls[key] = value; } else if (control && filterControl(control, key, false)) { controls[key] = mapControl(control, key); } @@ -287,28 +298,33 @@ export abstract class NgxSubFormComponent { - if (this.formGroup.get(key) instanceof UntypedFormArray && Array.isArray(value)) { + if ( + this.formGroup.get(key) instanceof UntypedFormArray && + Array.isArray(value) + ) { const formArray: UntypedFormArray = this.formGroup.get(key) as UntypedFormArray; - - // instead of creating a new array every time and push a new FormControl - // we just remove or add what is necessary so that: - // - it is as efficient as possible and do not create unnecessary FormControl every time - // - validators are not destroyed/created again and eventually fire again for no reason - while (formArray.length > value.length) { - formArray.removeAt(formArray.length - 1); - } - - for (let i = formArray.length; i < value.length; i++) { - if (this.formIsFormWithArrayControls()) { - formArray.insert(i, this.createFormArrayControl(key as ArrayPropertyKey, value[i])); - } else { - formArray.insert(i, new UntypedFormControl(value[i])); - } - } + formArray.clear(); + this.addAdditionalObjects(formArray, value, key); } }); } + private addAdditionalObjects( + formArray: UntypedFormArray, + value: Array, + key: string + ) { + for (let i = formArray.length; i < value.length; i++) { + const control = this.formIsFormWithArrayControls() + ? this.createFormArrayControl( + key as ArrayPropertyKey, + value[i] + ) + : new UntypedFormControl(value[i]); + formArray.insert(i, control); + } + } + private formIsFormWithArrayControls(): this is NgxFormWithArrayControls { return typeof (this as unknown as NgxFormWithArrayControls).createFormArrayControl === 'function'; } @@ -423,10 +439,11 @@ export abstract class NgxSubFormComponent( return; } - // instead of creating a new array every time and push a new FormControl - // we just remove or add what is necessary so that: - // - it is as efficient as possible and do not create unnecessary FormControl every time - // - validators are not destroyed/created again and eventually fire again for no reason - while (control.length > value.length) { - control.removeAt(control.length - 1); - } - + control.clear(); for (let i = control.length; i < value.length; i++) { const newControl = createFormArrayControl(key as ArrayPropertyKey, value[i]); if (control.disabled) { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 85248689..01a8e5ec 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,9 +1,13 @@ -import { NgModule } from '@angular/core'; +import { registerLocaleData } from '@angular/common'; +import { LOCALE_ID, NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { RouterModule } from '@angular/router'; import { AppComponent } from './app.component'; import { SharedModule } from './shared/shared.module'; +import localeDe from '@angular/common/locales/de'; + +registerLocaleData(localeDe, 'de'); @NgModule({ declarations: [AppComponent], @@ -25,7 +29,7 @@ import { SharedModule } from './shared/shared.module'; ), SharedModule, ], - providers: [], + providers: [{provide: LOCALE_ID, useValue: 'de' }], bootstrap: [AppComponent], }) export class AppModule {}