frontend: Refactors selection logic in lists

Ensures the selected items in lists are correctly updated
when the underlying data changes by using `untracked` to avoid circular dependencies.

Introduces a utility function to compare sets for equality,
preventing unnecessary updates and improving performance.
This commit is contained in:
Carlos Santos 2026-01-15 16:56:19 +01:00
parent eabb559a82
commit 68a10ff901
4 changed files with 44 additions and 20 deletions

View File

@ -1,14 +1,5 @@
import { CommonModule, DatePipe } from '@angular/common';
import {
Component,
computed,
effect,
EventEmitter,
input,
OnInit,
Output,
signal
} from '@angular/core';
import { Component, computed, effect, EventEmitter, input, OnInit, Output, signal, untracked } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { MatBadgeModule } from '@angular/material/badge';
import { MatButtonModule } from '@angular/material/button';
@ -26,6 +17,7 @@ import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MeetRecordingInfo, MeetRecordingStatus } from '@openvidu-meet/typings';
import { ViewportService } from 'openvidu-components-angular';
import { setsAreEqual } from '../../../../shared/utils/array.utils';
import { formatBytes, formatDurationToHMS } from '../../../../shared/utils/format.utils';
import { RecordingTableAction, RecordingTableFilter } from '../../models/recording-list.model';
@ -155,10 +147,17 @@ export class RecordingListsComponent implements OnInit {
effect(() => {
// Update selected recordings based on current recordings
const recordings = this.recordings();
const validIds = new Set(recordings.map((r) => r.recordingId));
const filteredSelection = new Set([...this.selectedRecordings()].filter((id) => validIds.has(id)));
this.selectedRecordings.set(filteredSelection);
this.updateSelectionState();
const validRecordingIds = new Set(recordings.map((r) => r.recordingId));
// Use untracked to avoid circular dependency in effect
const currentSelection = untracked(() => this.selectedRecordings());
const filteredSelection = new Set([...currentSelection].filter((id) => validRecordingIds.has(id)));
// Only update if the selection has actually changed
if (!setsAreEqual(filteredSelection, currentSelection)) {
this.selectedRecordings.set(filteredSelection);
this.updateSelectionState();
}
// Show message when no recordings match filters
this.showEmptyFilterMessage = recordings.length === 0 && this.hasActiveFilters();
@ -170,7 +169,7 @@ export class RecordingListsComponent implements OnInit {
this.updateDisplayedColumns();
// Calculate showEmptyFilterMessage based on initial state
this.showEmptyFilterMessage = this.recordings.length === 0 && this.hasActiveFilters();
this.showEmptyFilterMessage = this.recordings().length === 0 && this.hasActiveFilters();
}
// ===== INITIALIZATION METHODS =====

View File

@ -1,5 +1,5 @@
import { CommonModule, DatePipe } from '@angular/common';
import { Component, effect, EventEmitter, HostBinding, input, OnInit, Output, signal } from '@angular/core';
import { Component, effect, EventEmitter, HostBinding, input, OnInit, Output, signal, untracked } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { MatBadgeModule } from '@angular/material/badge';
import { MatButtonModule } from '@angular/material/button';
@ -16,6 +16,7 @@ import { MatTableModule } from '@angular/material/table';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MeetingEndAction, MeetRoom, MeetRoomStatus } from '@openvidu-meet/typings';
import { setsAreEqual } from '../../../../shared/utils/array.utils';
export interface RoomTableAction {
rooms: MeetRoom[];
@ -144,10 +145,18 @@ export class RoomsListsComponent implements OnInit {
effect(() => {
// Update selected rooms based on current rooms
const rooms = this.rooms();
const validIds = new Set(rooms.map((r) => r.roomId));
const filteredSelection = new Set([...this.selectedRooms()].filter((id) => validIds.has(id)));
this.selectedRooms.set(filteredSelection);
this.updateSelectionState();
const validRoomIds = new Set(rooms.map((r) => r.roomId));
// Use untracked to avoid creating a reactive dependency on selectedRooms
const currentSelection = untracked(() => this.selectedRooms());
const filteredSelection = new Set([...currentSelection].filter((id) => validRoomIds.has(id)));
// Only update if the selection has actually changed
if (!setsAreEqual(filteredSelection, currentSelection)) {
this.selectedRooms.set(filteredSelection);
this.updateSelectionState();
}
// Show message when no rooms match filters
this.showEmptyFilterMessage = rooms.length === 0 && this.hasActiveFilters();

View File

@ -0,0 +1,14 @@
export const setsAreEqual = <T>(setA: Set<T>, setB: Set<T>): boolean => {
if (setA.size !== setB.size) return false;
for (const item of setA) {
if (!setB.has(item)) return false;
}
return true;
};
export const arraysAreEqual = <T>(arrayA: T[], arrayB: T[]): boolean => {
if (arrayA.length !== arrayB.length) return false;
for (let i = 0; i < arrayA.length; i++) {
if (arrayA[i] !== arrayB[i]) return false;
}
return true;
};

View File

@ -1,2 +1,4 @@
export * from './array.utils';
export * from './format.utils';
export * from './token.utils';