Preguntas de entrevista de Angular con respuestas
En Angular moderno se pregunta por signals, inyección de dependencias, plantillas con control de flujo, el router y RxJS. Estas preguntas son de nivel 2, las que tienen trampa, y cada una trae su explicación.
Piensa la respuesta antes de abrirla: la explicación dice por qué fallan las demás opciones. En Streakode tienes 26 preguntas como estas de Angular, y un simulacro de 10 preguntas en 15 minutos, sin corrección hasta el final, para ver cómo irías en una entrevista de verdad.
12 preguntas de Angular
Temas: Control de flujo · Enlace bidireccional · Inyección de dependencias · Plantillas · Router · RxJS · Signals.
1 · Completa el hueco · nivel 2
Completa para numerar los usuarios y mostrar un aviso si no hay ninguno.
@for (user of users(); track user.id; let i = ___) { <li>{{ i + 1 }}. {{ user.name }}</li>} ___ { <li>No hay usuarios</li>}- $index
- @empty
- index
- $count
- @else
- @none
Ver la respuesta
Respuesta: $index · @empty
@for (user of users(); track user.id; let i = $index) { <li>{{ i + 1 }}. {{ user.name }}</li>} @empty { <li>No hay usuarios</li>}@for ofrece variables implícitas como $index, $count, $first y $last, y se les puede dar un alias con let. El bloque para una colección vacía es @empty.
2 · Elige la respuesta · nivel 2
¿Qué le falta a este bucle para compilar?
@for (item of items) { <li>{{ item.name }}</li>}- Atrack item.id, o la expresión que identifique cada elemento
- BUn *ngFor en el <li>
- CDeclarar let item
- DNada, compila tal cual
Ver la respuesta
Respuesta: A. track item.id, o la expresión que identifique cada elemento
En @for, track es obligatorio: le dice a Angular cómo identificar cada elemento para reutilizar el DOM en vez de recrearlo.
3 · Completa el hueco · nivel 2
Completa un slider cuyo valor se sincroniza en los dos sentidos con el padre.
// slider.tsexport class Slider { value = ___(0);} <!-- plantilla del padre --><app-slider ___="volume" />- model
- [(value)]
- input
- (value)
- [value]
- signal
Ver la respuesta
Respuesta: model · [(value)]
// slider.tsexport class Slider { value = model(0);} <!-- plantilla del padre --><app-slider [(value)]="volume" />model() declara una entrada que el propio componente puede modificar. El padre la enlaza con la «caja de plátanos» [( )], y así recibe los cambios en su signal volume.
4 · Elige la respuesta · nivel 2
¿Qué pasa cuando se llama a load() al pulsar un botón?
export class Profile { load() { const http = inject(HttpClient); return http.get('/api/me'); }}- ALanza un error: inject() solo funciona en un contexto de inyección
- BFunciona igual que en el constructor
- CDevuelve undefined
- DCrea un HttpClient nuevo en cada llamada
Ver la respuesta
Respuesta: A. Lanza un error: inject() solo funciona en un contexto de inyección
inject() solo se puede llamar mientras Angular construye algo: en el constructor, en los inicializadores de campos o en funciones como los guards. Desde un clic falla con NG0203. Hay que inyectarlo en un campo.
5 · Completa el hueco · nivel 2
Completa: inyecta HttpClient sin constructor y guarda el usuario en un signal.
export class Profile { private http = ___(HttpClient); user = ___<User | null>(null);}- inject
- signal
- computed
- new
- effect
- input
Ver la respuesta
Respuesta: inject · signal
export class Profile { private http = inject(HttpClient); user = signal<User | null>(null);}inject() obtiene una dependencia en un contexto de inyección, como los inicializadores de campos. signal() crea un estado reactivo con valor inicial.
6 · Elige la respuesta · nivel 2
¿Qué hace @let en esta plantilla?
@let total = price() * qty();<p>{{ total }} €</p>@if (total > 100) { <p>Envío gratis</p>}- ADeclara una variable local de solo lectura en la plantilla
- BCrea un signal editable
- CAñade una propiedad nueva a la clase del componente
- DNada: @let no existe en Angular
Ver la respuesta
Respuesta: A. Declara una variable local de solo lectura en la plantilla
@let guarda el resultado de una expresión para reutilizarlo en la plantilla sin repetir el cálculo. Se actualiza solo cuando cambian sus signals, pero no se puede reasignar.
7 · Elige la respuesta · nivel 2
Con provideRouter(routes, withComponentInputBinding()), ¿cómo recibe el componente el :id de la ruta products/:id?
- ACon una entrada: id = input<string>()
- BSolo con inject(ActivatedRoute).snapshot.params
- CComo parámetro del constructor
- DCon el decorador @Param('id')
Ver la respuesta
Respuesta: A. Con una entrada: id = input<string>()
withComponentInputBinding pasa los parámetros de ruta, los query params y los datos de la ruta a las entradas del componente que tengan el mismo nombre. ActivatedRoute sigue funcionando, pero ya no hace falta.
8 · Elige la respuesta · nivel 2
¿Qué ocurre si el usuario no ha iniciado sesión?
export const authGuard: CanActivateFn = () => { const auth = inject(Auth); const router = inject(Router); return auth.isLoggedIn() || router.createUrlTree(['/login']);};- ARedirige a /login
- BBloquea la navegación y se queda en la página actual
- CLanza un error porque un guard debe devolver boolean
- DNavega a la ruta y después redirige
Ver la respuesta
Respuesta: A. Redirige a /login
Un guard puede devolver true, false o un UrlTree. Si devuelve un UrlTree, el router cancela la navegación y va a esa URL. Es mejor que llamar a router.navigate dentro del guard.
9 · Elige la respuesta · nivel 2
¿Qué ocurre al ejecutar esta línea?
const numbers$ = of(1, 2, 3).pipe( tap((n) => console.log(n)));- ANo se imprime nada: el Observable no empieza hasta que alguien se suscribe
- BSe imprimen 1, 2 y 3
- CSe imprime solo 3, el último valor
- DLanza un error por no tener subscribe
Ver la respuesta
Respuesta: A. No se imprime nada: el Observable no empieza hasta que alguien se suscribe
Un Observable es perezoso: pipe solo describe lo que pasará con los valores. Hasta que algo llame a subscribe() (o a un async pipe), tap no se ejecuta ni se emite nada.
10 · Encuentra el fallo · nivel 2
Al salir de la página, el contador sigue sumando en segundo plano. ¿Qué línea tiene el fallo?
1export class Ticker {2 count = 0;3 4 constructor() {5 interval(1000).subscribe(() => this.count++);6 }7}Ver la respuesta
El fallo está en la línea 5:
interval(1000).subscribe(() => this.count++);La suscripción nunca se cancela y sobrevive al componente. Dentro del constructor basta con interval(1000).pipe(takeUntilDestroyed()).subscribe(...). Otra opción es convertirlo en signal con toSignal.
11 · Encuentra el fallo · nivel 2
El componente entra en un bucle infinito. ¿Qué línea lo provoca?
1export class Counter {2 count = signal(0);3 4 constructor() {5 effect(() => {6 this.count.set(this.count() + 1);7 });8 }9}Ver la respuesta
El fallo está en la línea 6:
this.count.set(this.count() + 1);El effect lee count y a la vez lo modifica, así que cada ejecución programa la siguiente. Para derivar un valor de otro, usa computed. Si de verdad hay que leer sin suscribirse, está untracked().
12 · Elige la respuesta · nivel 2
¿Qué imprime este código?
const price = signal(10);const double = computed(() => price() * 2);price.set(100);console.log(double());- A200
- B20
- C10
- DError: computed es de solo lectura
Ver la respuesta
Respuesta: A. 200
computed guarda qué signals lee y se recalcula cuando alguno cambia. Tras price.set(100), double() vale 200.