From f7686521a2a22c49b96824d3ffb2542a69f97249 Mon Sep 17 00:00:00 2001 From: jojohoch <joachim.hoch@iqb.hu-berlin.de> Date: Tue, 27 Jul 2021 10:03:44 +0200 Subject: [PATCH] [player] Use `PageComponent` as child formGroup * Register `PageComponent` as child formGroup, so that values for any page can be sent to the host. * Register formControls as controls of the page --- .../form-element-component.directive.ts | 6 +-- projects/common/form.service.ts | 11 ++++- projects/common/form.ts | 8 +++- .../src/app/components/form.component.ts | 41 ++++++++++--------- .../src/app/components/page.component.ts | 36 ++++++++++++++-- .../app/components/player-state.component.ts | 4 +- 6 files changed, 77 insertions(+), 29 deletions(-) diff --git a/projects/common/form-element-component.directive.ts b/projects/common/form-element-component.directive.ts index 5efe26e78..58f9d3764 100644 --- a/projects/common/form-element-component.directive.ts +++ b/projects/common/form-element-component.directive.ts @@ -25,13 +25,13 @@ export abstract class FormElementComponent extends ElementComponent implements O ngOnInit(): void { const formControl = new FormControl(this.elementModel.value, this.getValidations()); const id = this.elementModel.id; - this.formService.registerFormControl({ id, formControl }); + this.formService.registerFormControl({ id, formControl, formGroup: this.parentForm }); this.elementFormControl = this.getFormControl(id); this.elementFormControl.valueChanges .pipe( - takeUntil(this.ngUnsubscribe), startWith(this.elementModel.value), - pairwise() + pairwise(), + takeUntil(this.ngUnsubscribe) ) .subscribe(([prevValue, nextValue] : [string | number | boolean | undefined, string | number | boolean]) => { if (nextValue != null) { // invalid input on number fields generates event with null TODO find a better solution diff --git a/projects/common/form.service.ts b/projects/common/form.service.ts index 61d1dd048..fa30ea975 100644 --- a/projects/common/form.service.ts +++ b/projects/common/form.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Observable, Subject } from 'rxjs'; -import { FormControlElement, ValueChangeElement } from './form'; +import { FormControlElement, FormGroupPage, ValueChangeElement } from './form'; @Injectable({ providedIn: 'root' @@ -8,6 +8,7 @@ import { FormControlElement, ValueChangeElement } from './form'; export class FormService { private _elementValueChanged = new Subject<ValueChangeElement>(); private _controlAdded = new Subject<FormControlElement>(); + private _groupAdded = new Subject<FormGroupPage>(); get elementValueChanged(): Observable<ValueChangeElement> { return this._elementValueChanged.asObservable(); @@ -17,6 +18,10 @@ export class FormService { return this._controlAdded.asObservable(); } + get groupAdded(): Observable<FormGroupPage> { + return this._groupAdded.asObservable(); + } + changeElementValue(elementValues: ValueChangeElement): void { this._elementValueChanged.next(elementValues); } @@ -24,4 +29,8 @@ export class FormService { registerFormControl(control: FormControlElement): void { this._controlAdded.next(control); } + + registerFormGroup(group: FormGroupPage): void { + this._groupAdded.next(group); + } } diff --git a/projects/common/form.ts b/projects/common/form.ts index 31a950618..d22e47eab 100644 --- a/projects/common/form.ts +++ b/projects/common/form.ts @@ -1,4 +1,4 @@ -import { FormControl } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; export interface ValueChangeElement { id: string; @@ -8,4 +8,10 @@ export interface ValueChangeElement { export interface FormControlElement { id: string; formControl: FormControl; + formGroup: FormGroup; +} + +export interface FormGroupPage { + id: string; + formGroup: FormGroup; } diff --git a/projects/player/src/app/components/form.component.ts b/projects/player/src/app/components/form.component.ts index 09a9bdea6..b4dce6d80 100644 --- a/projects/player/src/app/components/form.component.ts +++ b/projects/player/src/app/components/form.component.ts @@ -1,11 +1,11 @@ import { Component, Input, OnDestroy } from '@angular/core'; -import { FormGroup } from '@angular/forms'; +import { FormArray, FormBuilder, FormGroup } from '@angular/forms'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { FormService } from '../../../../common/form.service'; import { VeronaSubscriptionService } from '../services/verona-subscription.service'; import { VeronaPostService } from '../services/verona-post.service'; -import { FormControlElement, ValueChangeElement } from '../../../../common/form'; +import { FormGroupPage, ValueChangeElement } from '../../../../common/form'; import { PlayerConfig, UnitState, VopNavigationDeniedNotification } from '../models/verona'; @@ -25,36 +25,37 @@ import { UnitPage } from '../../../../common/unit'; export class FormComponent implements OnDestroy { @Input() pages: UnitPage[] = []; @Input() playerConfig!: PlayerConfig; - form = new FormGroup({}); + form!: FormGroup; private ngUnsubscribe = new Subject<void>(); - constructor(private formService: FormService, + constructor(private formBuilder: FormBuilder, + private formService: FormService, private veronaSubscriptionService: VeronaSubscriptionService, private veronaPostService: VeronaPostService) { + this.form = this.formBuilder.group({ + pages: this.formBuilder.array([]) + }); this.initSubscriptions(); } get validPages():Record<string, string>[] { - return this.pages.map((page:UnitPage, index:number) => { - const validPage: Record<string, string> = {}; - validPage[`page${index}`] = `Seite ${index + 1}`; - return validPage; - }); + return this.pages.map((page:UnitPage): Record<string, string> => ( + { [page.id]: page.label })); } private initSubscriptions(): void { this.formService.elementValueChanged .pipe(takeUntil(this.ngUnsubscribe)) .subscribe((value: ValueChangeElement): void => this.onElementValueChanges(value)); - this.formService.controlAdded + this.formService.groupAdded .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe((control: FormControlElement): void => this.addControl(control)); + .subscribe((group: FormGroupPage): void => this.addGroup(group)); this.veronaSubscriptionService.vopNavigationDeniedNotification .pipe(takeUntil(this.ngUnsubscribe)) .subscribe((message: VopNavigationDeniedNotification): void => this.onNavigationDenied(message)); this.form.valueChanges .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe((formValues: any): void => this.onFormChanges(formValues)); + .subscribe((formValues: { pages: Record<string, string>[] }): void => this.onFormChanges(formValues)); } private onNavigationDenied(message: VopNavigationDeniedNotification): void { @@ -63,8 +64,9 @@ export class FormComponent implements OnDestroy { this.form.markAllAsTouched(); } - private addControl(control: FormControlElement): void { - this.form.addControl(control.id, control.formControl); + private addGroup(group: FormGroupPage): void { + const pages: FormArray = this.form.get('pages') as FormArray; + pages.push(new FormGroup({ [group.id]: group.formGroup })); } private onElementValueChanges = (value: ValueChangeElement): void => { @@ -72,14 +74,15 @@ export class FormComponent implements OnDestroy { console.log(`player: onElementValueChanges - ${value.id}: ${value.values[0]} -> ${value.values[1]}`); }; - private onFormChanges(formValues: unknown): void { + private onFormChanges(formValues: { pages: Record<string, string>[] }): void { // eslint-disable-next-line no-console console.log('player: onFormChanges', formValues); - // TODO: map by page and? section not all const unitState: UnitState = { - dataParts: { - all: JSON.stringify(formValues) - } + dataParts: formValues.pages + .reduce((obj, page): Record<string, string> => { + obj[Object.keys(page)[0]] = JSON.stringify(page[Object.keys(page)[0]]); + return obj; + }, {}) }; this.veronaPostService.sendVopStateChangedNotification({ unitState }); } diff --git a/projects/player/src/app/components/page.component.ts b/projects/player/src/app/components/page.component.ts index d7a414a70..883f8f56d 100644 --- a/projects/player/src/app/components/page.component.ts +++ b/projects/player/src/app/components/page.component.ts @@ -1,12 +1,18 @@ -import { Component, Input } from '@angular/core'; +import { + Component, Input, OnDestroy, OnInit +} from '@angular/core'; import { FormGroup } from '@angular/forms'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; import { UnitPage } from '../../../../common/unit'; +import { FormService } from '../../../../common/form.service'; +import { FormControlElement } from '../../../../common/form'; @Component({ selector: 'app-page', template: ` <app-section *ngFor="let section of page.sections; let i = index" - [parentForm]="parentForm" + [parentForm]="pageForm" [section]="section" [ngStyle]="{ position: 'relative', @@ -19,7 +25,31 @@ import { UnitPage } from '../../../../common/unit'; ` }) -export class PageComponent { +export class PageComponent implements OnInit, OnDestroy { @Input() page!: UnitPage; @Input() parentForm!: FormGroup; + pageForm!: FormGroup; + private ngUnsubscribe = new Subject<void>(); + + constructor(private formService: FormService) {} + + private addControl(control: FormControlElement): void { + // we need to check that the control belongs to the page + if (this.pageForm === control.formGroup) { + this.pageForm.addControl(control.id, control.formControl); + } + } + + ngOnInit(): void { + this.pageForm = new FormGroup({}); + this.formService.registerFormGroup({ id: this.page.id, formGroup: this.pageForm }); + this.formService.controlAdded.pipe( + takeUntil(this.ngUnsubscribe) + ).subscribe((control: FormControlElement): void => this.addControl(control)); + } + + ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } } diff --git a/projects/player/src/app/components/player-state.component.ts b/projects/player/src/app/components/player-state.component.ts index 1d7809731..0addbe8ad 100644 --- a/projects/player/src/app/components/player-state.component.ts +++ b/projects/player/src/app/components/player-state.component.ts @@ -19,7 +19,7 @@ import { VeronaPostService } from '../services/verona-post.service'; <mat-tab-group [(selectedIndex)]="currentIndex" (selectedIndexChange)="onSelectedIndexChange()" mat-align-tabs="start"> - <mat-tab *ngFor="let page of pages; let i = index" label="{{validPages[i]['page'+i]}}"> + <mat-tab *ngFor="let page of pages; let i = index" label="{{page.label}}"> <app-page [parentForm]="parenForm" [page]="page"></app-page> </mat-tab> </mat-tab-group> @@ -38,7 +38,6 @@ export class PlayerStateComponent implements OnInit, OnDestroy { constructor(private veronaSubscriptionService: VeronaSubscriptionService, private veronaPostService: VeronaPostService) { - this.initSubscriptions(); } private get state(): RunningState { @@ -46,6 +45,7 @@ export class PlayerStateComponent implements OnInit, OnDestroy { } ngOnInit(): void { + this.initSubscriptions(); this.sendVopStateChangedNotification(); } -- GitLab