29/10/2017
Si alguna vez has trabajado con Angular Material, es casi seguro que te has topado con su potente componente de tablas, la mat-table. Es una herramienta fantástica para mostrar datos de forma ordenada y eficiente. Sin embargo, muchos desarrolladores, tanto novatos como experimentados, se encuentran con un muro aparentemente inexplicable: modifican el array de datos, añaden un elemento, lo eliminan... y la tabla permanece estática, inmutable, como si no se hubiera enterado del cambio. O peor aún, empiezan a aparecer comportamientos extraños, como filas vacías inesperadas, especialmente en escenarios complejos como tablas anidadas. ¿Te suena familiar? No estás solo. Este comportamiento no es un error, sino una decisión de diseño deliberada por parte del equipo de Angular para optimizar el rendimiento. En este artículo, desmitificaremos este proceso y te daremos la llave maestra para controlar la renderización de tus tablas a voluntad.

¿Por qué Mat-Table no se actualiza sola? El secreto del rendimiento
La primera pregunta que surge es: si Angular es famoso por su "data binding" bidireccional y su mágica detección de cambios, ¿por qué la mat-table parece ignorarlo? La respuesta corta es: rendimiento. Imagina que tienes una tabla con miles de registros. Si Angular tuviera que comprobar profundamente cada cambio dentro del array de datos (un `push`, un `splice`, la modificación de una propiedad en un objeto) en cada ciclo de detección de cambios, la aplicación se volvería increíblemente lenta. Para evitar este cuello de botella, los desarrolladores de Angular Material tomaron una decisión inteligente: la tabla solo reacciona a un cambio en la referencia del objeto `dataSource`. Es decir, no vigila el *contenido* del array, sino el array *en sí mismo*.
Esto significa que si tienes un array `_dataSource_` y haces `this.dataSource.push(nuevoElemento)`, la referencia al array sigue siendo la misma, por lo que la tabla no detecta ningún cambio y no se vuelve a renderizar. Este es el núcleo del problema que confunde a tantos programadores. La tabla necesita una notificación explícita de que sus datos han sido alterados y que necesita volver a dibujar sus filas.
La Solución Definitiva: `renderRows()` al Rescate
Afortunadamente, Angular Material nos proporciona un método específico para solucionar esto: `renderRows()`. Esta función, como su nombre indica, le ordena a la tabla que vuelva a renderizar las filas basándose en el estado actual del `dataSource`. Pero, ¿cómo llamamos a este método que pertenece al componente de la tabla desde nuestro código TypeScript? La respuesta está en una de las herramientas más útiles de Angular: `@ViewChild`.
El proceso consta de tres sencillos pasos:
Paso 1: Crear una referencia en la plantilla HTML
Primero, necesitamos darle un "nombre" a nuestra tabla en el archivo HTML para poder identificarla. Esto se hace usando una variable de referencia de plantilla, que no es más que un `#` seguido del nombre que elijamos.
<table #miTabla mat-table [dataSource]="listData"> <!-- El resto de las columnas y definiciones de la tabla --> </table>Con `#miTabla`, hemos etiquetado nuestro componente `mat-table` para poder acceder a él desde el archivo TypeScript.

