From dc9f09bedefc5852378836ee7154d5295ef6afc4 Mon Sep 17 00:00:00 2001 From: lobo Date: Tue, 13 Oct 2026 11:10:04 +0200 Subject: [PATCH] refactor(components): use OnPush and signals in components Angular 22 defaults to OnPush, so Eager is removed. Template state is now signals, so sorting needs no detectChanges. Game of Life execution time now updates live. Remove the unused ImageDialogComponent and turn the OnPush lint rule back on. --- eslint.config.js | 2 - src/app/layout/app/app.component.ts | 3 +- src/app/layout/imageDialog/image.component.ts | 72 ----------------- src/app/layout/topbar/topbar.component.ts | 3 +- src/app/pages/about/about.component.ts | 3 +- .../pages/algorithms/algorithms.component.ts | 3 +- .../algorithms/cloth/cloth.component.html | 6 +- .../pages/algorithms/cloth/cloth.component.ts | 19 +++-- .../conway-gol/conway-gol.component.html | 10 +-- .../conway-gol/conway-gol.component.ts | 58 +++++++++----- .../four-color/four-color.component.html | 10 +-- .../four-color/four-color.component.ts | 75 +++++++++--------- .../algorithms/fractal/fractal.component.ts | 3 +- .../fractal3d/fractal3d.component.ts | 3 +- .../algorithms/information/information.html | 19 ++--- .../algorithms/information/information.ts | 5 +- .../labyrinth/labyrinth.component.html | 4 +- .../labyrinth/labyrinth.component.ts | 13 ++-- .../pathfinding/pathfinding.component.html | 12 +-- .../pathfinding/pathfinding.component.ts | 71 +++++++++-------- .../pendulum/pendulum.component.html | 14 ++-- .../algorithms/pendulum/pendulum.component.ts | 45 ++++++----- .../algorithms/sorting/sorting.component.html | 9 ++- .../sorting/sorting.component.spec.ts | 71 +++++++++++++++++ .../algorithms/sorting/sorting.component.ts | 77 ++++++++----------- src/app/pages/imprint/imprint.component.ts | 3 +- .../pages/not-found/not-found.component.ts | 3 +- .../dialog/project-dialog.component.ts | 3 +- src/app/pages/projects/projects.component.ts | 3 +- .../pages/stopwatch/stopwatch.component.ts | 3 +- .../components/generic-grid/generic-grid.ts | 3 +- .../particles-background.component.ts | 3 +- .../render-canvas/babylon-canvas.component.ts | 41 +++++----- 33 files changed, 336 insertions(+), 336 deletions(-) delete mode 100644 src/app/layout/imageDialog/image.component.ts create mode 100644 src/app/pages/algorithms/sorting/sorting.component.spec.ts diff --git a/eslint.config.js b/eslint.config.js index 2d0de58..7c67f09 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -18,8 +18,6 @@ module.exports = defineConfig([ "@typescript-eslint/no-inferrable-types": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/prefer-for-of": "off", - // ng update set ChangeDetectionStrategy.Eager everywhere; OnPush is a separate refactor. - "@angular-eslint/prefer-on-push-component-change-detection": "off", "@angular-eslint/directive-selector": [ "error", { diff --git a/src/app/layout/app/app.component.ts b/src/app/layout/app/app.component.ts index 03d4fb5..b95bee8 100644 --- a/src/app/layout/app/app.component.ts +++ b/src/app/layout/app/app.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, ChangeDetectionStrategy } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { RouterLink, RouterOutlet } from '@angular/router'; import {TopbarComponent} from '../topbar/topbar.component'; import {TranslatePipe} from '@ngx-translate/core'; @@ -10,7 +10,6 @@ import {SeoService} from '../../service/seo.service'; selector: 'app-root', imports: [RouterLink, RouterOutlet, TopbarComponent, TranslatePipe, ParticleBackgroundComponent], templateUrl: './app.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './app.component.scss' }) export class AppComponent { diff --git a/src/app/layout/imageDialog/image.component.ts b/src/app/layout/imageDialog/image.component.ts deleted file mode 100644 index 9e2151f..0000000 --- a/src/app/layout/imageDialog/image.component.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Component, inject, ChangeDetectionStrategy } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; -import { MatButtonModule } from '@angular/material/button'; -import { MatIconModule } from '@angular/material/icon'; - -@Component({ - imports: [MatDialogModule, MatButtonModule, MatIconModule], - template: ` -
-
-
{{ data.title }}
- -
- -
- -
-
- `, - changeDetection: ChangeDetectionStrategy.Eager, - styles: [` - .dialog { - display: flex; - flex-direction: column; - height: 100%; - } - - .topbar { - flex: 0 0 auto; - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; - - padding: 14px 16px; - } - - .title { - font-weight: 600; - line-height: 1.2; - max-width: calc(100% - 56px); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .stage { - flex: 1 1 auto; - padding: 0 16px 16px; - min-height: 0; - display: grid; - place-items: center; - } - - .full { - width: 100%; - height: 100%; - object-fit: contain; - border-radius: 12px; - } - `], -}) -export class ImageDialogComponent { - data = inject<{ - title: string; - src: string; -}>(MAT_DIALOG_DATA); - - -} diff --git a/src/app/layout/topbar/topbar.component.ts b/src/app/layout/topbar/topbar.component.ts index c1ee1dd..fa2e65b 100644 --- a/src/app/layout/topbar/topbar.component.ts +++ b/src/app/layout/topbar/topbar.component.ts @@ -1,4 +1,4 @@ -import { Component, computed, inject, ChangeDetectionStrategy } from '@angular/core'; +import { Component, computed, inject } from '@angular/core'; import { RouterLink, RouterLinkActive } from '@angular/router'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatIconModule } from '@angular/material/icon'; @@ -21,7 +21,6 @@ import {RouterConstants} from '../../constants/RouterConstants'; TranslatePipe, MatDivider, NgOptimizedImage ], templateUrl: './topbar.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './topbar.component.scss' }) export class TopbarComponent { diff --git a/src/app/pages/about/about.component.ts b/src/app/pages/about/about.component.ts index b9ba5f8..2b1c004 100644 --- a/src/app/pages/about/about.component.ts +++ b/src/app/pages/about/about.component.ts @@ -1,4 +1,4 @@ -import { Component, computed, signal, ChangeDetectionStrategy } from '@angular/core'; +import { Component, computed, signal } from '@angular/core'; import { NgOptimizedImage } from '@angular/common'; import { MatCardModule } from '@angular/material/card'; import { MatChipsModule } from '@angular/material/chips'; @@ -37,7 +37,6 @@ interface SkillGroup { RouterLink ], templateUrl: './about.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './about.component.scss' }) export class AboutComponent { diff --git a/src/app/pages/algorithms/algorithms.component.ts b/src/app/pages/algorithms/algorithms.component.ts index 8895c49..7ebb2a4 100644 --- a/src/app/pages/algorithms/algorithms.component.ts +++ b/src/app/pages/algorithms/algorithms.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, ChangeDetectionStrategy } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { AlgorithmsService } from './algorithms.service'; import { AlgorithmCategory } from './algorithm-category'; import { RouterLink } from '@angular/router'; @@ -10,7 +10,6 @@ import {TranslatePipe} from '@ngx-translate/core'; selector: 'app-algorithms', templateUrl: './algorithms.component.html', styleUrl: './algorithms.component.scss', - changeDetection: ChangeDetectionStrategy.Eager, imports: [RouterLink, MatCardModule, MatIconModule, TranslatePipe], }) export class AlgorithmsComponent { diff --git a/src/app/pages/algorithms/cloth/cloth.component.html b/src/app/pages/algorithms/cloth/cloth.component.html index 026005a..cbc11a7 100644 --- a/src/app/pages/algorithms/cloth/cloth.component.html +++ b/src/app/pages/algorithms/cloth/cloth.component.html @@ -7,17 +7,17 @@
- {{ 'CLOTH.ELONGATION' | translate }}: {{ elongation }} + {{ 'CLOTH.ELONGATION' | translate }}: {{ elongation() }} diff --git a/src/app/pages/algorithms/cloth/cloth.component.ts b/src/app/pages/algorithms/cloth/cloth.component.ts index 9f15e77..a248981 100644 --- a/src/app/pages/algorithms/cloth/cloth.component.ts +++ b/src/app/pages/algorithms/cloth/cloth.component.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; import {MatSliderModule} from '@angular/material/slider'; @@ -28,7 +28,6 @@ import {ClothCpuStrategy} from './strategies/cloth-cpu.strategy'; Information ], templateUrl: './cloth.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './cloth.component.scss', }) export class ClothComponent { @@ -36,10 +35,10 @@ export class ClothComponent { private simulationTime: number = 0; private strategy: ClothSimulationStrategy | null = null; - public isWindActive: boolean = false; - public isOutlineActive: boolean = false; + public readonly isWindActive = signal(false); + public readonly isOutlineActive = signal(false); public stiffness: number = 80; - public elongation: number = 1.0; + public readonly elongation = signal(1.0); public renderConfig: RenderConfig = { mode: '3D', @@ -85,16 +84,16 @@ export class ClothComponent { } public toggleWind(): void { - this.isWindActive = !this.isWindActive; + this.isWindActive.update(v => !v); } public toggleMesh(): void { - this.isOutlineActive = !this.isOutlineActive; + this.isOutlineActive.update(v => !v); const mesh = this.strategy?.getMesh(); if (!mesh?.material) { return; } - mesh.material.wireframe = this.isOutlineActive; + mesh.material.wireframe = this.isOutlineActive(); } public restartSimulation(): void { @@ -130,8 +129,8 @@ export class ClothComponent { if (this.strategy) { this.strategy.updateParams({ stiffness: this.stiffness, - elongation: this.elongation, - isWindActive: this.isWindActive, + elongation: this.elongation(), + isWindActive: this.isWindActive(), simulationTime: this.simulationTime, deltaTime: engine.getDeltaTime() / 1000.0 }); diff --git a/src/app/pages/algorithms/conway-gol/conway-gol.component.html b/src/app/pages/algorithms/conway-gol/conway-gol.component.html index 2cab315..dae6f6d 100644 --- a/src/app/pages/algorithms/conway-gol/conway-gol.component.html +++ b/src/app/pages/algorithms/conway-gol/conway-gol.component.html @@ -33,7 +33,7 @@ play_arrow {{ 'GOL.START' | translate }} } -

{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime }} ms

+

{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime() }} ms

@@ -44,7 +44,7 @@ [(ngModel)]="gridRows" [min]="MIN_GRID_SIZE" [max]="MAX_GRID_SIZE" - (ngModelChange)="pauseGame(); genericGridComponent.gridRows = gridRows; genericGridComponent.applyGridSize()" + (ngModelChange)="applyGridRows($event)" /> @@ -55,7 +55,7 @@ [(ngModel)]="gridCols" [min]="MIN_GRID_SIZE" [max]="MAX_GRID_SIZE" - (ngModelChange)="pauseGame(); genericGridComponent.gridCols = gridCols; genericGridComponent.applyGridSize()" + (ngModelChange)="applyGridCols($event)" /> @@ -78,8 +78,8 @@
= 0 && r < this.gridRows && c >= 0 && c < this.gridCols) { + if (r >= 0 && r < this.gridRows() && c >= 0 && c < this.gridCols()) { this.readGrid[r][c].alive = true; } } diff --git a/src/app/pages/algorithms/four-color/four-color.component.html b/src/app/pages/algorithms/four-color/four-color.component.html index 05a3851..16c3aae 100644 --- a/src/app/pages/algorithms/four-color/four-color.component.html +++ b/src/app/pages/algorithms/four-color/four-color.component.html @@ -49,13 +49,13 @@
{{ 'FOUR_COLOR.STATUS.LABEL' | translate }}: - {{ 'FOUR_COLOR.STATUS.' + solutionStatus | translate }} + {{ 'FOUR_COLOR.STATUS.' + solutionStatus() | translate }}
diff --git a/src/app/pages/algorithms/four-color/four-color.component.ts b/src/app/pages/algorithms/four-color/four-color.component.ts index bbedce1..a0676e1 100644 --- a/src/app/pages/algorithms/four-color/four-color.component.ts +++ b/src/app/pages/algorithms/four-color/four-color.component.ts @@ -1,4 +1,4 @@ -import {AfterViewInit, Component, ElementRef, inject, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {AfterViewInit, Component, ElementRef, inject, signal, ViewChild} from '@angular/core'; import {CommonModule} from '@angular/common'; import {FormsModule} from '@angular/forms'; import {MatButtonModule} from '@angular/material/button'; @@ -29,7 +29,6 @@ import {UrlConstants} from '../../../constants/UrlConstants'; Information ], templateUrl: './four-color.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './four-color.component.scss' }) export class FourColorComponent implements AfterViewInit { @@ -59,12 +58,12 @@ export class FourColorComponent implements AfterViewInit { ] }; - gridRows = DEFAULT_GRID_ROWS; - gridCols = DEFAULT_GRID_COLS; + readonly gridRows = signal(DEFAULT_GRID_ROWS); + readonly gridCols = signal(DEFAULT_GRID_COLS); grid: FourColorNode[][] = []; regions: Region[] = []; executionTime = 0; - solutionStatus: 'INCOMPLETE' | 'SOLVED' | 'CONFLICTS' | 'INVALID' = 'INCOMPLETE'; + readonly solutionStatus = signal<'INCOMPLETE' | 'SOLVED' | 'CONFLICTS' | 'INVALID'>('INCOMPLETE'); @ViewChild('fourColorCanvas') canvasRef!: ElementRef; private ctx!: CanvasRenderingContext2D; @@ -76,20 +75,18 @@ export class FourColorComponent implements AfterViewInit { } applyGridSize(): void { - if (this.gridRows < MIN_GRID_SIZE) this.gridRows = MIN_GRID_SIZE; - if (this.gridRows > MAX_GRID_SIZE) this.gridRows = MAX_GRID_SIZE; - if (this.gridCols < MIN_GRID_SIZE) this.gridCols = MIN_GRID_SIZE; - if (this.gridCols > MAX_GRID_SIZE) this.gridCols = MAX_GRID_SIZE; + this.gridRows.set(Math.min(Math.max(this.gridRows(), MIN_GRID_SIZE), MAX_GRID_SIZE)); + this.gridCols.set(Math.min(Math.max(this.gridCols(), MIN_GRID_SIZE), MAX_GRID_SIZE)); this.initializeGrid(); } initializeGrid(): void { this.grid = []; - this.solutionStatus = 'INCOMPLETE'; - for (let r = 0; r < this.gridRows; r++) { + this.solutionStatus.set('INCOMPLETE'); + for (let r = 0; r < this.gridRows(); r++) { const row: FourColorNode[] = []; - for (let c = 0; c < this.gridCols; c++) { + for (let c = 0; c < this.gridCols(); c++) { row.push({ row: r, col: c, @@ -107,15 +104,15 @@ export class FourColorComponent implements AfterViewInit { private resizeCanvas(): void { const canvas = this.canvasRef.nativeElement; - const maxDim = Math.max(this.gridRows, this.gridCols); + const maxDim = Math.max(this.gridRows(), this.gridCols()); this.nodeSize = Math.floor(MAX_GRID_PX / maxDim); - canvas.width = this.gridCols * this.nodeSize; - canvas.height = this.gridRows * this.nodeSize; + canvas.width = this.gridCols() * this.nodeSize; + canvas.height = this.gridRows() * this.nodeSize; } generateRegions(): void { - const numRegions = Math.floor((this.gridRows * this.gridCols) / 30); + const numRegions = Math.floor((this.gridRows() * this.gridCols()) / 30); this.regions = []; const seeds = this.determineSeeds(numRegions); this.regionGrowth(seeds); @@ -123,8 +120,8 @@ export class FourColorComponent implements AfterViewInit { } private determineAdjacency() { - for (let row = 0; row < this.gridRows; row++) { - for (let col = 0; col < this.gridCols; col++) { + for (let row = 0; row < this.gridRows(); row++) { + for (let col = 0; col < this.gridCols(); col++) { const currentRegionId = this.grid[row][col].regionId; const neighbors = this.getNeighbors(row, col); for (const neighbor of neighbors) { @@ -157,11 +154,11 @@ export class FourColorComponent implements AfterViewInit { private determineSeeds(numRegions: number) { const seeds: GridPos[] = []; for (let i = 0; i < numRegions; i++) { - let r = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1); - let c = SharedFunctions.randomIntFromInterval(0, this.gridCols - 1); + let r = SharedFunctions.randomIntFromInterval(0, this.gridRows() - 1); + let c = SharedFunctions.randomIntFromInterval(0, this.gridCols() - 1); while (this.grid[r][c].regionId !== -1) { - r = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1); - c = SharedFunctions.randomIntFromInterval(0, this.gridCols - 1); + r = SharedFunctions.randomIntFromInterval(0, this.gridRows() - 1); + c = SharedFunctions.randomIntFromInterval(0, this.gridCols() - 1); } this.grid[r][c].regionId = i; seeds.push({row: r, col: c}); @@ -173,9 +170,9 @@ export class FourColorComponent implements AfterViewInit { private getNeighbors(row: number, col: number): GridPos[] { const res: GridPos[] = []; if (row > 0) res.push({row: row - 1, col}); - if (row < this.gridRows - 1) res.push({row: row + 1, col}); + if (row < this.gridRows() - 1) res.push({row: row + 1, col}); if (col > 0) res.push({row, col: col - 1}); - if (col < this.gridCols - 1) res.push({row, col: col + 1}); + if (col < this.gridCols() - 1) res.push({row, col: col + 1}); return res; } @@ -185,8 +182,8 @@ export class FourColorComponent implements AfterViewInit { this.ctx.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height); // 1. Draw Cell Backgrounds - for (let r = 0; r < this.gridRows; r++) { - for (let c = 0; c < this.gridCols; c++) { + for (let r = 0; r < this.gridRows(); r++) { + for (let c = 0; c < this.gridCols(); c++) { const node = this.grid[r][c]; this.ctx.fillStyle = this.getNodeColor(node); this.ctx.fillRect(c * this.nodeSize, r * this.nodeSize, this.nodeSize, this.nodeSize); @@ -197,18 +194,18 @@ export class FourColorComponent implements AfterViewInit { this.ctx.strokeStyle = '#000'; this.ctx.lineWidth = 2; this.ctx.beginPath(); - for (let r = 0; r < this.gridRows; r++) { - for (let c = 0; c < this.gridCols; c++) { + for (let r = 0; r < this.gridRows(); r++) { + for (let c = 0; c < this.gridCols(); c++) { const currentRegion = this.grid[r][c].regionId; // Right border - if (c < this.gridCols - 1 && this.grid[r][c+1].regionId !== currentRegion) { + if (c < this.gridCols() - 1 && this.grid[r][c+1].regionId !== currentRegion) { this.ctx.moveTo((c + 1) * this.nodeSize, r * this.nodeSize); this.ctx.lineTo((c + 1) * this.nodeSize, (r + 1) * this.nodeSize); } // Bottom border - if (r < this.gridRows - 1 && this.grid[r+1][c].regionId !== currentRegion) { + if (r < this.gridRows() - 1 && this.grid[r+1][c].regionId !== currentRegion) { this.ctx.moveTo(c * this.nodeSize, (r + 1) * this.nodeSize); this.ctx.lineTo((c + 1) * this.nodeSize, (r + 1) * this.nodeSize); } @@ -219,7 +216,7 @@ export class FourColorComponent implements AfterViewInit { // 3. Draw Outer Border this.ctx.strokeStyle = '#000'; this.ctx.lineWidth = 2; - this.ctx.strokeRect(0, 0, this.gridCols * this.nodeSize, this.gridRows * this.nodeSize); + this.ctx.strokeRect(0, 0, this.gridCols() * this.nodeSize, this.gridRows() * this.nodeSize); } private getNodeColor(node: FourColorNode): string { @@ -260,10 +257,10 @@ export class FourColorComponent implements AfterViewInit { const x = event.clientX - rect.left; const y = event.clientY - rect.top; - const col = Math.floor(x / (rect.width / this.gridCols)); - const row = Math.floor(y / (rect.height / this.gridRows)); + const col = Math.floor(x / (rect.width / this.gridCols())); + const row = Math.floor(y / (rect.height / this.gridRows())); - if (row >= 0 && row < this.gridRows && col >= 0 && col < this.gridCols) { + if (row >= 0 && row < this.gridRows() && col >= 0 && col < this.gridCols()) { return {row, col}; } return null; @@ -283,8 +280,8 @@ export class FourColorComponent implements AfterViewInit { } private updateRegionColors(region: Region): void { - for (let row = 0; row < this.gridRows; row++) { - for (let col = 0; col < this.gridCols; col++) { + for (let row = 0; row < this.gridRows(); row++) { + for (let col = 0; col < this.gridCols(); col++) { if (this.grid[row][col].regionId === region.id) { this.grid[row][col].color = region.color; } @@ -297,7 +294,7 @@ export class FourColorComponent implements AfterViewInit { region.color = 0; this.updateRegionColors(region); } - this.solutionStatus = 'INCOMPLETE'; + this.solutionStatus.set('INCOMPLETE'); this.drawGrid(); } @@ -363,9 +360,9 @@ export class FourColorComponent implements AfterViewInit { } if (hasConflicts) { - this.solutionStatus = allColored ? 'INVALID' : 'CONFLICTS'; + this.solutionStatus.set(allColored ? 'INVALID' : 'CONFLICTS'); } else { - this.solutionStatus = allColored ? 'SOLVED' : 'INCOMPLETE'; + this.solutionStatus.set(allColored ? 'SOLVED' : 'INCOMPLETE'); } } diff --git a/src/app/pages/algorithms/fractal/fractal.component.ts b/src/app/pages/algorithms/fractal/fractal.component.ts index 8008d43..c1c6707 100644 --- a/src/app/pages/algorithms/fractal/fractal.component.ts +++ b/src/app/pages/algorithms/fractal/fractal.component.ts @@ -1,4 +1,4 @@ -import {Component, OnInit, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, OnInit, signal} from '@angular/core'; import {Information} from '../information/information'; import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; import {TranslatePipe} from '@ngx-translate/core'; @@ -35,7 +35,6 @@ import {NgxSliderModule, Options} from '@angular-slider/ngx-slider'; NgxSliderModule ], templateUrl: './fractal.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './fractal.component.scss', }) export class FractalComponent implements OnInit { diff --git a/src/app/pages/algorithms/fractal3d/fractal3d.component.ts b/src/app/pages/algorithms/fractal3d/fractal3d.component.ts index 30a158f..b9244b7 100644 --- a/src/app/pages/algorithms/fractal3d/fractal3d.component.ts +++ b/src/app/pages/algorithms/fractal3d/fractal3d.component.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ArcRotateCamera, Camera, ShaderMaterial} from '@babylonjs/core'; import {MANDELBULB_FRAGMENT, MANDELBULB_VERTEX} from './fractal3d.shader'; import {Information} from '../information/information'; @@ -22,7 +22,6 @@ import {BabylonCanvas, RenderCallback, RenderConfig} from '../../../shared/compo BabylonCanvas ], templateUrl: './fractal3d.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './fractal3d.component.scss', }) export class Fractal3dComponent { diff --git a/src/app/pages/algorithms/information/information.html b/src/app/pages/algorithms/information/information.html index 13125e9..51b5f2b 100644 --- a/src/app/pages/algorithms/information/information.html +++ b/src/app/pages/algorithms/information/information.html @@ -1,8 +1,9 @@ +@let info = algorithmInformation();
-

{{ algorithmInformation.title | translate }}

+

{{ info.title | translate }}

- @if(algorithmInformation.entries && algorithmInformation.entries.length > 0){ - @for (algo of algorithmInformation.entries; track algo) + @if(info.entries && info.entries.length > 0){ + @for (algo of info.entries; track algo) {

@@ -18,24 +19,24 @@ } } - @if (algorithmInformation.disclaimer !== '') + @if (info.disclaimer !== '') {

- {{ 'ALGORITHM.NOTE' | translate}} {{ algorithmInformation.disclaimer | translate}} + {{ 'ALGORITHM.NOTE' | translate}} {{ info.disclaimer | translate}}

- @if (algorithmInformation.disclaimerListEntry && algorithmInformation.disclaimerListEntry.length > 0) + @if (info.disclaimerListEntry && info.disclaimerListEntry.length > 0) {
    - @for (entry of algorithmInformation.disclaimerListEntry; track entry) + @for (entry of info.disclaimerListEntry; track entry) {
  • {{ entry | translate}}
  • }
} - @if (algorithmInformation.disclaimerBottom !== '') + @if (info.disclaimerBottom !== '') {

- {{ algorithmInformation.disclaimerBottom | translate}} + {{ info.disclaimerBottom | translate}}

} } diff --git a/src/app/pages/algorithms/information/information.ts b/src/app/pages/algorithms/information/information.ts index 9669c94..a80387d 100644 --- a/src/app/pages/algorithms/information/information.ts +++ b/src/app/pages/algorithms/information/information.ts @@ -1,4 +1,4 @@ -import {Component, Input, ChangeDetectionStrategy} from '@angular/core'; +import {Component, input} from '@angular/core'; import {TranslatePipe} from "@ngx-translate/core"; import {AlgorithmInformation} from './information.models'; @@ -8,10 +8,9 @@ import {AlgorithmInformation} from './information.models'; TranslatePipe ], templateUrl: './information.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './information.scss', }) export class Information { - @Input({ required: true }) algorithmInformation!: AlgorithmInformation; + readonly algorithmInformation = input.required(); } diff --git a/src/app/pages/algorithms/pathfinding/labyrinth/labyrinth.component.html b/src/app/pages/algorithms/pathfinding/labyrinth/labyrinth.component.html index f394cc3..3d2d88c 100644 --- a/src/app/pages/algorithms/pathfinding/labyrinth/labyrinth.component.html +++ b/src/app/pages/algorithms/pathfinding/labyrinth/labyrinth.component.html @@ -22,8 +22,8 @@ {{ 'PATHFINDING.PATH' | translate }}
-

{{ 'PATHFINDING.PATH_LENGTH' | translate }}: {{ pathLength }}

-

{{ 'PATHFINDING.EXECUTION_TIME' | translate }}: {{ executionTime | number:'1.2-2' }} ms

+

{{ 'PATHFINDING.PATH_LENGTH' | translate }}: {{ pathLength() }}

+

{{ 'PATHFINDING.EXECUTION_TIME' | translate }}: {{ executionTime() | number:'1.2-2' }} ms

@@ -46,7 +46,7 @@ [min]="MIN_GRID_SIZE" [max]="MAX_GRID_SIZE" [(ngModel)]="gridCols" - (ngModelChange)="genericGridComponent.gridCols = gridCols; genericGridComponent.applyGridSize()" + (ngModelChange)="applyGridCols($event)" /> @@ -59,14 +59,14 @@ {{ 'PATHFINDING.PATH' | translate }}
-

{{ 'PATHFINDING.PATH_LENGTH' | translate }}: {{ pathLength }}

-

{{ 'PATHFINDING.EXECUTION_TIME' | translate }}: {{ executionTime | number:'1.2-2' }} ms

+

{{ 'PATHFINDING.PATH_LENGTH' | translate }}: {{ pathLength() }}

+

{{ 'PATHFINDING.EXECUTION_TIME' | translate }}: {{ executionTime() | number:'1.2-2' }} ms

(NodeType.None); private shouldAddWall = true; // Moved here animationSpeed = 3; - pathLength = "0"; - executionTime = 0; + readonly pathLength = signal("0"); + readonly executionTime = signal(0); private timeoutIds: number[] = []; @@ -101,8 +100,8 @@ export class PathfindingComponent implements AfterViewInit { this.genericGridComponent.createNodeFn = this.createPathfindingNode; this.genericGridComponent.getNodeColorFn = this.getPathfindingNodeColor; this.genericGridComponent.applySelectionFn = this.applyPathfindingSelection; - this.genericGridComponent.gridRows = this.gridRows; - this.genericGridComponent.gridCols = this.gridCols; + this.genericGridComponent.gridRows = this.gridRows(); + this.genericGridComponent.gridCols = this.gridCols(); this.genericGridComponent.minGridSize = this.MIN_GRID_SIZE; this.genericGridComponent.maxGridSize = this.MAX_GRID_SIZE; this.genericGridComponent.maxGridPx = MAX_GRID_PX; @@ -111,6 +110,16 @@ export class PathfindingComponent implements AfterViewInit { this.createCase({withWalls: true, scenario: "normal"}); } + protected applyGridRows(rows: number): void { + this.genericGridComponent.gridRows = rows; + this.genericGridComponent.applyGridSize(); + } + + protected applyGridCols(cols: number): void { + this.genericGridComponent.gridCols = cols; + this.genericGridComponent.applyGridSize(); + } + // --- Callbacks for GenericGridComponent --- createPathfindingNode = (row: number, col: number): Node => { return { @@ -142,11 +151,11 @@ export class PathfindingComponent implements AfterViewInit { const node = grid[pos.row][pos.col]; // Determine if we should add or remove a wall - if (this.selectedNodeType === NodeType.Wall && this.genericGridComponent.isDrawing && this.genericGridComponent['lastCell'] === null) { + if (this.selectedNodeType() === NodeType.Wall && this.genericGridComponent.isDrawing && this.genericGridComponent['lastCell'] === null) { this.shouldAddWall = !node.isWall; } - switch (this.selectedNodeType) { + switch (this.selectedNodeType()) { case NodeType.Start: this.trySetStart(node); break; @@ -200,13 +209,13 @@ export class PathfindingComponent implements AfterViewInit { const lengthOfShortestPath = result.nodesInShortestPathOrder.length; if (lengthOfShortestPath === 0) { - this.pathLength = "∞" + this.pathLength.set("∞"); } else { - this.pathLength = result.nodesInShortestPathOrder.length + ""; + this.pathLength.set(result.nodesInShortestPathOrder.length + ""); } - this.executionTime = endTime - startTime; + this.executionTime.set(endTime - startTime); this.animateAlgorithm(result.visitedNodesInOrder, result.nodesInShortestPathOrder); } @@ -312,31 +321,31 @@ export class PathfindingComponent implements AfterViewInit { if (scenario === 'edge') { return { start: {row: 0, col: 0}, - end: {row: this.gridRows - 1, col: this.gridCols - 1} + end: {row: this.gridRows() - 1, col: this.gridCols() - 1} }; } else if (scenario === 'random') { return this.createRandomStartEndPosition(); } else { // normal: mid-left -> mid-right - const midRow = Math.floor(this.gridRows / 2); + const midRow = Math.floor(this.gridRows() / 2); return { start: {row: midRow, col: 0}, - end: {row: midRow, col: this.gridCols - 1} + end: {row: midRow, col: this.gridCols() - 1} }; } } private createRandomStartEndPosition(): { start: GridPos; end: GridPos } { - const midCol = Math.floor(this.gridCols / 2); + const midCol = Math.floor(this.gridCols() / 2); - const startRow: number = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1); - const startCol: number = SharedFunctions.randomIntFromInterval(0, this.gridCols - 1); + const startRow: number = SharedFunctions.randomIntFromInterval(0, this.gridRows() - 1); + const startCol: number = SharedFunctions.randomIntFromInterval(0, this.gridCols() - 1); - const endRow: number = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1); + const endRow: number = SharedFunctions.randomIntFromInterval(0, this.gridRows() - 1); let endCol: number; if (startCol <= midCol) { - endCol = SharedFunctions.randomIntFromInterval(midCol + 1, this.gridCols - 1); + endCol = SharedFunctions.randomIntFromInterval(midCol + 1, this.gridCols() - 1); } else { endCol = SharedFunctions.randomIntFromInterval(0, midCol); } @@ -358,12 +367,12 @@ export class PathfindingComponent implements AfterViewInit { } private createRandomWalls() { - const maxNumberOfWalls = Math.floor(MAX_RANDOM_WALLS_FACTORS * this.gridCols * this.gridRows); + const maxNumberOfWalls = Math.floor(MAX_RANDOM_WALLS_FACTORS * this.gridCols() * this.gridRows()); for (let wall = 0; wall < maxNumberOfWalls; wall++) { - const row: number = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1); - const col: number = SharedFunctions.randomIntFromInterval(0, this.gridCols - 1); + const row: number = SharedFunctions.randomIntFromInterval(0, this.gridRows() - 1); + const col: number = SharedFunctions.randomIntFromInterval(0, this.gridCols() - 1); if (!this.grid[row][col]) { // Use the grid passed from GenericGrid wall--; @@ -382,8 +391,8 @@ export class PathfindingComponent implements AfterViewInit { } private createVerticalWall() { - const height = this.gridRows; - const startCol = Math.floor(this.gridCols / 2); + const height = this.gridRows(); + const startCol = Math.floor(this.gridCols() / 2); for (let i = 5; i < (height - 5); i++) { const row = i; @@ -404,8 +413,8 @@ export class PathfindingComponent implements AfterViewInit { private createDiagonalWall() { // Diagonal-ish wall; avoids start/end - const len = Math.min(this.gridRows, this.gridCols); - const startCol = Math.floor((this.gridCols - len) / 2); + const len = Math.min(this.gridRows(), this.gridCols()); + const startCol = Math.floor((this.gridCols() - len) / 2); for (let i = 0; i < Math.max(0, len - 10); i++) { const row = len - i - 1; @@ -433,8 +442,8 @@ export class PathfindingComponent implements AfterViewInit { } private clearPath(): void { - for (let row = 0; row < this.gridRows; row++) { - for (let col = 0; col < this.gridCols; col++) { + for (let row = 0; row < this.gridRows(); row++) { + for (let col = 0; col < this.gridCols(); col++) { const node = this.grid[row][col]; node.isVisited = false; node.isPath = false; diff --git a/src/app/pages/algorithms/pendulum/pendulum.component.html b/src/app/pages/algorithms/pendulum/pendulum.component.html index 055acb6..c9f42e0 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.component.html +++ b/src/app/pages/algorithms/pendulum/pendulum.component.html @@ -8,34 +8,34 @@

{{ 'PENDULUM.TRAIL_DECAY_TIME' | translate }}

- +

{{ 'PENDULUM.ATTRACTION' | translate }}

- +

{{ 'PENDULUM.L1_LENGTH' | translate }}

- +

{{ 'PENDULUM.L2_LENGTH' | translate }}

- +

{{ 'PENDULUM.M1_MASS' | translate }}

- +

{{ 'PENDULUM.M2_MASS' | translate }}

- +

{{ 'PENDULUM.DAMPING' | translate }}

- +
diff --git a/src/app/pages/algorithms/pendulum/pendulum.component.ts b/src/app/pages/algorithms/pendulum/pendulum.component.ts index e82eb12..43d0de4 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.component.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.component.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {BabylonCanvas, RenderConfig, SceneEventData} from '../../../shared/components/render-canvas/babylon-canvas.component'; import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; @@ -29,7 +29,6 @@ import {PendulumCpuStrategy} from './strategies/pendulum-cpu.strategy'; Information, ], templateUrl: './pendulum.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './pendulum.component.scss', }) class PendulumComponent { @@ -103,16 +102,17 @@ class PendulumComponent { hidePointerLabels: false }; - readonly simParams = { + readonly trailDecay = signal(DEFAULT_TRAIL_DECAY); + readonly g = signal(DEFAULT_G); + readonly l1 = signal(DEFAULT_L1_LENGTH); + readonly l2 = signal(DEFAULT_L2_LENGTH); + readonly m1 = signal(DEFAULT_M1_MASS); + readonly m2 = signal(DEFAULT_M2_MASS); + readonly damping = signal(DEFAULT_DAMPING); + + private readonly loop = { time: 0, dt: 0.015, - g: DEFAULT_G, - m1: DEFAULT_M1_MASS, - m2: DEFAULT_M2_MASS, - l1: DEFAULT_L1_LENGTH, - l2: DEFAULT_L2_LENGTH, - damping: DEFAULT_DAMPING, - trailDecay: DEFAULT_TRAIL_DECAY, impulseM1: 0.0, impulseM2: 0.0, }; @@ -147,10 +147,19 @@ class PendulumComponent { private startParamUpdateLoop(scene: any): void { scene.onAfterRenderObservable.clear(); scene.onAfterRenderObservable.add(() => { - this.simParams.time += this.simParams.dt; + this.loop.time += this.loop.dt; if (this.strategy) { - this.strategy.updateParams({...this.simParams}); + this.strategy.updateParams({ + ...this.loop, + g: this.g(), + m1: this.m1(), + m2: this.m2(), + l1: this.l1(), + l2: this.l2(), + damping: this.damping(), + trailDecay: this.trailDecay() + }); } this.resetImpulses(); @@ -158,22 +167,22 @@ class PendulumComponent { } private resetImpulses() { - if (this.simParams.impulseM1 !== 0.0) { - this.simParams.impulseM1 = 0; + if (this.loop.impulseM1 !== 0.0) { + this.loop.impulseM1 = 0; } - if (this.simParams.impulseM2 !== 0.0) { - this.simParams.impulseM2 = 0; + if (this.loop.impulseM2 !== 0.0) { + this.loop.impulseM2 = 0; } } pushPendulum(m1: boolean) { if (m1) { - this.simParams.impulseM1 = IMPULSE_M1; + this.loop.impulseM1 = IMPULSE_M1; return; } - this.simParams.impulseM2 = IMPULSE_M2; + this.loop.impulseM2 = IMPULSE_M2; } resetPendulum() { diff --git a/src/app/pages/algorithms/sorting/sorting.component.html b/src/app/pages/algorithms/sorting/sorting.component.html index ababbbe..3b9de29 100644 --- a/src/app/pages/algorithms/sorting/sorting.component.html +++ b/src/app/pages/algorithms/sorting/sorting.component.html @@ -38,19 +38,20 @@ add_box {{ 'SORTING.GENERATE_NEW_ARRAY' | translate }}
-

{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime }} ms

+

{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime() }} ms

- @for (item of sortArray; track $index) { + @for (item of sortArray(); track $index) {
Number.parseFloat((bar.nativeElement as HTMLElement).style.height)); + } + + it('shows every bar as sorted once the animation has run', () => { + fixture.componentInstance.startSorting(); + vi.runAllTimers(); + fixture.detectChanges(); + + expect(bars()).toHaveLength(50); + expect(bars().every(bar => bar.nativeElement.classList.contains('bg-[#4caf50]'))).toBe(true); + const sorted = heights(); + expect(sorted).toEqual([...sorted].sort((a, b) => a - b)); + }); + + it('restores the unsorted array when reset during the animation', () => { + const initial = heights(); + + fixture.componentInstance.startSorting(); + vi.advanceTimersByTime(300); + fixture.componentInstance.resetSorting(); + fixture.detectChanges(); + + expect(heights()).toEqual(initial); + expect(bars().some(bar => bar.nativeElement.classList.contains('bg-[#ffeb3b]'))).toBe(false); + }); +}); diff --git a/src/app/pages/algorithms/sorting/sorting.component.ts b/src/app/pages/algorithms/sorting/sorting.component.ts index d71ded6..24d13d6 100644 --- a/src/app/pages/algorithms/sorting/sorting.component.ts +++ b/src/app/pages/algorithms/sorting/sorting.component.ts @@ -1,4 +1,4 @@ -import {ChangeDetectorRef, Component, inject, OnInit, ChangeDetectionStrategy} from '@angular/core'; +import {Component, inject, OnInit, signal} from '@angular/core'; import { CommonModule } from '@angular/common'; import {MatCardModule} from "@angular/material/card"; import {MatFormFieldModule} from "@angular/material/form-field"; @@ -18,14 +18,12 @@ import {Information} from '../information/information'; selector: 'app-sorting', imports: [CommonModule, MatCardModule, MatFormFieldModule, MatSelectModule, MatButtonModule, MatIconModule, TranslatePipe, FormsModule, MatInput, Information], templateUrl: './sorting.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './sorting.component.scss' }) export class SortingComponent implements OnInit { private readonly sortingService: SortingService = inject(SortingService); private readonly audioService: SortingAudioService = inject(SortingAudioService); - private readonly cdr: ChangeDetectorRef = inject(ChangeDetectorRef); readonly MAX_ARRAY_SIZE: number = 200; readonly MIN_ARRAY_SIZE: number = 20; @@ -69,14 +67,14 @@ export class SortingComponent implements OnInit { }; private timeoutIds: number[] = []; - sortArray: SortData[] = []; + readonly sortArray = signal([]); unsortedArrayCopy: SortData[] = []; - arraySize = 50; + readonly arraySize = signal(50); maxArrayValue = 100; animationSpeed = 100; // Milliseconds per step - selectedAlgorithm: string = this.algoInformation.entries[0].name; - executionTime = 0; - isSoundEnabled = false; + readonly selectedAlgorithm = signal(this.algoInformation.entries[0].name); + readonly executionTime = signal(0); + readonly isSoundEnabled = signal(false); ngOnInit(): void { this.generateNewArray(); @@ -84,8 +82,9 @@ export class SortingComponent implements OnInit { newArraySizeSet() { - this.arraySize = Math.min(Math.max(this.arraySize, this.MIN_ARRAY_SIZE), this.MAX_ARRAY_SIZE); - if (this.arraySize == this.sortArray.length) + const clamped = Math.min(Math.max(this.arraySize(), this.MIN_ARRAY_SIZE), this.MAX_ARRAY_SIZE); + this.arraySize.set(clamped); + if (clamped == this.sortArray().length) { return; } @@ -94,35 +93,27 @@ export class SortingComponent implements OnInit { generateNewArray(): void { this.resetSorting(); - this.executionTime = 0; - this.unsortedArrayCopy = []; - this.sortArray = []; + this.executionTime.set(0); + const unsorted: SortData[] = []; - for (let i = 0; i < this.arraySize; i++) { + for (let i = 0; i < this.arraySize(); i++) { const randomValue = Math.floor(Math.random() * this.maxArrayValue) + 1; - this.sortArray.push({ - value: randomValue, - state: 'unsorted' - }); - - this.unsortedArrayCopy.push({ + unsorted.push({ value: randomValue, state: 'unsorted' }); } + + this.unsortedArrayCopy = unsorted; + this.resetSortState(); } private resetSortState() { - for (let i = 0; i < this.sortArray.length; i++) { - const element = this.sortArray[i]; - const unsortedElement = this.unsortedArrayCopy[i]; - element.value = unsortedElement.value; - element.state = 'unsorted'; - } + this.sortArray.set(this.unsortedArrayCopy.map(item => ({...item}))); } toggleSound(): void { - this.isSoundEnabled = !this.isSoundEnabled; + this.isSoundEnabled.update(v => !v); } async startSorting(): Promise { @@ -132,26 +123,26 @@ export class SortingComponent implements OnInit { const startTime = performance.now(); let snapshots: SortSnapshot[] = []; - switch (this.selectedAlgorithm) { + switch (this.selectedAlgorithm()) { case 'Bubble Sort': - snapshots = this.sortingService.bubbleSort(this.sortArray); + snapshots = this.sortingService.bubbleSort(this.sortArray()); break; case 'Quick Sort': - snapshots = this.sortingService.quickSort(this.sortArray); + snapshots = this.sortingService.quickSort(this.sortArray()); break; case 'Heap Sort': - snapshots = this.sortingService.heapSort(this.sortArray); + snapshots = this.sortingService.heapSort(this.sortArray()); break; case 'Cocktail Sort': - snapshots = this.sortingService.cocktailSort(this.sortArray); + snapshots = this.sortingService.cocktailSort(this.sortArray()); break; case 'Tim Sort': - snapshots = this.sortingService.timSort(this.sortArray); + snapshots = this.sortingService.timSort(this.sortArray()); break; } const endTime = performance.now(); - this.executionTime = Number.parseFloat((endTime - startTime).toFixed(4)); + this.executionTime.set(Number.parseFloat((endTime - startTime).toFixed(4))); this.animateSorting(snapshots); } @@ -159,14 +150,9 @@ export class SortingComponent implements OnInit { private animateSorting(snapshots: SortSnapshot[]): void { snapshots.forEach((snapshot, index) => { const id = setTimeout(() => { - for (let i = 0; i < this.sortArray.length; i++) { - if (snapshot.array[i]) { - this.sortArray[i].value = snapshot.array[i].value; - this.sortArray[i].state = snapshot.array[i].state; - } - } + this.sortArray.set(snapshot.array); - if (this.isSoundEnabled) { + if (this.isSoundEnabled()) { // Play a tone for each comparing bar (max 2 at once to avoid noise) snapshot.array .filter(item => item.state === 'comparing') @@ -174,13 +160,10 @@ export class SortingComponent implements OnInit { .forEach(item => this.audioService.playTone(item.value, this.maxArrayValue, this.animationSpeed)); } - this.cdr.detectChanges(); - if (index === snapshots.length - 1) { - this.sortArray.forEach(item => item.state = 'sorted'); - this.cdr.detectChanges(); - if (this.isSoundEnabled) { - this.audioService.playSortedSweep(this.sortArray.map(item => item.value), this.maxArrayValue); + this.sortArray.set(snapshot.array.map(item => ({...item, state: 'sorted'}))); + if (this.isSoundEnabled()) { + this.audioService.playSortedSweep(this.sortArray().map(item => item.value), this.maxArrayValue); } } }, index * this.animationSpeed); diff --git a/src/app/pages/imprint/imprint.component.ts b/src/app/pages/imprint/imprint.component.ts index 4f35f47..c1579c7 100644 --- a/src/app/pages/imprint/imprint.component.ts +++ b/src/app/pages/imprint/imprint.component.ts @@ -1,4 +1,4 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { Component } from '@angular/core'; import {MatCard} from '@angular/material/card'; import {TranslatePipe} from '@ngx-translate/core'; import {SharedFunctions} from '../../shared/SharedFunctions'; @@ -10,7 +10,6 @@ import {SharedFunctions} from '../../shared/SharedFunctions'; TranslatePipe ], templateUrl: './imprint.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './imprint.component.scss', }) export class ImprintComponent { diff --git a/src/app/pages/not-found/not-found.component.ts b/src/app/pages/not-found/not-found.component.ts index bca38af..193475e 100644 --- a/src/app/pages/not-found/not-found.component.ts +++ b/src/app/pages/not-found/not-found.component.ts @@ -1,4 +1,4 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { Component } from '@angular/core'; import { RouterLink } from '@angular/router'; import { MatCard } from '@angular/material/card'; import { MatButton } from '@angular/material/button'; @@ -8,7 +8,6 @@ import { RouterConstants } from '../../constants/RouterConstants'; @Component({ selector: 'app-not-found', imports: [RouterLink, MatCard, MatButton, TranslatePipe], - changeDetection: ChangeDetectionStrategy.Eager, templateUrl: './not-found.component.html', }) export class NotFoundComponent { diff --git a/src/app/pages/projects/dialog/project-dialog.component.ts b/src/app/pages/projects/dialog/project-dialog.component.ts index c7c47c9..f79ca06 100644 --- a/src/app/pages/projects/dialog/project-dialog.component.ts +++ b/src/app/pages/projects/dialog/project-dialog.component.ts @@ -1,4 +1,4 @@ -import {AfterViewInit, Component, inject, CUSTOM_ELEMENTS_SCHEMA, ViewChild, ElementRef, ChangeDetectionStrategy} from '@angular/core'; +import {AfterViewInit, Component, inject, CUSTOM_ELEMENTS_SCHEMA, ViewChild, ElementRef} from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogActions, @@ -30,7 +30,6 @@ register(); MatButton, MatDialogClose ], - changeDetection: ChangeDetectionStrategy.Eager, schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class ProjectDialogComponent implements AfterViewInit { diff --git a/src/app/pages/projects/projects.component.ts b/src/app/pages/projects/projects.component.ts index 17bc785..0a81a6e 100644 --- a/src/app/pages/projects/projects.component.ts +++ b/src/app/pages/projects/projects.component.ts @@ -1,4 +1,4 @@ -import { Component, computed, inject, CUSTOM_ELEMENTS_SCHEMA, OnDestroy, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { Component, computed, inject, CUSTOM_ELEMENTS_SCHEMA, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { Subscription } from "rxjs"; import { MatCardModule } from "@angular/material/card"; @@ -43,7 +43,6 @@ export interface Projects { ], templateUrl: './projects.component.html', styleUrl: './projects.component.scss', - changeDetection: ChangeDetectionStrategy.Eager, schemas: [CUSTOM_ELEMENTS_SCHEMA], }) export class ProjectsComponent implements OnInit, OnDestroy { diff --git a/src/app/pages/stopwatch/stopwatch.component.ts b/src/app/pages/stopwatch/stopwatch.component.ts index 7b1fd46..e3d3a38 100644 --- a/src/app/pages/stopwatch/stopwatch.component.ts +++ b/src/app/pages/stopwatch/stopwatch.component.ts @@ -1,4 +1,4 @@ -import {Component, computed, effect, inject, OnDestroy, signal, WritableSignal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, computed, effect, inject, OnDestroy, signal, WritableSignal} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatButtonModule} from '@angular/material/button'; import {MatCardModule} from '@angular/material/card'; @@ -30,7 +30,6 @@ const SECOND_INTERVAL_FREQUENCY_HZ = 587; MatCheckboxModule, TranslatePipe ], - changeDetection: ChangeDetectionStrategy.Eager, templateUrl: './stopwatch.component.html' }) export class StopwatchComponent implements OnDestroy { diff --git a/src/app/shared/components/generic-grid/generic-grid.ts b/src/app/shared/components/generic-grid/generic-grid.ts index 2aedb47..77c3c86 100644 --- a/src/app/shared/components/generic-grid/generic-grid.ts +++ b/src/app/shared/components/generic-grid/generic-grid.ts @@ -1,4 +1,4 @@ -import {AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, ViewChild} from '@angular/core'; import {CommonModule} from '@angular/common'; export interface GridPos { row: number; col: number } @@ -7,7 +7,6 @@ export interface GridPos { row: number; col: number } selector: 'app-generic-grid', imports: [CommonModule], templateUrl: './generic-grid.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './generic-grid.scss', }) export class GenericGridComponent implements AfterViewInit { diff --git a/src/app/shared/components/particles-background/particles-background.component.ts b/src/app/shared/components/particles-background/particles-background.component.ts index 957c85c..8a76159 100644 --- a/src/app/shared/components/particles-background/particles-background.component.ts +++ b/src/app/shared/components/particles-background/particles-background.component.ts @@ -1,11 +1,10 @@ -import {AfterViewInit, Component, ElementRef, HostListener, inject, NgZone, OnDestroy, PLATFORM_ID, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {AfterViewInit, Component, ElementRef, HostListener, inject, NgZone, OnDestroy, PLATFORM_ID, ViewChild} from '@angular/core'; import {isPlatformBrowser} from '@angular/common'; @Component({ selector: 'app-particles-background', imports: [], templateUrl: './particles-background.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './particles-background.component.scss', }) export class ParticleBackgroundComponent implements AfterViewInit, OnDestroy { diff --git a/src/app/shared/components/render-canvas/babylon-canvas.component.ts b/src/app/shared/components/render-canvas/babylon-canvas.component.ts index aaf8cab..362b330 100644 --- a/src/app/shared/components/render-canvas/babylon-canvas.component.ts +++ b/src/app/shared/components/render-canvas/babylon-canvas.component.ts @@ -1,4 +1,4 @@ -import {AfterViewInit, Component, ElementRef, EventEmitter, inject, Input, NgZone, OnDestroy, Output, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {AfterViewInit, Component, ElementRef, EventEmitter, inject, input, NgZone, OnDestroy, Output, ViewChild} from '@angular/core'; import {MatSnackBar} from '@angular/material/snack-bar'; import {TranslateService} from '@ngx-translate/core'; import {ArcRotateCamera, Camera, Engine, MeshBuilder, Scene, ShaderLanguage, ShaderMaterial, Vector2, Vector3, WebGPUEngine} from '@babylonjs/core'; @@ -26,7 +26,6 @@ export interface SceneEventData { selector: 'app-babylon-canvas', imports: [], templateUrl: './babylon-canvas.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './babylon-canvas.component.scss', }) export class BabylonCanvas implements AfterViewInit, OnDestroy { @@ -37,8 +36,8 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { @ViewChild('renderCanvas', { static: true }) canvasRef!: ElementRef; - @Input({ required: true }) config!: RenderConfig; - @Input() renderCallback?: RenderCallback; + readonly config = input.required(); + readonly renderCallback = input(); @Output() sceneReady = new EventEmitter(); @Output() sceneResized = new EventEmitter(); @@ -131,7 +130,7 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { } private setupCamera(canvas: HTMLCanvasElement) { - if (this.config.mode === '3D') { + if (this.config().mode === '3D') { this.setup3dCamera(canvas); return; } @@ -144,7 +143,7 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { cam.mode = Camera.ORTHOGRAPHIC_CAMERA; const aspect = canvas.width / canvas.height; - const viewSize = this.config?.initialViewSize ?? 10; + const viewSize = this.config().initialViewSize ?? 10; cam.orthoLeft = -viewSize * aspect / 2; cam.orthoRight = viewSize * aspect / 2; cam.orthoTop = viewSize / 2; @@ -161,17 +160,18 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { cam.maxZ = 100; cam.lowerRadiusLimit = 1.5; cam.upperRadiusLimit = 20; - cam.radius = this.config?.initialViewSize ?? 1; + cam.radius = this.config().initialViewSize ?? 1; cam.attachControl(canvas, true); this.camera = cam; } private createFullScreenRect() { - if (!this.config.vertexShader || !this.config.fragmentShader) { + const config = this.config(); + if (!config.vertexShader || !config.fragmentShader) { return; } const plane = MeshBuilder.CreatePlane("plane", {size: 100}, this.scene); - if (this.config.mode === '3D') { + if (config.mode === '3D') { plane.parent = this.camera; plane.position.z = 1; } else { @@ -183,7 +183,8 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { } private createShaderMaterial() { - if (!this.config.vertexShader || !this.config.fragmentShader || !this.config.uniformNames) { + const config = this.config(); + if (!config.vertexShader || !config.fragmentShader || !config.uniformNames) { return; } @@ -191,14 +192,14 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { "shaderMaterial", this.scene, { - vertexSource: this.config.vertexShader, - fragmentSource: this.config.fragmentShader + vertexSource: config.vertexShader, + fragmentSource: config.fragmentShader }, { attributes: ["position", "uv"], - uniforms: ["resolution", "cameraPosition", ...this.config.uniformNames], - uniformBuffers: this.config.uniformBufferNames ?? [], - shaderLanguage: this.config.shaderLanguage ?? ShaderLanguage.GLSL + uniforms: ["resolution", "cameraPosition", ...config.uniformNames], + uniformBuffers: config.uniformBufferNames ?? [], + shaderLanguage: config.shaderLanguage ?? ShaderLanguage.GLSL } ); this.shaderMaterial.disableDepthWrite = true; @@ -209,8 +210,9 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { this.engine.runRenderLoop(() => { // callback call to call specific uniforms - if (this.renderCallback) { - this.renderCallback(this.shaderMaterial, this.camera, canvas, this.scene); + const callback = this.renderCallback(); + if (callback) { + callback(this.shaderMaterial, this.camera, canvas, this.scene); } // default uniforms which maybe each scene has @@ -228,8 +230,9 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { this.engine.resize(); } - if (this.config.mode === '2D' && this.camera instanceof ArcRotateCamera) { - const viewSize = this.config?.initialViewSize ?? 10; + const config = this.config(); + if (config.mode === '2D' && this.camera instanceof ArcRotateCamera) { + const viewSize = config.initialViewSize ?? 10; this.camera.orthoLeft = -viewSize / 2; this.camera.orthoRight = viewSize / 2; this.camera.orthoTop = viewSize / 2;