frontend: Refactors rooms lists component to use Angular signals

Migrates the rooms lists component to leverage Angular's signal-based inputs.
This improves change detection and simplifies data flow within the component.

Updates the component's template to reflect the use of signal accessors.
Ensures initial filters are correctly applied.
This commit is contained in:
Carlos Santos 2026-01-15 14:50:48 +01:00
parent 520816b983
commit e70dc6619f
6 changed files with 61 additions and 76 deletions

View File

@ -1,4 +1,4 @@
@if (!loading && rooms.length === 0 && !showEmptyFilterMessage) {
@if (!loading() && rooms().length === 0 && !showEmptyFilterMessage) {
<!-- Empty State -->
<div class="no-rooms-state">
<div class="empty-content">
@ -18,7 +18,7 @@
<mat-toolbar class="rooms-toolbar" id="rooms-toolbar">
<!-- Left Section: Search -->
<div class="toolbar-left" id="toolbar-left">
@if (showSearchBox) {
@if (showSearchBox()) {
<mat-form-field class="search-field" appearance="outline" id="search-field">
<mat-label>Search rooms</mat-label>
<input
@ -33,7 +33,7 @@
matSuffix
class="search-btn"
(click)="triggerSearch()"
[disabled]="loading || !nameFilterControl.value"
[disabled]="loading() || !nameFilterControl.value"
matTooltip="Search"
id="search-btn"
>
@ -44,14 +44,14 @@
</div>
<!-- Center Section: Batch Actions (visible when items selected) -->
@if (showSelection && selectedRooms().size > 0) {
@if (showSelection() && selectedRooms().size > 0) {
<div class="toolbar-center" id="toolbar-center">
<div class="batch-actions" id="batch-actions">
<button
mat-icon-button
color="warn"
(click)="bulkDeleteSelected()"
[disabled]="loading"
[disabled]="loading()"
matTooltip="Delete selected rooms"
id="bulk-delete-btn"
>
@ -68,7 +68,7 @@
mat-icon-button
class="clear-btn"
(click)="clearFilters()"
[disabled]="loading"
[disabled]="loading()"
matTooltip="Clear all filters"
id="clear-filters-btn"
>
@ -80,7 +80,7 @@
mat-icon-button
class="refresh-btn"
(click)="refreshRooms()"
[disabled]="loading"
[disabled]="loading()"
matTooltip="Refresh rooms"
id="refresh-btn"
>
@ -92,7 +92,7 @@
Create Room
</button>
@if (showFilters) {
@if (showFilters()) {
<button mat-icon-button [matMenuTriggerFor]="filtersMenu" matTooltip="Filter rooms" id="filters-btn">
<mat-icon>tune</mat-icon>
</button>
@ -117,12 +117,12 @@
</mat-toolbar>
<!-- Loading Spinner -->
@if (loading) {
@if (loading()) {
<div class="loading-container">
<mat-spinner diameter="40"></mat-spinner>
<span>Loading rooms...</span>
</div>
} @else if (rooms.length === 0 && showEmptyFilterMessage) {
} @else if (rooms().length === 0 && showEmptyFilterMessage) {
<!-- No rooms match the current filters -->
<div class="no-rooms-state">
<div class="empty-content">
@ -141,7 +141,7 @@
<div class="table-container" id="table-container">
<table
mat-table
[dataSource]="rooms"
[dataSource]="rooms()"
matSort
matSortDisableClear
[matSortActive]="currentSortField"
@ -151,14 +151,14 @@
id="rooms-table"
>
<!-- Selection Column -->
@if (showSelection) {
@if (showSelection()) {
<ng-container matColumnDef="select">
<th mat-header-cell *matHeaderCellDef id="select-header">
<mat-checkbox
[checked]="allSelected()"
[indeterminate]="someSelected()"
(change)="toggleAllSelection()"
[disabled]="loading"
[disabled]="loading()"
id="select-all-checkbox"
>
</mat-checkbox>
@ -168,7 +168,7 @@
<mat-checkbox
[checked]="isRoomSelected(room)"
(change)="toggleRoomSelection(room)"
[disabled]="loading"
[disabled]="loading()"
id="select-room-{{ room.roomId }}"
>
</mat-checkbox>
@ -309,7 +309,7 @@
mat-icon-button
matTooltip="Open Room"
(click)="openRoom(room)"
[disabled]="loading"
[disabled]="loading()"
class="primary-action"
id="open-room-btn-{{ room.roomId }}"
>
@ -323,7 +323,7 @@
mat-icon-button
matTooltip="Room Settings"
(click)="editRoom(room)"
[disabled]="loading"
[disabled]="loading()"
id="edit-room-btn-{{ room.roomId }}"
>
<mat-icon class="ov-settings-icon">settings</mat-icon>
@ -335,7 +335,7 @@
mat-icon-button
[matMenuTriggerFor]="actionsMenu"
matTooltip="More Actions"
[disabled]="loading"
[disabled]="loading()"
id="more-actions-btn-{{ room.roomId }}"
>
<mat-icon>more_vert</mat-icon>
@ -406,13 +406,13 @@
</div>
<!-- Load More Section -->
@if (showLoadMore) {
@if (showLoadMore()) {
<div class="load-more-container" id="load-more-container">
<button
mat-button
class="load-more-btn"
(click)="loadMoreRooms()"
[disabled]="loading"
[disabled]="loading()"
id="load-more-btn"
>
<mat-icon>expand_more</mat-icon>

View File

@ -1,15 +1,5 @@
import { CommonModule, DatePipe } from '@angular/common';
import {
Component,
EventEmitter,
HostBinding,
Input,
OnChanges,
OnInit,
Output,
signal,
SimpleChanges
} from '@angular/core';
import { Component, effect, EventEmitter, HostBinding, input, OnInit, Output, signal } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { MatBadgeModule } from '@angular/material/badge';
import { MatButtonModule } from '@angular/material/button';
@ -98,20 +88,19 @@ export interface RoomTableFilter {
templateUrl: './rooms-lists.component.html',
styleUrl: './rooms-lists.component.scss'
})
export class RoomsListsComponent implements OnInit, OnChanges {
// Input properties
@Input() rooms: MeetRoom[] = [];
@Input() showSearchBox = true;
@Input() showFilters = true;
@Input() showSelection = true;
@Input() showLoadMore = false;
@Input() loading = false;
@Input() initialFilters: RoomTableFilter = {
export class RoomsListsComponent implements OnInit {
rooms = input<MeetRoom[]>([]);
showSearchBox = input(true);
showFilters = input(true);
showSelection = input(true);
showLoadMore = input(false);
loading = input(false);
initialFilters = input<RoomTableFilter>({
nameFilter: '',
statusFilter: '',
sortField: 'creationDate',
sortOrder: 'desc'
};
});
// Host binding for styling when rooms are selected
@HostBinding('class.has-selections')
@ -151,34 +140,33 @@ export class RoomsListsComponent implements OnInit, OnChanges {
{ value: MeetRoomStatus.CLOSED, label: 'Closed' }
];
constructor() {}
constructor() {
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();
// Show message when no rooms match filters
this.showEmptyFilterMessage = rooms.length === 0 && this.hasActiveFilters();
});
}
ngOnInit() {
this.setupFilters();
this.updateDisplayedColumns();
}
ngOnChanges(changes: SimpleChanges) {
if (changes['rooms']) {
// Update selected rooms based on current rooms
const validIds = new Set(this.rooms.map((r) => r.roomId));
const filteredSelection = new Set([...this.selectedRooms()].filter((id) => validIds.has(id)));
this.selectedRooms.set(filteredSelection);
this.updateSelectionState();
// Show message when no rooms match filters
this.showEmptyFilterMessage = this.rooms.length === 0 && this.hasActiveFilters();
}
}
// ===== INITIALIZATION METHODS =====
private setupFilters() {
// Set up initial filter values
this.nameFilterControl.setValue(this.initialFilters.nameFilter);
this.statusFilterControl.setValue(this.initialFilters.statusFilter);
this.currentSortField = this.initialFilters.sortField;
this.currentSortOrder = this.initialFilters.sortOrder;
// Initialize from initialFilters input
this.nameFilterControl.setValue(this.initialFilters().nameFilter);
this.statusFilterControl.setValue(this.initialFilters().statusFilter);
this.currentSortField = this.initialFilters().sortField;
this.currentSortOrder = this.initialFilters().sortOrder;
// Set up name filter change detection
this.nameFilterControl.valueChanges.subscribe((value) => {
@ -197,7 +185,7 @@ export class RoomsListsComponent implements OnInit, OnChanges {
private updateDisplayedColumns() {
this.displayedColumns = [];
if (this.showSelection) {
if (this.showSelection()) {
this.displayedColumns.push('select');
}
@ -211,7 +199,7 @@ export class RoomsListsComponent implements OnInit, OnChanges {
if (this.allSelected()) {
selected.clear();
} else {
this.rooms.forEach((room) => {
this.rooms().forEach((room) => {
if (this.canSelectRoom(room)) {
selected.add(room.roomId);
}
@ -233,7 +221,7 @@ export class RoomsListsComponent implements OnInit, OnChanges {
}
private updateSelectionState() {
const selectableRooms = this.rooms.filter((r) => this.canSelectRoom(r));
const selectableRooms = this.rooms().filter((r) => this.canSelectRoom(r));
const selectedCount = this.selectedRooms().size;
const selectableCount = selectableRooms.length;
@ -251,7 +239,7 @@ export class RoomsListsComponent implements OnInit, OnChanges {
getSelectedRooms(): MeetRoom[] {
const selected = this.selectedRooms();
return this.rooms.filter((r) => selected.has(r.roomId));
return this.rooms().filter((r) => selected.has(r.roomId));
}
// ===== ACTION METHODS =====

View File

@ -48,7 +48,7 @@
[showRoomInfo]="false"
[showLoadMore]="hasMoreRecordings"
[roomName]="roomName"
[initialFilters]="initialFilters"
[initialFilters]="initialFilters()"
(recordingAction)="onRecordingAction($event)"
(loadMore)="loadMoreRecordings($event)"
(refresh)="refreshRecordings($event)"

View File

@ -9,11 +9,8 @@ import { ILogger, LoggerService } from 'openvidu-components-angular';
import { NavigationService } from '../../../../shared/services/navigation.service';
import { NotificationService } from '../../../../shared/services/notification.service';
import { MeetingContextService } from '../../../meeting/services';
import {
RecordingListsComponent,
RecordingTableAction,
RecordingTableFilter
} from '../../../recordings/components/recording-lists/recording-lists.component';
import { RecordingListsComponent } from '../../../recordings/components/recording-lists/recording-lists.component';
import { RecordingTableAction, RecordingTableFilter } from '../../../recordings/models/recording-list.model';
import { RecordingService } from '../../../recordings/services/recording.service';
import { RoomMemberService } from '../../services/room-member.service';
@ -34,12 +31,12 @@ export class RoomRecordingsComponent implements OnInit {
showInitialLoader = false;
isLoading = false;
initialFilters: RecordingTableFilter = {
initialFilters = signal<RecordingTableFilter>({
nameFilter: '',
statusFilter: '',
sortField: 'startDate',
sortOrder: 'desc'
};
});
// Pagination
hasMoreRecordings = false;
@ -68,7 +65,7 @@ export class RoomRecordingsComponent implements OnInit {
this.showInitialLoader = true;
}, 200);
await this.loadRecordings(this.initialFilters);
await this.loadRecordings(this.initialFilters());
clearTimeout(delayLoader);
this.showInitialLoader = false;

View File

@ -40,7 +40,7 @@
[showFilters]="true"
[showSelection]="true"
[showLoadMore]="hasMoreRooms"
[initialFilters]="initialFilters"
[initialFilters]="initialFilters()"
(roomAction)="onRoomAction($event)"
(loadMore)="loadMoreRooms($event)"
(refresh)="refreshRooms($event)"

View File

@ -67,12 +67,12 @@ export class RoomsComponent implements OnInit {
showInitialLoader = false;
isLoading = false;
initialFilters: RoomTableFilter = {
initialFilters = signal<RoomTableFilter>({
nameFilter: '',
statusFilter: '',
sortField: 'creationDate',
sortOrder: 'desc'
};
});
// Pagination
hasMoreRooms = false;
@ -96,7 +96,7 @@ export class RoomsComponent implements OnInit {
this.showInitialLoader = true;
}, 200);
await this.loadRooms(this.initialFilters);
await this.loadRooms(this.initialFilters());
clearTimeout(delayLoader);
this.showInitialLoader = false;