ViewChild Queries: @ViewChild, @ViewChildren
Las template queries nos permiten acceder desde el código TypeScript a elementos del DOM o componentes hijos definidos en nuestro template. En lecciones anteriores aprendiste a usar template reference variables con #ref
para referenciar elementos desde el template. Ahora veremos cómo acceder a esos mismos elementos desde el código del componente usando decoradores especializados.
Los decoradores @ViewChild
y @ViewChildren
nos proporcionan una forma elegante de obtener referencias a elementos de la vista del componente, ya sean elementos HTML nativos, otros componentes o directivas.
Acceso a elementos con @ViewChild
El decorador @ViewChild
nos permite obtener una referencia única a un elemento del template. Puede buscar elementos por diferentes criterios:
Búsqueda por template reference variable:
import { Component, ViewChild, ElementRef } from '@angular/core';
@Component({
template: `
<input #nombreInput type="text" placeholder="Escribe tu nombre">
<button (click)="enfocarInput()">Enfocar input</button>
<p>Valor actual: {{ obtenerValor() }}</p>
`
})
export class MiComponente {
@ViewChild('nombreInput') inputElement!: ElementRef<HTMLInputElement>;
enfocarInput() {
this.inputElement.nativeElement.focus();
}
obtenerValor() {
return this.inputElement?.nativeElement.value || '';
}
}
Búsqueda por tipo de componente:
import { Component, ViewChild } from '@angular/core';
// Componente hijo
@Component({
selector: 'hijo-componente',
template: '<p>Soy el componente hijo</p>'
})
export class HijoComponente {
mensaje = 'Hola desde el hijo';
saludar() {
console.log('¡Saludo desde el componente hijo!');
}
}
// Componente padre
@Component({
template: `
<hijo-componente></hijo-componente>
<button (click)="interactuarConHijo()">Llamar método del hijo</button>
`,
imports: [HijoComponente]
})
export class PadreComponente {
@ViewChild(HijoComponente) componenteHijo!: HijoComponente;
interactuarConHijo() {
console.log(this.componenteHijo.mensaje);
this.componenteHijo.saludar();
}
}
Múltiples elementos con @ViewChildren
Cuando necesitamos acceder a múltiples elementos que coinciden con nuestro criterio de búsqueda, utilizamos @ViewChildren
. Este decorador devuelve un QueryList
que contiene todas las referencias encontradas.
import { Component, ViewChildren, QueryList, ElementRef } from '@angular/core';
@Component({
template: `
<div>
<input #item type="text" value="Elemento 1">
<input #item type="text" value="Elemento 2">
<input #item type="text" value="Elemento 3">
</div>
<button (click)="procesarTodosLosInputs()">Procesar todos</button>
<button (click)="limpiarTodos()">Limpiar todos</button>
`
})
export class ListaComponente {
@ViewChildren('item') inputs!: QueryList<ElementRef<HTMLInputElement>>;
procesarTodosLosInputs() {
this.inputs.forEach((input, index) => {
console.log(`Input ${index + 1}: ${input.nativeElement.value}`);
});
}
limpiarTodos() {
this.inputs.forEach(input => {
input.nativeElement.value = '';
});
}
}
Configuración de ViewChild
Los decoradores ViewChild admiten varias opciones de configuración que controlan cuándo y cómo se resuelven las consultas:
Consultas estáticas vs dinámicas:
@Component({
template: `
@if (mostrarInput) {
<input #dinamicoInput type="text">
}
<input #estaticoInput type="text">
`
})
export class ConfiguracionComponente {
mostrarInput = false;
// static: true - Se resuelve en ngOnInit (solo para elementos siempre presentes)
@ViewChild('estaticoInput', { static: true })
inputEstatico!: ElementRef;
// static: false - Se resuelve en ngAfterViewInit (por defecto)
@ViewChild('dinamicoInput', { static: false })
inputDinamico!: ElementRef;
ngOnInit() {
// inputEstatico ya está disponible
console.log('Input estático:', this.inputEstatico.nativeElement);
}
ngAfterViewInit() {
// inputDinamico estará disponible si mostrarInput es true
if (this.inputDinamico) {
console.log('Input dinámico:', this.inputDinamico.nativeElement);
}
}
}
Trabajando con QueryList
El QueryList
que devuelve @ViewChildren
es una estructura especial que proporciona métodos útiles para trabajar con colecciones de elementos:
@Component({
template: `
@for (item of elementos; track item.id) {
<div #tarjeta class="tarjeta">{{ item.nombre }}</div>
}
<button (click)="analizarTarjetas()">Analizar tarjetas</button>
`
})
export class TarjetasComponente {
elementos = [
{ id: 1, nombre: 'Tarjeta 1' },
{ id: 2, nombre: 'Tarjeta 2' },
{ id: 3, nombre: 'Tarjeta 3' }
];
@ViewChildren('tarjeta') tarjetas!: QueryList<ElementRef>;
analizarTarjetas() {
console.log('Total de tarjetas:', this.tarjetas.length);
console.log('Primera tarjeta:', this.tarjetas.first?.nativeElement.textContent);
console.log('Última tarjeta:', this.tarjetas.last?.nativeElement.textContent);
// Convertir a array para usar métodos de array
const arrayTarjetas = this.tarjetas.toArray();
arrayTarjetas.forEach((tarjeta, index) => {
tarjeta.nativeElement.style.backgroundColor = `hsl(${index * 60}, 70%, 80%)`;
});
}
ngAfterViewInit() {
// Detectar cambios en la lista
this.tarjetas.changes.subscribe(() => {
console.log('Las tarjetas han cambiado');
});
}
}
Cuándo usar ViewChild vs ViewChildren
La elección entre @ViewChild
y @ViewChildren
depende de cuántos elementos esperas encontrar:
-
@ViewChild: Utilízalo cuando necesites acceder a un elemento específico. Si hay múltiples elementos que coinciden, obtendrás el primero.
-
@ViewChildren: Utilízalo cuando necesites acceder a todos los elementos que coinciden con el criterio de búsqueda.
Timing y ciclo de vida
Es importante entender cuándo están disponibles las referencias de ViewChild:
@Component({
template: '<input #miInput type="text">'
})
export class TimingComponente {
@ViewChild('miInput') input!: ElementRef;
ngOnInit() {
// ❌ input todavía no está disponible aquí
console.log(this.input); // undefined
}
ngAfterViewInit() {
// ✅ input ya está disponible aquí
console.log(this.input); // ElementRef
this.input.nativeElement.focus();
}
}
Las template queries tradicionales que acabas de aprender son muy útiles para casos de uso específicos, pero Angular también ofrece Signal Queries como alternativa moderna que veremos más adelante en el módulo de Signals. Por ahora, dominar @ViewChild
y @ViewChildren
te dará las herramientas necesarias para interactuar con elementos de tu vista desde el código del componente.
ContentChild Queries: @ContentChild, @ContentChildren
Mientras que @ViewChild
y @ViewChildren
nos permiten acceder a elementos definidos en el template del propio componente, los decoradores @ContentChild
y @ContentChildren
nos dan acceso a elementos proyectados desde el componente padre mediante ng-content
.
Estos decoradores son especialmente útiles cuando creamos componentes reutilizables que actúan como contenedores y necesitamos interactuar con el contenido que se proyecta en ellos.
Diferencia entre View y Content
Para entender mejor la diferencia, veamos un ejemplo visual:
// Componente contenedor
@Component({
selector: 'mi-tarjeta',
template: `
<div class="tarjeta">
<header class="cabecera">
<!-- Este input pertenece a la VIEW del componente -->
<input #viewInput type="text" placeholder="Título">
</header>
<main class="contenido">
<!-- Este ng-content proyecta CONTENT desde el padre -->
<ng-content></ng-content>
</main>
</div>
`
})
export class TarjetaComponente {
// Accede a elementos del template del componente
@ViewChild('viewInput') inputDelTemplate!: ElementRef;
// Accede a elementos proyectados desde el padre
@ContentChild('contentInput') inputProyectado!: ElementRef;
}
// Uso del componente
@Component({
template: `
<mi-tarjeta>
<!-- Este input será proyectado como CONTENT -->
<input #contentInput type="text" placeholder="Contenido proyectado">
<p>Este párrafo también es contenido proyectado</p>
</mi-tarjeta>
`,
imports: [TarjetaComponente]
})
export class AppComponent {
}
Acceso a contenido proyectado con @ContentChild
El decorador @ContentChild
nos permite obtener una referencia única a un elemento proyectado a través de ng-content
:
@Component({
selector: 'panel-expandible',
template: `
<div class="panel">
<button (click)="alternarContenido()" class="boton-expandir">
{{ expandido ? 'Contraer' : 'Expandir' }}
</button>
@if (expandido) {
<div class="contenido-panel">
<ng-content></ng-content>
</div>
}
</div>
`,
styles: [`
.contenido-panel {
border: 1px solid #ccc;
padding: 16px;
margin-top: 8px;
}
`]
})
export class PanelExpandibleComponente {
expandido = false;
// Accede al botón proyectado desde el padre
@ContentChild('botonAccion') botonAccion!: ElementRef<HTMLButtonElement>;
alternarContenido() {
this.expandido = !this.expandido;
// Interactuar con el contenido proyectado
if (this.expandido && this.botonAccion) {
this.botonAccion.nativeElement.style.backgroundColor = '#4CAF50';
}
}
ngAfterContentInit() {
// Las content queries están disponibles aquí
if (this.botonAccion) {
console.log('Botón proyectado encontrado:', this.botonAccion.nativeElement);
}
}
}
Uso del componente:
@Component({
template: `
<panel-expandible>
<h3>Contenido del panel</h3>
<p>Este contenido se proyecta dentro del panel.</p>
<button #botonAccion (click)="accionPersonalizada()">
Acción personalizada
</button>
</panel-expandible>
`,
imports: [PanelExpandibleComponente]
})
export class ContenedorComponent {
accionPersonalizada() {
console.log('Acción ejecutada desde el contenido proyectado');
}
}
Múltiples elementos proyectados con @ContentChildren
Cuando el contenido proyectado incluye múltiples elementos que necesitamos manejar, utilizamos @ContentChildren
:
@Component({
selector: 'lista-pestañas',
template: `
<div class="pestanas">
@for (pestaña of pestañas; track $index) {
<button
(click)="seleccionarPestaña($index)"
[class.activa]="pestañaActiva === $index">
Pestaña {{ $index + 1 }}
</button>
}
</div>
<div class="contenido-pestañas">
<ng-content></ng-content>
</div>
`,
styles: [`
.pestanas button.activa {
background-color: #2196F3;
color: white;
}
`]
})
export class ListaPestañasComponente {
pestañaActiva = 0;
pestañas: any[] = [];
// Accede a todos los elementos de pestaña proyectados
@ContentChildren('pestaña') elementosPestañas!: QueryList<ElementRef>;
seleccionarPestaña(indice: number) {
this.pestañaActiva = indice;
this.actualizarVisibilidadPestañas();
}
actualizarVisibilidadPestañas() {
this.elementosPestañas.forEach((elemento, indice) => {
const divPestaña = elemento.nativeElement;
divPestaña.style.display = indice === this.pestañaActiva ? 'block' : 'none';
});
}
ngAfterContentInit() {
// Inicializar pestañas basándose en el contenido proyectado
this.pestañas = this.elementosPestañas.toArray();
this.actualizarVisibilidadPestañas();
// Detectar cambios en el contenido proyectado
this.elementosPestañas.changes.subscribe(() => {
this.pestañas = this.elementosPestañas.toArray();
this.actualizarVisibilidadPestañas();
});
}
}
Uso con múltiples pestañas:
@Component({
template: `
<lista-pestañas>
<div #pestaña>
<h3>Primera pestaña</h3>
<p>Contenido de la primera pestaña.</p>
</div>
<div #pestaña>
<h3>Segunda pestaña</h3>
<p>Contenido de la segunda pestaña.</p>
<ul>
<li>Elemento 1</li>
<li>Elemento 2</li>
</ul>
</div>
<div #pestaña>
<h3>Tercera pestaña</h3>
<p>Contenido de la tercera pestaña.</p>
</div>
</lista-pestañas>
`,
imports: [ListaPestañasComponente]
})
export class PaginaPestañasComponent {
}
Búsqueda por tipo de componente
Al igual que con ViewChild, también podemos buscar componentes proyectados por su tipo:
// Componente que será proyectado
@Component({
selector: 'elemento-especial',
template: `
<div class="elemento-especial">
<strong>{{ titulo }}</strong>
<p>{{ contenido }}</p>
</div>
`
})
export class ElementoEspecialComponente {
titulo = 'Elemento especial';
contenido = 'Este es un componente proyectado';
destacar() {
console.log('Elemento destacado');
}
}
// Componente contenedor
@Component({
selector: 'galeria-elementos',
template: `
<div class="galeria">
<button (click)="destacarElementos()">Destacar elementos especiales</button>
<ng-content></ng-content>
</div>
`
})
export class GaleriaElementosComponente {
// Buscar un componente específico proyectado
@ContentChild(ElementoEspecialComponente)
elementoEspecial!: ElementoEspecialComponente;
// Buscar todos los componentes de este tipo
@ContentChildren(ElementoEspecialComponente)
todosElementosEspeciales!: QueryList<ElementoEspecialComponente>;
destacarElementos() {
this.todosElementosEspeciales.forEach(elemento => {
elemento.destacar();
});
}
ngAfterContentInit() {
console.log('Elementos especiales encontrados:', this.todosElementosEspeciales.length);
}
}
Ciclo de vida de ContentChild
Las content queries se resuelven en un momento diferente del ciclo de vida que las view queries:
@Component({
selector: 'contenedor-con-ciclo',
template: `
<div class="contenedor">
<ng-content></ng-content>
</div>
`
})
export class ContenedorConCicloComponente {
@ContentChild('contenidoProyectado') contenido!: ElementRef;
ngOnInit() {
// ❌ contenido todavía no está disponible
console.log('OnInit - Contenido:', this.contenido); // undefined
}
ngAfterContentInit() {
// ✅ contenido ya está disponible aquí
console.log('AfterContentInit - Contenido:', this.contenido);
}
ngAfterViewInit() {
// ✅ contenido también está disponible aquí
console.log('AfterViewInit - Contenido:', this.contenido);
}
}
Cuándo usar ContentChild
Los decoradores @ContentChild
y @ContentChildren
son especialmente útiles para:
- Componentes de layout que necesitan interactuar con su contenido proyectado
- Componentes wrapper que añaden funcionalidad al contenido proyectado
- Componentes contenedor como modales, pestañas o acordeones
- Validación o procesamiento del contenido proyectado
Estas template queries tradicionales te permiten crear componentes muy flexibles que pueden interactuar tanto con su propia vista como con el contenido que reciben del exterior. En módulos posteriores veremos las Signal Queries como alternativa moderna, pero dominar @ContentChild
y @ContentChildren
es fundamental para trabajar con proyección de contenido en Angular.

Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Angular es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Angular
Explora más contenido relacionado con Angular y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Comprender el uso de @ViewChild y @ViewChildren para acceder a elementos y componentes en la vista del propio componente.
- Aprender a configurar consultas estáticas y dinámicas con ViewChild.
- Entender cómo trabajar con QueryList para manejar múltiples elementos.
- Diferenciar entre ViewChild/ViewChildren y ContentChild/ContentChildren para acceder a contenido proyectado.
- Conocer el ciclo de vida y el momento adecuado para acceder a las referencias obtenidas con estas queries.