diff --git a/src/main/webapp/app/exercises/programming/manage/update/update-components/information/programming-exercise-information.component.ts b/src/main/webapp/app/exercises/programming/manage/update/update-components/information/programming-exercise-information.component.ts index 78158c26e1ce..6893eef163cf 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/update-components/information/programming-exercise-information.component.ts +++ b/src/main/webapp/app/exercises/programming/manage/update/update-components/information/programming-exercise-information.component.ts @@ -161,7 +161,7 @@ export class ProgrammingExerciseInformationComponent implements AfterViewInit, O const areAuxiliaryRepositoriesValid = this.areAuxiliaryRepositoriesValid(); const areCheckoutPathsValid = this.areCheckoutPathsValid(); this.formValid = Boolean( - this.exerciseTitleChannelComponent()?.titleChannelNameComponent?.formValidSignal() && + this.exerciseTitleChannelComponent()?.titleChannelNameComponent?.isFormValidSignal() && this.getIsShortNameFieldValid() && isCheckoutSolutionRepositoryValid && isRecreateBuildPlansValid && diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.html b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.html index 2a895b2d195e..8176c1f6a48d 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.html +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.html @@ -73,10 +73,10 @@
- - @if (hasCancelButton) { + @if (hasCancelButton()) { diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts index 3648cb3bb8db..67adf4f2c8dc 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts @@ -1,5 +1,5 @@ import dayjs from 'dayjs/esm'; -import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; +import { Component, OnChanges, computed, inject, input, output } from '@angular/core'; import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { faArrowLeft, faTimes } from '@fortawesome/free-solid-svg-icons'; import { map } from 'rxjs'; @@ -7,6 +7,7 @@ import { HttpResponse } from '@angular/common/http'; import { OnlineResourceDTO } from 'app/lecture/lecture-unit/lecture-unit-management/online-resource-dto.model'; import { OnlineUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/onlineUnit.service'; import { CompetencyLectureUnitLink } from 'app/entities/competency.model'; +import { toSignal } from '@angular/core/rxjs-interop'; export interface OnlineUnitFormData { name?: string; @@ -32,32 +33,33 @@ function urlValidator(control: AbstractControl) { selector: 'jhi-online-unit-form', templateUrl: './online-unit-form.component.html', }) -export class OnlineUnitFormComponent implements OnInit, OnChanges { - @Input() - formData: OnlineUnitFormData; - @Input() - isEditMode = false; +export class OnlineUnitFormComponent implements OnChanges { + protected readonly faArrowLeft = faArrowLeft; + protected readonly faTimes = faTimes; - @Output() - formSubmitted: EventEmitter = new EventEmitter(); - form: FormGroup; + formData = input(); + isEditMode = input(false); - @Input() - hasCancelButton: boolean; - @Output() - onCancel: EventEmitter = new EventEmitter(); + formSubmitted = output(); - faTimes = faTimes; + hasCancelButton = input(false); + onCancel = output(); urlValidator = urlValidator; - // Icons - faArrowLeft = faArrowLeft; + private readonly formBuilder = inject(FormBuilder); + private readonly onlineUnitService = inject(OnlineUnitService); - constructor( - private fb: FormBuilder, - private onlineUnitService: OnlineUnitService, - ) {} + form: FormGroup = this.formBuilder.group({ + name: [undefined, [Validators.required, Validators.maxLength(255)]], + description: [undefined, [Validators.maxLength(1000)]], + releaseDate: [undefined], + source: [undefined, [Validators.required, this.urlValidator]], + competencyLinks: [undefined as CompetencyLectureUnitLink[] | undefined], + }); + + private readonly statusChanges = toSignal(this.form.statusChanges ?? 'INVALID'); + isFormValid = computed(() => this.statusChanges() === 'VALID'); get nameControl() { return this.form.get('name'); @@ -76,27 +78,9 @@ export class OnlineUnitFormComponent implements OnInit, OnChanges { } ngOnChanges(): void { - this.initializeForm(); - if (this.isEditMode && this.formData) { - this.setFormValues(this.formData); - } - } - - ngOnInit(): void { - this.initializeForm(); - } - - private initializeForm() { - if (this.form) { - return; + if (this.isEditMode() && this.formData()) { + this.setFormValues(this.formData()!); } - this.form = this.fb.group({ - name: [undefined, [Validators.required, Validators.maxLength(255)]], - description: [undefined, [Validators.maxLength(1000)]], - releaseDate: [undefined], - source: [undefined, [Validators.required, this.urlValidator]], - competencyLinks: [undefined as CompetencyLectureUnitLink[] | undefined], - }); } private setFormValues(formData: OnlineUnitFormData) { @@ -136,10 +120,6 @@ export class OnlineUnitFormComponent implements OnInit, OnChanges { this.formSubmitted.emit(onlineUnitFormData); } - get isSubmitPossible() { - return !this.form.invalid; - } - cancelForm() { this.onCancel.emit(); } diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.html b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.html index 403f20a774cf..7e879d12d121 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.html +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.html @@ -50,10 +50,10 @@
- - @if (hasCancelButton) { + @if (hasCancelButton()) { diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts index 28817538a1e4..b6d7b31447b2 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core'; +import { Component, OnChanges, OnDestroy, OnInit, computed, inject, input, output } from '@angular/core'; import dayjs from 'dayjs/esm'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; @@ -7,6 +7,7 @@ import { debounceTime } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { CompetencyLectureUnitLink } from 'app/entities/competency.model'; +import { toSignal } from '@angular/core/rxjs-interop'; export interface TextUnitFormData { name?: string; @@ -21,30 +22,36 @@ export interface TextUnitFormData { styles: [], }) export class TextUnitFormComponent implements OnInit, OnChanges, OnDestroy { - @Input() - formData: TextUnitFormData; + protected readonly faTimes = faTimes; - @Input() isEditMode = false; - @Output() formSubmitted: EventEmitter = new EventEmitter(); + formData = input(); - @Input() - hasCancelButton: boolean; - @Output() - onCancel: EventEmitter = new EventEmitter(); + isEditMode = input(false); + formSubmitted = output(); - faTimes = faTimes; + hasCancelButton = input(false); + onCancel = output(); - form: FormGroup; // not included in reactive form content: string | undefined; contentLoadedFromCache = false; firstMarkdownChangeHappened = false; + private readonly formBuilder = inject(FormBuilder); + + form: FormGroup = this.formBuilder.group({ + name: [undefined as string | undefined, [Validators.required, Validators.maxLength(255)]], + releaseDate: [undefined as dayjs.Dayjs | undefined], + competencyLinks: [undefined as CompetencyLectureUnitLink[] | undefined], + }); + + private readonly statusChanges = toSignal(this.form.statusChanges ?? 'INVALID'); + isFormValid = computed(() => this.statusChanges() === 'VALID'); + private markdownChanges = new Subject(); private markdownChangesSubscription: Subscription; constructor( - private fb: FormBuilder, private router: Router, private translateService: TranslateService, ) {} @@ -58,9 +65,8 @@ export class TextUnitFormComponent implements OnInit, OnChanges, OnDestroy { } ngOnChanges(): void { - this.initializeForm(); - if (this.isEditMode && this.formData) { - this.setFormValues(this.formData); + if (this.isEditMode() && this.formData()) { + this.setFormValues(this.formData()!); } } @@ -85,18 +91,6 @@ export class TextUnitFormComponent implements OnInit, OnChanges, OnDestroy { this.firstMarkdownChangeHappened = true; } }); - this.initializeForm(); - } - - private initializeForm() { - if (this.form) { - return; - } - this.form = this.fb.group({ - name: [undefined as string | undefined, [Validators.required, Validators.maxLength(255)]], - releaseDate: [undefined as dayjs.Dayjs | undefined], - competencyLinks: [undefined as CompetencyLectureUnitLink[] | undefined], - }); } private setFormValues(formData: TextUnitFormData) { @@ -115,10 +109,6 @@ export class TextUnitFormComponent implements OnInit, OnChanges, OnDestroy { this.formSubmitted.emit(textUnitFormData); } - get isSubmitPossible() { - return !this.form.invalid; - } - onMarkdownChange(markdown: string) { this.markdownChanges.next(markdown); } diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 8e8fbd4f6dcb..b72ea717afd9 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -21,7 +21,16 @@ import { FormulaAction } from 'app/shared/monaco-editor/model/actions/formula.ac styleUrls: ['./lecture-update.component.scss'], }) export class LectureUpdateComponent implements OnInit { - readonly documentationType: DocumentationType = 'Lecture'; + protected readonly documentationType: DocumentationType = 'Lecture'; + protected readonly faQuestionCircle = faQuestionCircle; + protected readonly faSave = faSave; + protected readonly faPuzzleProcess = faPuzzlePiece; + protected readonly faBan = faBan; + protected readonly faHandShakeAngle = faHandshakeAngle; + // A human-readable list of allowed file extensions + protected readonly allowedFileExtensions = UPLOAD_FILE_EXTENSIONS.join(', '); + // The list of file extensions for the "accept" attribute of the file input field + protected readonly acceptedFileExtensionsFileBrowser = UPLOAD_FILE_EXTENSIONS.map((ext) => '.' + ext).join(','); @ViewChild(LectureUpdateWizardComponent, { static: false }) wizardComponent: LectureUpdateWizardComponent; @@ -38,18 +47,6 @@ export class LectureUpdateComponent implements OnInit { fileName: string; fileInputTouched = false; - // Icons - faQuestionCircle = faQuestionCircle; - faSave = faSave; - faPuzzleProcess = faPuzzlePiece; - faBan = faBan; - faHandShakeAngle = faHandshakeAngle; - - // A human-readable list of allowed file extensions - readonly allowedFileExtensions = UPLOAD_FILE_EXTENSIONS.join(', '); - // The list of file extensions for the "accept" attribute of the file input field - readonly acceptedFileExtensionsFileBrowser = UPLOAD_FILE_EXTENSIONS.map((ext) => '.' + ext).join(','); - toggleModeFunction = () => this.toggleWizardMode(); saveLectureFunction = () => this.save(); @@ -89,8 +86,8 @@ export class LectureUpdateComponent implements OnInit { /** * Revert to the previous state, equivalent with pressing the back button on your browser - * Returns to the detail page if there is no previous state and we edited an existing lecture - * Returns to the overview page if there is no previous state and we created a new lecture + * Returns to the detail page if there is no previous state, and we edited an existing lecture + * Returns to the overview page if there is no previous state, and we created a new lecture */ previousState() { this.navigationUtilService.navigateBackWithOptional(['course-management', this.lecture.course!.id!.toString(), 'lectures'], this.lecture.id?.toString()); diff --git a/src/main/webapp/app/shared/form/title-channel-name/title-channel-name.component.ts b/src/main/webapp/app/shared/form/title-channel-name/title-channel-name.component.ts index 6fbbfdca412f..269e3a7e84e4 100644 --- a/src/main/webapp/app/shared/form/title-channel-name/title-channel-name.component.ts +++ b/src/main/webapp/app/shared/form/title-channel-name/title-channel-name.component.ts @@ -29,8 +29,14 @@ export class TitleChannelNameComponent implements AfterViewInit, OnDestroy, OnIn @Output() titleChange = new EventEmitter(); @Output() channelNameChange = new EventEmitter(); - formValidSignal = signal(false); + isFormValidSignal = signal(false); + /** + * @deprecated Use {@link isFormValidSignal} instead. + */ formValid: boolean; + /** + * @deprecated Use {@link isFormValidSignal} instead. + */ formValidChanges = new Subject(); fieldTitleSubscription?: Subscription; @@ -88,7 +94,7 @@ export class TitleChannelNameComponent implements AfterViewInit, OnDestroy, OnIn calculateFormValid(): void { const updatedFormValidValue = Boolean(this.field_title.valid && (!this.isChannelFieldDisplayed() || this.field_channel_name()?.valid)); - this.formValidSignal.set(updatedFormValidValue); + this.isFormValidSignal.set(updatedFormValidValue); this.formValid = updatedFormValidValue; this.formValidChanges.next(this.formValid); } diff --git a/src/test/javascript/spec/component/lecture-unit/online-unit/online-unit-form.component.spec.ts b/src/test/javascript/spec/component/lecture-unit/online-unit/online-unit-form.component.spec.ts index 6c36b6f44af5..8f4411cd4f40 100644 --- a/src/test/javascript/spec/component/lecture-unit/online-unit/online-unit-form.component.spec.ts +++ b/src/test/javascript/spec/component/lecture-unit/online-unit/online-unit-form.component.spec.ts @@ -121,7 +121,7 @@ describe('OnlineUnitFormComponent', () => { }); it('should correctly set form values in edit mode', () => { - onlineUnitFormComponent.isEditMode = true; + onlineUnitFormComponentFixture.componentRef.setInput('isEditMode', true); const formData: OnlineUnitFormData = { name: 'test', description: 'lorem ipsum', @@ -130,7 +130,7 @@ describe('OnlineUnitFormComponent', () => { }; onlineUnitFormComponentFixture.detectChanges(); - onlineUnitFormComponent.formData = formData; + onlineUnitFormComponentFixture.componentRef.setInput('formData', formData); onlineUnitFormComponent.ngOnChanges(); expect(onlineUnitFormComponent.nameControl?.value).toEqual(formData.name); @@ -151,10 +151,10 @@ describe('OnlineUnitFormComponent', () => { const getOnlineResourceStub = jest.spyOn(onlineUnitService, 'getOnlineResource').mockReturnValue(of(response)); onlineUnitFormComponentFixture.detectChanges(); - onlineUnitFormComponent.isEditMode = true; - onlineUnitFormComponent.formData = { + onlineUnitFormComponentFixture.componentRef.setInput('isEditMode', true); + onlineUnitFormComponentFixture.componentRef.setInput('formData', { source: 'example.com', - }; + }); onlineUnitFormComponent.ngOnChanges(); // WHEN diff --git a/src/test/javascript/spec/component/lecture-unit/text-unit/text-unit-form.component.spec.ts b/src/test/javascript/spec/component/lecture-unit/text-unit/text-unit-form.component.spec.ts index 4b9fcac77a25..fae301e74331 100644 --- a/src/test/javascript/spec/component/lecture-unit/text-unit/text-unit-form.component.spec.ts +++ b/src/test/javascript/spec/component/lecture-unit/text-unit/text-unit-form.component.spec.ts @@ -167,12 +167,11 @@ describe('TextUnitFormComponent', () => { // init textUnitFormComponentFixture.detectChanges(); // ngOnInit - textUnitFormComponent.isEditMode = true; + textUnitFormComponentFixture.componentRef.setInput('isEditMode', true); tick(); - // setting the form data - textUnitFormComponent.formData = formData; - textUnitFormComponent.ngOnChanges(); // ngOnChanges + textUnitFormComponentFixture.componentRef.setInput('formData', formData); + textUnitFormComponent.ngOnChanges(); textUnitFormComponentFixture.detectChanges(); expect(textUnitFormComponent.nameControl!.value).toEqual(formData.name);