From 0db5cc1631d41590657c2c7c33a0b9bd7593c121 Mon Sep 17 00:00:00 2001 From: Johannes Wiest Date: Sat, 16 Nov 2024 10:08:44 +0100 Subject: [PATCH] Development: Improve client code quality for learning paths (#9654) --- .../competency-graph-modal.component.ts | 23 ++++++++---- .../competency-graph.component.ts | 10 ++--- .../competency-node.component.ts | 3 +- .../learning-path-exercise.component.ts | 9 +++-- .../learning-path-lecture-unit.component.ts | 15 ++++++-- ...nav-overview-learning-objects.component.ts | 36 +++++++++--------- .../learning-path-nav-overview.component.ts | 37 +++++++++++-------- .../learning-path-student-nav.component.ts | 7 ++-- .../learning-paths-analytics.component.ts | 16 ++++++-- .../learning-paths-configuration.component.ts | 15 ++++++-- .../learning-paths-state.component.ts | 17 +++++++-- .../learning-paths-table.component.html | 1 + .../learning-paths-table.component.ts | 11 ++++-- .../exceptions/entity-not-found.error.ts | 6 --- ...learning-path-instructor-page.component.ts | 15 ++++++-- .../learning-path-student-page.component.ts | 11 +++++- ...arning-path-student-page.component.spec.ts | 5 +-- 17 files changed, 148 insertions(+), 89 deletions(-) delete mode 100644 src/main/webapp/app/course/learning-paths/exceptions/entity-not-found.error.ts diff --git a/src/main/webapp/app/course/learning-paths/components/competency-graph-modal/competency-graph-modal.component.ts b/src/main/webapp/app/course/learning-paths/components/competency-graph-modal/competency-graph-modal.component.ts index ac7a27af9f09..28594a2f49d9 100644 --- a/src/main/webapp/app/course/learning-paths/components/competency-graph-modal/competency-graph-modal.component.ts +++ b/src/main/webapp/app/course/learning-paths/components/competency-graph-modal/competency-graph-modal.component.ts @@ -1,25 +1,26 @@ -import { Component, effect, inject, input, signal } from '@angular/core'; -import { FontAwesomeModule, IconDefinition } from '@fortawesome/angular-fontawesome'; +import { ChangeDetectionStrategy, Component, effect, inject, input, signal, untracked } from '@angular/core'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { faXmark } from '@fortawesome/free-solid-svg-icons'; import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { CompetencyGraphComponent } from 'app/course/learning-paths/components/competency-graph/competency-graph.component'; -import { ArtemisSharedModule } from 'app/shared/shared.module'; import { LearningPathApiService } from 'app/course/learning-paths/services/learning-path-api.service'; import { AlertService } from 'app/core/util/alert.service'; import { CompetencyGraphDTO } from 'app/entities/competency/learning-path.model'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; @Component({ selector: 'jhi-competency-graph-modal', standalone: true, - imports: [FontAwesomeModule, CompetencyGraphComponent, ArtemisSharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [FontAwesomeModule, CompetencyGraphComponent, TranslateDirective], templateUrl: './competency-graph-modal.component.html', styleUrl: './competency-graph-modal.component.scss', }) export class CompetencyGraphModalComponent { - protected readonly closeIcon: IconDefinition = faXmark; + protected readonly closeIcon = faXmark; - private readonly learningPathApiService: LearningPathApiService = inject(LearningPathApiService); - private readonly alertService: AlertService = inject(AlertService); + private readonly learningPathApiService = inject(LearningPathApiService); + private readonly alertService = inject(AlertService); readonly learningPathId = input.required(); @@ -28,7 +29,13 @@ export class CompetencyGraphModalComponent { private readonly activeModal: NgbActiveModal = inject(NgbActiveModal); constructor() { - effect(() => this.loadCompetencyGraph(this.learningPathId()), { allowSignalWrites: true }); + effect( + () => { + const learningPathId = this.learningPathId(); + untracked(() => this.loadCompetencyGraph(learningPathId)); + }, + { allowSignalWrites: true }, + ); } private async loadCompetencyGraph(learningPathId: number): Promise { diff --git a/src/main/webapp/app/course/learning-paths/components/competency-graph/competency-graph.component.ts b/src/main/webapp/app/course/learning-paths/components/competency-graph/competency-graph.component.ts index 88cd93ac83e2..0e0bbe4c14a1 100644 --- a/src/main/webapp/app/course/learning-paths/components/competency-graph/competency-graph.component.ts +++ b/src/main/webapp/app/course/learning-paths/components/competency-graph/competency-graph.component.ts @@ -1,20 +1,18 @@ -import { Component, computed, effect, input, signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, effect, input, signal } from '@angular/core'; import { NgxGraphModule, NgxGraphZoomOptions } from '@swimlane/ngx-graph'; import { Subject } from 'rxjs'; -import { CompetencyGraphDTO, NodeType } from 'app/entities/competency/learning-path.model'; +import { CompetencyGraphDTO } from 'app/entities/competency/learning-path.model'; import { CompetencyNodeComponent, SizeUpdate } from 'app/course/learning-paths/components/competency-node/competency-node.component'; -import { ArtemisSharedModule } from 'app/shared/shared.module'; @Component({ selector: 'jhi-competency-graph', standalone: true, - imports: [CompetencyNodeComponent, NgxGraphModule, ArtemisSharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [CompetencyNodeComponent, NgxGraphModule], templateUrl: './competency-graph.component.html', styleUrl: './competency-graph.component.scss', }) export class CompetencyGraphComponent { - protected readonly NodeType = NodeType; - readonly competencyGraph = input.required(); private readonly internalCompetencyGraph = signal({ diff --git a/src/main/webapp/app/course/learning-paths/components/competency-node/competency-node.component.ts b/src/main/webapp/app/course/learning-paths/components/competency-node/competency-node.component.ts index 5365bb22387b..e4d60b32ef47 100644 --- a/src/main/webapp/app/course/learning-paths/components/competency-node/competency-node.component.ts +++ b/src/main/webapp/app/course/learning-paths/components/competency-node/competency-node.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { AfterViewInit, Component, ElementRef, computed, inject, input, output } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, computed, inject, input, output } from '@angular/core'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { NgbAccordionModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { NodeDimension } from '@swimlane/ngx-graph'; @@ -13,6 +13,7 @@ export interface SizeUpdate { @Component({ selector: 'jhi-learning-path-competency-node', standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgbDropdownModule, FontAwesomeModule, NgbAccordionModule, CommonModule], templateUrl: './competency-node.component.html', styleUrl: './competency-node.component.scss', diff --git a/src/main/webapp/app/course/learning-paths/components/learning-path-exercise/learning-path-exercise.component.ts b/src/main/webapp/app/course/learning-paths/components/learning-path-exercise/learning-path-exercise.component.ts index 05f904046533..99f230ce1938 100644 --- a/src/main/webapp/app/course/learning-paths/components/learning-path-exercise/learning-path-exercise.component.ts +++ b/src/main/webapp/app/course/learning-paths/components/learning-path-exercise/learning-path-exercise.component.ts @@ -1,18 +1,19 @@ -import { Component, InputSignal, ViewContainerRef, effect, inject, input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ViewContainerRef, effect, inject, input } from '@angular/core'; import { CourseExerciseDetailsComponent } from 'app/overview/exercise-details/course-exercise-details.component'; import { CourseExerciseDetailsModule } from 'app/overview/exercise-details/course-exercise-details.module'; @Component({ selector: 'jhi-learning-path-exercise', standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [CourseExerciseDetailsModule], templateUrl: './learning-path-exercise.component.html', }) export class LearningPathExerciseComponent { - public readonly courseId: InputSignal = input.required(); - public readonly exerciseId: InputSignal = input.required(); + public readonly courseId = input.required(); + public readonly exerciseId = input.required(); - private readonly viewContainerRef: ViewContainerRef = inject(ViewContainerRef); + private readonly viewContainerRef = inject(ViewContainerRef); constructor() { effect(() => { diff --git a/src/main/webapp/app/course/learning-paths/components/learning-path-lecture-unit/learning-path-lecture-unit.component.ts b/src/main/webapp/app/course/learning-paths/components/learning-path-lecture-unit/learning-path-lecture-unit.component.ts index 77f4cf3dd262..04822ff754d8 100644 --- a/src/main/webapp/app/course/learning-paths/components/learning-path-lecture-unit/learning-path-lecture-unit.component.ts +++ b/src/main/webapp/app/course/learning-paths/components/learning-path-lecture-unit/learning-path-lecture-unit.component.ts @@ -1,4 +1,4 @@ -import { Component, computed, effect, inject, input, signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, effect, inject, input, signal, untracked } from '@angular/core'; import { LectureUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/lectureUnit.service'; import { AlertService } from 'app/core/util/alert.service'; import { LectureUnit, LectureUnitType } from 'app/entities/lecture-unit/lectureUnit.model'; @@ -6,18 +6,19 @@ import { ArtemisLectureUnitsModule } from 'app/overview/course-lectures/lecture- import { LectureUnitCompletionEvent } from 'app/overview/course-lectures/course-lecture-details.component'; import { LearningPathNavigationService } from 'app/course/learning-paths/services/learning-path-navigation.service'; import { lastValueFrom } from 'rxjs'; -import { ArtemisSharedModule } from 'app/shared/shared.module'; import { VideoUnitComponent } from 'app/overview/course-lectures/video-unit/video-unit.component'; import { TextUnitComponent } from 'app/overview/course-lectures/text-unit/text-unit.component'; import { AttachmentUnitComponent } from 'app/overview/course-lectures/attachment-unit/attachment-unit.component'; import { OnlineUnitComponent } from 'app/overview/course-lectures/online-unit/online-unit.component'; import { isCommunicationEnabled } from 'app/entities/course.model'; import { DiscussionSectionComponent } from 'app/overview/discussion-section/discussion-section.component'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; @Component({ selector: 'jhi-learning-path-lecture-unit', standalone: true, - imports: [ArtemisLectureUnitsModule, ArtemisSharedModule, VideoUnitComponent, TextUnitComponent, AttachmentUnitComponent, OnlineUnitComponent, DiscussionSectionComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ArtemisLectureUnitsModule, VideoUnitComponent, TextUnitComponent, AttachmentUnitComponent, OnlineUnitComponent, DiscussionSectionComponent, TranslateDirective], templateUrl: './learning-path-lecture-unit.component.html', }) export class LearningPathLectureUnitComponent { @@ -36,7 +37,13 @@ export class LearningPathLectureUnitComponent { readonly isCommunicationEnabled = computed(() => isCommunicationEnabled(this.lecture()?.course)); constructor() { - effect(() => this.loadLectureUnit(this.lectureUnitId()), { allowSignalWrites: true }); + effect( + () => { + const lectureUnitId = this.lectureUnitId(); + untracked(() => this.loadLectureUnit(lectureUnitId)); + }, + { allowSignalWrites: true }, + ); } async loadLectureUnit(lectureUnitId: number): Promise { diff --git a/src/main/webapp/app/course/learning-paths/components/learning-path-nav-overview-learning-objects/learning-path-nav-overview-learning-objects.component.ts b/src/main/webapp/app/course/learning-paths/components/learning-path-nav-overview-learning-objects/learning-path-nav-overview-learning-objects.component.ts index 97609dd46c27..21c0fa4c1c32 100644 --- a/src/main/webapp/app/course/learning-paths/components/learning-path-nav-overview-learning-objects/learning-path-nav-overview-learning-objects.component.ts +++ b/src/main/webapp/app/course/learning-paths/components/learning-path-nav-overview-learning-objects/learning-path-nav-overview-learning-objects.component.ts @@ -1,47 +1,49 @@ -import { Component, InputSignal, OutputEmitterRef, Signal, WritableSignal, computed, effect, inject, input, output, signal, untracked } from '@angular/core'; -import { ArtemisSharedModule } from 'app/shared/shared.module'; +import { ChangeDetectionStrategy, Component, computed, effect, inject, input, output, signal, untracked } from '@angular/core'; import { AlertService } from 'app/core/util/alert.service'; import { LearningPathApiService } from 'app/course/learning-paths/services/learning-path-api.service'; import { LearningPathNavigationService } from 'app/course/learning-paths/services/learning-path-navigation.service'; import { LearningPathNavigationObjectDTO } from 'app/entities/competency/learning-path.model'; -import { IconDefinition, faCheckCircle, faLock } from '@fortawesome/free-solid-svg-icons'; +import { faCheckCircle, faLock } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; +import { CommonModule } from '@angular/common'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; @Component({ selector: 'jhi-learning-path-nav-overview-learning-objects', standalone: true, - imports: [NgbAccordionModule, FontAwesomeModule, ArtemisSharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [NgbAccordionModule, FontAwesomeModule, CommonModule, TranslateDirective], templateUrl: './learning-path-nav-overview-learning-objects.component.html', styleUrl: './learning-path-nav-overview-learning-objects.component.scss', }) export class LearningPathNavOverviewLearningObjectsComponent { - protected readonly faCheckCircle: IconDefinition = faCheckCircle; - protected readonly faLock: IconDefinition = faLock; + protected readonly faCheckCircle = faCheckCircle; + protected readonly faLock = faLock; - private readonly alertService: AlertService = inject(AlertService); - private readonly learningPathApiService: LearningPathApiService = inject(LearningPathApiService); + private readonly alertService = inject(AlertService); + private readonly learningPathApiService = inject(LearningPathApiService); private readonly learningPathNavigationService = inject(LearningPathNavigationService); - readonly learningPathId: InputSignal = input.required(); - readonly competencyId: InputSignal = input.required(); + readonly learningPathId = input.required(); + readonly competencyId = input.required(); // competency id of current competency of learning path (not the one of the selected learning object) - readonly currentCompetencyIdOnPath: InputSignal = input.required(); - readonly currentLearningObject: Signal = this.learningPathNavigationService.currentLearningObject; + readonly currentCompetencyIdOnPath = input.required(); + readonly currentLearningObject = this.learningPathNavigationService.currentLearningObject; - readonly isLoading: WritableSignal = signal(false); - readonly learningObjects: WritableSignal = signal(undefined); + readonly isLoading = signal(false); + readonly learningObjects = signal(undefined); - readonly nextLearningObjectOnPath: Signal = computed(() => + readonly nextLearningObjectOnPath = computed(() => this.competencyId() === this.currentCompetencyIdOnPath() ? this.learningObjects()?.find((learningObject) => !learningObject.completed) : undefined, ); - readonly onLearningObjectSelected: OutputEmitterRef = output(); + readonly onLearningObjectSelected = output(); constructor() { effect( () => { - untracked(async () => await this.loadLearningObjects()); + untracked(() => this.loadLearningObjects()); }, { allowSignalWrites: true }, ); diff --git a/src/main/webapp/app/course/learning-paths/components/learning-path-nav-overview/learning-path-nav-overview.component.ts b/src/main/webapp/app/course/learning-paths/components/learning-path-nav-overview/learning-path-nav-overview.component.ts index 07722fb3d0e7..6bf5c49cea7a 100644 --- a/src/main/webapp/app/course/learning-paths/components/learning-path-nav-overview/learning-path-nav-overview.component.ts +++ b/src/main/webapp/app/course/learning-paths/components/learning-path-nav-overview/learning-path-nav-overview.component.ts @@ -1,46 +1,53 @@ -import { Component, InputSignal, OutputEmitterRef, Signal, WritableSignal, computed, effect, inject, input, output, signal, viewChild } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, effect, inject, input, output, signal, untracked, viewChild } from '@angular/core'; import { NgbAccordionDirective, NgbAccordionModule, NgbDropdownModule, NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { CommonModule } from '@angular/common'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { IconDefinition, faCheckCircle } from '@fortawesome/free-solid-svg-icons'; +import { faCheckCircle } from '@fortawesome/free-solid-svg-icons'; import { AlertService } from 'app/core/util/alert.service'; import { LearningPathCompetencyDTO } from 'app/entities/competency/learning-path.model'; -import { ArtemisSharedModule } from 'app/shared/shared.module'; import { LearningPathApiService } from 'app/course/learning-paths/services/learning-path-api.service'; import { CompetencyGraphModalComponent } from 'app/course/learning-paths/components/competency-graph-modal/competency-graph-modal.component'; import { LearningPathNavOverviewLearningObjectsComponent } from 'app/course/learning-paths/components/learning-path-nav-overview-learning-objects/learning-path-nav-overview-learning-objects.component'; import { LearningPathNavigationService } from 'app/course/learning-paths/services/learning-path-navigation.service'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; @Component({ selector: 'jhi-learning-path-nav-overview', standalone: true, - imports: [FontAwesomeModule, CommonModule, NgbDropdownModule, NgbAccordionModule, ArtemisSharedModule, LearningPathNavOverviewLearningObjectsComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [FontAwesomeModule, CommonModule, NgbDropdownModule, NgbAccordionModule, LearningPathNavOverviewLearningObjectsComponent, TranslateDirective], templateUrl: './learning-path-nav-overview.component.html', styleUrl: './learning-path-nav-overview.component.scss', }) export class LearningPathNavOverviewComponent { - protected readonly faCheckCircle: IconDefinition = faCheckCircle; + protected readonly faCheckCircle = faCheckCircle; - private readonly alertService: AlertService = inject(AlertService); - private readonly modalService: NgbModal = inject(NgbModal); - private readonly learningPathApiService: LearningPathApiService = inject(LearningPathApiService); + private readonly alertService = inject(AlertService); + private readonly modalService = inject(NgbModal); + private readonly learningPathApiService = inject(LearningPathApiService); private readonly learningPathNavigationService = inject(LearningPathNavigationService); - readonly learningPathId: InputSignal = input.required(); + readonly learningPathId = input.required(); - readonly competencyAccordion: Signal = viewChild.required(NgbAccordionDirective); + readonly competencyAccordion = viewChild.required(NgbAccordionDirective); - readonly onLearningObjectSelected: OutputEmitterRef = output(); - readonly isLoading: WritableSignal = signal(false); + readonly onLearningObjectSelected = output(); + readonly isLoading = signal(false); readonly competencies = signal([]); // competency id of currently selected learning object - readonly currentCompetencyId: Signal = computed(() => this.learningPathNavigationService.currentLearningObject()?.competencyId); + readonly currentCompetencyId = computed(() => this.learningPathNavigationService.currentLearningObject()?.competencyId); // current competency of learning path (not the one of the selected learning object) - readonly currentCompetencyOnPath: Signal = computed(() => this.competencies()?.find((competency) => competency.masteryProgress < 1)); + readonly currentCompetencyOnPath = computed(() => this.competencies()?.find((competency) => competency.masteryProgress < 1)); constructor() { - effect(async () => await this.loadCompetencies(this.learningPathId()), { allowSignalWrites: true }); + effect( + () => { + const learningPathId = this.learningPathId(); + untracked(() => this.loadCompetencies(learningPathId)); + }, + { allowSignalWrites: true }, + ); } async loadCompetencies(learningPathId: number): Promise { diff --git a/src/main/webapp/app/course/learning-paths/components/learning-path-student-nav/learning-path-student-nav.component.ts b/src/main/webapp/app/course/learning-paths/components/learning-path-student-nav/learning-path-student-nav.component.ts index d243c506179b..dd938e28bd80 100644 --- a/src/main/webapp/app/course/learning-paths/components/learning-path-student-nav/learning-path-student-nav.component.ts +++ b/src/main/webapp/app/course/learning-paths/components/learning-path-student-nav/learning-path-student-nav.component.ts @@ -1,17 +1,18 @@ -import { Component, computed, effect, inject, input, signal, untracked } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, effect, inject, input, signal, untracked } from '@angular/core'; import { LearningPathNavigationObjectDTO } from 'app/entities/competency/learning-path.model'; import { CommonModule } from '@angular/common'; import { NgbAccordionModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { faCheckCircle, faChevronDown, faChevronLeft, faChevronRight, faFlag, faSpinner } from '@fortawesome/free-solid-svg-icons'; import { LearningPathNavOverviewComponent } from 'app/course/learning-paths/components/learning-path-nav-overview/learning-path-nav-overview.component'; -import { ArtemisSharedModule } from 'app/shared/shared.module'; import { LearningPathNavigationService } from 'app/course/learning-paths/services/learning-path-navigation.service'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; @Component({ selector: 'jhi-learning-path-student-nav', standalone: true, - imports: [CommonModule, NgbDropdownModule, NgbAccordionModule, FontAwesomeModule, LearningPathNavOverviewComponent, ArtemisSharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [CommonModule, NgbDropdownModule, NgbAccordionModule, FontAwesomeModule, LearningPathNavOverviewComponent, TranslateDirective], templateUrl: './learning-path-student-nav.component.html', styleUrl: './learning-path-student-nav.component.scss', }) diff --git a/src/main/webapp/app/course/learning-paths/components/learning-paths-analytics/learning-paths-analytics.component.ts b/src/main/webapp/app/course/learning-paths/components/learning-paths-analytics/learning-paths-analytics.component.ts index 41a8261eb206..6c2678d30c5a 100644 --- a/src/main/webapp/app/course/learning-paths/components/learning-paths-analytics/learning-paths-analytics.component.ts +++ b/src/main/webapp/app/course/learning-paths/components/learning-paths-analytics/learning-paths-analytics.component.ts @@ -1,15 +1,17 @@ -import { Component, effect, inject, input, signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, effect, inject, input, signal, untracked } from '@angular/core'; import { LearningPathApiService } from 'app/course/learning-paths/services/learning-path-api.service'; import { CompetencyGraphDTO, CompetencyGraphNodeValueType } from 'app/entities/competency/learning-path.model'; import { AlertService } from 'app/core/util/alert.service'; -import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; import { CompetencyGraphComponent } from 'app/course/learning-paths/components/competency-graph/competency-graph.component'; import { onError } from 'app/shared/util/global.utils'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { CommonModule } from '@angular/common'; @Component({ selector: 'jhi-learning-paths-analytics', standalone: true, - imports: [ArtemisSharedCommonModule, CompetencyGraphComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [CompetencyGraphComponent, TranslateDirective, CommonModule], templateUrl: './learning-paths-analytics.component.html', styleUrl: './learning-paths-analytics.component.scss', }) @@ -27,7 +29,13 @@ export class LearningPathsAnalyticsComponent { readonly valueSelection = signal(CompetencyGraphNodeValueType.AVERAGE_MASTERY_PROGRESS); constructor() { - effect(() => this.loadInstructionCompetencyGraph(this.courseId()), { allowSignalWrites: true }); + effect( + () => { + const courseId = this.courseId(); + untracked(() => this.loadInstructionCompetencyGraph(courseId)); + }, + { allowSignalWrites: true }, + ); } private async loadInstructionCompetencyGraph(courseId: number): Promise { diff --git a/src/main/webapp/app/course/learning-paths/components/learning-paths-configuration/learning-paths-configuration.component.ts b/src/main/webapp/app/course/learning-paths/components/learning-paths-configuration/learning-paths-configuration.component.ts index 23e5cb8e8612..6f2d305f54cc 100644 --- a/src/main/webapp/app/course/learning-paths/components/learning-paths-configuration/learning-paths-configuration.component.ts +++ b/src/main/webapp/app/course/learning-paths/components/learning-paths-configuration/learning-paths-configuration.component.ts @@ -1,17 +1,18 @@ -import { Component, computed, effect, inject, input, signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, effect, inject, input, signal, untracked } from '@angular/core'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { faSpinner } from '@fortawesome/free-solid-svg-icons'; import { LearningPathApiService } from '../../services/learning-path-api.service'; import { LearningPathsConfigurationDTO } from 'app/entities/competency/learning-path.model'; import { AlertService } from 'app/core/util/alert.service'; import { onError } from 'app/shared/util/global.utils'; -import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; @Component({ selector: 'jhi-learning-paths-configuration', standalone: true, - imports: [FontAwesomeModule, ArtemisSharedCommonModule, ArtemisSharedComponentModule], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [FontAwesomeModule, ArtemisSharedComponentModule, TranslateDirective], templateUrl: './learning-paths-configuration.component.html', styleUrls: ['../../pages/learning-path-instructor-page/learning-path-instructor-page.component.scss'], }) @@ -32,7 +33,13 @@ export class LearningPathsConfigurationComponent { readonly includeAllGradedExercisesEnabled = computed(() => this.learningPathsConfiguration()?.includeAllGradedExercises ?? false); constructor() { - effect(() => this.loadLearningPathsConfiguration(this.courseId()), { allowSignalWrites: true }); + effect( + () => { + const courseId = this.courseId(); + untracked(() => this.loadLearningPathsConfiguration(courseId)); + }, + { allowSignalWrites: true }, + ); } private async loadLearningPathsConfiguration(courseId: number): Promise { diff --git a/src/main/webapp/app/course/learning-paths/components/learning-paths-state/learning-paths-state.component.ts b/src/main/webapp/app/course/learning-paths/components/learning-paths-state/learning-paths-state.component.ts index e5fc57092472..a8460830ca71 100644 --- a/src/main/webapp/app/course/learning-paths/components/learning-paths-state/learning-paths-state.component.ts +++ b/src/main/webapp/app/course/learning-paths/components/learning-paths-state/learning-paths-state.component.ts @@ -1,16 +1,19 @@ -import { Component, computed, effect, inject, input, signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, effect, inject, input, signal, untracked } from '@angular/core'; import { LearningPathApiService } from 'app/course/learning-paths/services/learning-path-api.service'; import { HealthStatus, LearningPathHealthDTO } from 'app/entities/competency/learning-path-health.model'; import { AlertService } from 'app/core/util/alert.service'; import { onError } from 'app/shared/util/global.utils'; -import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; import { ActivatedRoute, Router } from '@angular/router'; import { faSpinner } from '@fortawesome/free-solid-svg-icons'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { CommonModule } from '@angular/common'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; @Component({ selector: 'jhi-learning-paths-state', standalone: true, - imports: [ArtemisSharedCommonModule], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [TranslateDirective, CommonModule, FontAwesomeModule], templateUrl: './learning-paths-state.component.html', styleUrls: ['./learning-paths-state.component.scss', '../../pages/learning-path-instructor-page/learning-path-instructor-page.component.scss'], }) @@ -42,7 +45,13 @@ export class LearningPathsStateComponent { readonly learningPathHealthState = computed(() => this.learningPathHealth()?.status ?? []); constructor() { - effect(() => this.loadLearningPathHealthState(this.courseId()), { allowSignalWrites: true }); + effect( + () => { + const courseId = this.courseId(); + untracked(() => this.loadLearningPathHealthState(courseId)); + }, + { allowSignalWrites: true }, + ); } protected async loadLearningPathHealthState(courseId: number): Promise { diff --git a/src/main/webapp/app/course/learning-paths/components/learning-paths-table/learning-paths-table.component.html b/src/main/webapp/app/course/learning-paths/components/learning-paths-table/learning-paths-table.component.html index 4f4b23a690e0..a97a5bbcd377 100644 --- a/src/main/webapp/app/course/learning-paths/components/learning-paths-table/learning-paths-table.component.html +++ b/src/main/webapp/app/course/learning-paths/components/learning-paths-table/learning-paths-table.component.html @@ -55,6 +55,7 @@
(false); constructor() { - effect(() => this.loadCourse(this.courseId()), { allowSignalWrites: true }); + effect( + () => { + const courseId = this.courseId(); + untracked(() => this.loadCourse(courseId)); + }, + { allowSignalWrites: true }, + ); } private async loadCourse(courseId: number): Promise { diff --git a/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.ts b/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.ts index 7d83742015f0..72c77fbf2d40 100644 --- a/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.ts +++ b/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.ts @@ -1,4 +1,4 @@ -import { Component, effect, inject, signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, effect, inject, signal, untracked } from '@angular/core'; import { LearningObjectType, LearningPathDTO } from 'app/entities/competency/learning-path.model'; import { map } from 'rxjs'; import { toSignal } from '@angular/core/rxjs-interop'; @@ -16,6 +16,7 @@ import { TranslateDirective } from 'app/shared/language/translate.directive'; selector: 'jhi-learning-path-student-page', templateUrl: './learning-path-student-page.component.html', styleUrl: './learning-path-student-page.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [LearningPathNavComponent, LearningPathLectureUnitComponent, LearningPathExerciseComponent, TranslateDirective], }) @@ -34,7 +35,13 @@ export class LearningPathStudentPageComponent { readonly isLearningPathNavigationLoading = this.learningPathNavigationService.isLoading; constructor() { - effect(() => this.loadLearningPath(this.courseId()), { allowSignalWrites: true }); + effect( + () => { + const courseId = this.courseId(); + untracked(() => this.loadLearningPath(courseId)); + }, + { allowSignalWrites: true }, + ); } private async loadLearningPath(courseId: number): Promise { diff --git a/src/test/javascript/spec/component/learning-paths/pages/learning-path-student-page.component.spec.ts b/src/test/javascript/spec/component/learning-paths/pages/learning-path-student-page.component.spec.ts index 3c4d22ecbf22..19b21643e037 100644 --- a/src/test/javascript/spec/component/learning-paths/pages/learning-path-student-page.component.spec.ts +++ b/src/test/javascript/spec/component/learning-paths/pages/learning-path-student-page.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { LearningPathStudentPageComponent } from 'app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component'; -import { provideHttpClient } from '@angular/common/http'; +import { HttpErrorResponse, provideHttpClient } from '@angular/common/http'; import { ActivatedRoute } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { of } from 'rxjs'; @@ -12,7 +12,6 @@ import { MockTranslateService } from '../../../helpers/mocks/service/mock-transl import { LearningPathApiService } from 'app/course/learning-paths/services/learning-path-api.service'; import { AlertService } from 'app/core/util/alert.service'; import { MockAlertService } from '../../../helpers/mocks/service/mock-alert.service'; -import { EntityNotFoundError } from 'app/course/learning-paths/exceptions/entity-not-found.error'; import { LearningPathDTO } from 'app/entities/competency/learning-path.model'; import { provideHttpClientTesting } from '@angular/common/http/testing'; @@ -127,7 +126,7 @@ describe('LearningPathStudentPageComponent', () => { }); it('should generate learning path on start when not found', async () => { - jest.spyOn(learningPathApiService, 'getLearningPathForCurrentUser').mockReturnValueOnce(Promise.reject(new EntityNotFoundError())); + jest.spyOn(learningPathApiService, 'getLearningPathForCurrentUser').mockReturnValueOnce(Promise.reject(new HttpErrorResponse({ status: 404 }))); const generateLearningPathSpy = jest.spyOn(learningPathApiService, 'generateLearningPathForCurrentUser').mockResolvedValue(learningPath); const startSpy = jest.spyOn(learningPathApiService, 'startLearningPathForCurrentUser');