Paso 2: Capturar la referencia en TypeScript con `@ViewChild`
Ahora, en nuestro archivo `.ts` del componente, usamos el decorador `@ViewChild` para obtener una instancia del componente de la tabla. Le decimos a `@ViewChild` que busque la referencia que nombramos `#miTabla`.
import { Component, ViewChild } from '@angular/core'; import { MatTable } from '@angular/material/table'; @Component({ // ... }) export class TuComponente { @ViewChild('miTabla') miTabla!: MatTable<any>; // O de forma más robusta, haciendo referencia al tipo de componente: // @ViewChild(MatTable) miTabla!: MatTable<any>; listData = [...]; // Tu fuente de datos // ... }Una nota importante sobre `miTabla!: MatTable
Paso 3: Llamar al método `renderRows()` después de cada modificación
Con la referencia a la tabla ya en nuestro poder a través de `this.miTabla`, ahora podemos llamar a su método `renderRows()` cada vez que modifiquemos los datos. Veamos cómo se aplicaría en operaciones CRUD (Crear, Leer, Actualizar, Borrar) básicas.
// Función para añadir un nuevo elemento public anadirElemento(nuevoElemento: any): void { this.listData.push(nuevoElemento); // ¡El paso clave! Notificamos a la tabla que debe redibujarse. this.miTabla.renderRows(); } // Función para eliminar un elemento por su ID public eliminarElemento(id: number): void { const index = this.listData.findIndex(el => el.id === id); if (index > -1) { this.listData.splice(index, 1); // Notificamos de nuevo a la tabla. this.miTabla.renderRows(); } } // Función para actualizar un elemento public actualizarElemento(elementoActualizado: any): void { const index = this.listData.findIndex(el => el.id === elementoActualizado.id); if (index > -1) { this.listData[index] = elementoActualizado; // Y una vez más, notificamos. this.miTabla.renderRows(); } }Tabla Comparativa: El Antes y el Después
Para que quede aún más claro, veamos una comparación directa entre el enfoque incorrecto y el correcto.
| Acción | Método Incorrecto (La UI no se actualiza) | Método Correcto (La UI se actualiza) |
|---|---|---|
| Añadir Fila | this.dataSource.push(newItem); | this.dataSource.push(newItem); |
| Eliminar Fila | this.dataSource.splice(index, 1); | this.dataSource.splice(index, 1); |
| Modificar Fila | this.dataSource[index].name = 'Nuevo Nombre'; | this.dataSource[index].name = 'Nuevo Nombre'; |
Caso Avanzado: Tablas Anidadas y las Filas Fantasma
Volvamos al problema original que motivó a muchos a buscar esta solución: una tabla anidada que muestra una fila vacía extra por cada fila del padre. Este es un síntoma clásico de problemas de renderización y gestión de datos. El problema suele ocurrir porque la fila de detalle expandida (`expandedDetail`) se renderiza, pero el `dataSource` de la tabla interna aún no está listo o está mal definido en ese ciclo de renderización. Al expandir una fila, Angular crea el espacio para la tabla interna, pero si los datos no se sincronizan correctamente, el resultado es una tabla vacía que ocupa espacio (la "fila fantasma").
La solución de `renderRows()` es fundamental aquí también. Al gestionar la expansión de una fila, debes asegurarte de que el `dataSource` de la tabla hija (por ejemplo, `element.taxVersion` en el código de ejemplo) esté correctamente poblado. Si esos datos se cargan de forma asíncrona, deberás llamar a `renderRows()` en la tabla hija una vez que los datos hayan llegado, para asegurar que se pinte correctamente y no muestre un estado vacío o intermedio.

Preguntas Frecuentes (FAQ)
¿Es `renderRows()` la única forma de actualizar la tabla?
No, existe otra técnica muy común. En lugar de mutar el array existente, puedes crear una nueva referencia de array. Angular sí detectará este cambio. Por ejemplo:
this.listData.push(nuevoElemento);this.listData = [...this.listData];
Al usar el operador de propagación (`...`), estás creando un array completamente nuevo que contiene todos los elementos del anterior más el nuevo. Como la referencia de `this.listData` ha cambiado, la tabla se actualizará. Ambas formas son válidas. `renderRows()` puede ser marginalmente más performante ya que no crea un nuevo array, mientras que reasignar el `dataSource` es a veces considerado un enfoque más "inmutable" y alineado con otros patrones de Angular.
¿Por qué necesito `@ViewChild`? ¿No puedo usar `document.getElementById`?
Usar `document.getElementById` y manipular el DOM directamente es considerado un anti-patrón en Angular. El framework está diseñado para abstraer la manipulación directa del DOM. `@ViewChild` es la forma "Angular" de obtener referencias a elementos y componentes de la plantilla, permitiendo una interacción segura, tipada y dentro del ecosistema y ciclo de vida de Angular.
¿Funciona esto en versiones antiguas de Angular Material?
Sí, el concepto de `renderRows()` y la necesidad de notificar a la tabla de los cambios ha estado presente durante mucho tiempo. Es el método estándar y recomendado para gestionar tablas dinámicas, especialmente a partir de Angular Material 10 y 11, donde la documentación lo enfatiza claramente.
Mi tabla sigue sin actualizarse después de llamar a `renderRows()`. ¿Qué más puedo hacer?
Si esto ocurre, revisa lo siguiente:
- Asegúrate de que la referencia de `@ViewChild` es correcta y no está indefinida cuando la llamas.
- Verifica que estás modificando el mismo array que está enlazado al `[dataSource]` de la tabla.
- Si realizas cambios fuera de la zona de Angular (por ejemplo, en un callback de una librería de terceros), puede que necesites inyectar `ChangeDetectorRef` y llamar a `this.cdr.detectChanges()` después de `renderRows()` para forzar un ciclo de detección de cambios.
Conclusión
La mat-table de Angular Material es una pieza de ingeniería impresionante, optimizada para el rendimiento. Esta optimización viene con una responsabilidad para el desarrollador: la de comunicar explícitamente cuándo los datos han cambiado. Lejos de ser un defecto, es una característica que nos da un control preciso sobre el comportamiento de nuestras aplicaciones. Al dominar el dúo de `@ViewChild` y `renderRows()`, no solo solucionarás los frustrantes problemas de actualización, sino que también obtendrás una comprensión más profunda de cómo funciona Angular bajo el capó. Así que la próxima vez que tu tabla se niegue a cooperar, ya sabes exactamente qué hacer para ponerla en su sitio.
Si quieres conocer otros artículos parecidos a Mat-Table: Domina la Renderización de Filas puedes visitar la categoría Juegos.
