<link rel="stylesheet" href="/assets/fonts/jetbrains-mono/jetbrains-mono.css" />
All posts

25 preguntas de entrevista para frontend / Angular developer: De junior a lead (con respuestas)

Introducción

Una entrevista para un puesto de Frontend o Angular Developer nunca es una simple prueba de sintaxis. Quien realiza la entrevista quiere entender cómo piensa usted ante un problema, cuánto Conoces profundamente las herramientas que utilizas todos los días y cómo cambian tus elecciones cuando lo haces. El escenario va de "un componente" a "una aplicación empresarial utilizada por cientos de miles de usuarios".

Esta guía recopila 25 preguntas reales, organizadas en cuatro niveles de antigüedad: Junior, Nivel medio, Senior y Experto/Líder: con respuestas completas que puedes usar tanto para prepararte para una Entreviste a ambos, si está al otro lado de la mesa, para realizar uno usted mismo de una manera estructurado.

Las preguntas del primer nivel prueban los fundamentos (HTML, CSS, JavaScript, TypeScript, angular básico); los de los niveles intermedios entran en RxJS, componentes, servicios, enrutamiento y gestión estatal; los Senior tocan la detección de cambios, renderizado, rendimiento avanzado, patrones de prueba y diseño; los expertos/líderes abordan arquitecturas de aplicaciones grandes, microfrontend, SSR, seguridad y las compensaciones técnicas que un líder debe poder argumentar, no solo sabiendo.

Temas: #Angular #Frontend #Entrevistadetrabajo #TypeScript #JavaScript #RxJS #DesarrolloWeb #Asesoramiento Profesional #ArquitecturaDeSoftware #EntrevistaTécnica

Cómo está estructurada esta guía

Cada pregunta está diseñada para reflejar lo que realmente se pregunta en las entrevistas técnicas. no preguntas de "libro de texto" aisladas del contexto. Las respuestas no se limitan a la definición. formal: explican por qué la respuesta es que, cuando la regla tiene excepciones, y qué Un entrevistador experimentado espera oír para distinguir una respuesta de libro de texto de una una respuesta de alguien que realmente resolvió el problema en producción.

  • Nivel junior (6 preguntas): fundamentos de HTML, CSS, JavaScript, TypeScript y Angular.
  • Nivel medio (6 preguntas): RxJS, componentes, servicios, enrutamiento, gestión de estado, rendimiento básico.
  • Nivel superior (7 preguntas): arquitectura, detección de cambios, renderizado, rendimiento avanzado, pruebas, patrón de diseño.
  • Nivel experto/líder (6 preguntas): arquitecturas de aplicaciones grandes, escalabilidad, microfrontends, SSR, seguridad, compensaciones técnicas.

Nivel Junior: HTML, CSS, JavaScript, TypeScript y fundamentos de Angular

En este nivel, el objetivo del entrevistador no es encontrar el error, sino comprender si se ha cometido un error. bases sólidas sobre las que construir. Las mejores respuestas son aquellas que, además de la definición, Explican el impacto práctico del conocimiento.

1. ¿Cuál es la diferencia entre elementos HTML a nivel de bloque y en línea y por qué es importante saberlo?

Los elementos nivel de bloque (como <div>, <section>, <p>) siempre ocupan todo el ancho disponible del padre y comenzar en una nueva línea, aceptando width, alto y márgenes/relleno en todos los lados. Los elementos en línea (como <span>, <a>, <strong>) ocupan sólo el espacio necesario para el contenido, están ordenados en línea con el texto circundante y ignorar ancho/alto, además de manejar los márgenes de una manera especial verticales.

Saberlo es importante porque explica errores de diseño muy comunes: a <span> al que configuraste width y no pasa nada, o un elemento que "no se alinea" como esperas. Con display: flex, grid y inline-block Esta distinción clásica es confusa, pero comprender el comportamiento predeterminado sigue siendo fundamental. para predecir cómo se comportará un elemento incluso antes de aplicar CSS.

2. ¿Cuáles son el modelo de cuadro CSS y las diferencias entre cuadro de contenido y cuadro de borde?

El modelo de caja describe cómo el navegador calcula las dimensiones finales de un elemento: content (el contenido), padding (espacio interno), border (borde) y margin (espacio exterior), concéntricos entre sí en el otro. La propiedad box-sizing determina cómo width y altura se interpretan:

/* content-box (default del browser): width/height si riferiscono SOLO al contenuto.
   padding e border si sommano, ingrandendo la dimensione finale renderizzata. */
.box-legacy {
  box-sizing: content-box;
  width: 200px;
  padding: 20px;
  border: 5px solid black;
  /* larghezza finale renderizzata: 200 + 20*2 + 5*2 = 250px */
}

/* border-box: width/height includono padding e border.
   La dimensione dichiarata è la dimensione finale renderizzata. */
.box-modern {
  box-sizing: border-box;
  width: 200px;
  padding: 20px;
  border: 5px solid black;
  /* larghezza finale renderizzata: 200px, esattamente come dichiarato */
}

En la práctica, casi todos los proyectos modernos establecen *, *::antes, *::después { box-sizing: border-box; } globalmente solo porque hace que los cálculos de diseño sean predecibles, evitando que agregar relleno "rompa" una cuadrícula ya dimensionado.

3. ¿Cuál es la diferencia entre var, let y const en JavaScript y qué problemas resuelve let?

var tiene alcance de función (o alcance global si se declara fuera funciones) y sufre de hoisting con inicialización a undefinido, que le permite "usarlo antes de declararlo" sin errores: un comportamiento con errores silencioso. let y const en su lugar tienen bloque de alcance (visibles sólo dentro de las llaves en las que están declarados) y viven en una zona muerta temporal: acceder a ella antes de que la declaración lance un ReferenceError explícito en lugar de regresar silenciosamente indefinido.

// Il classico bug da var dentro un loop asincrono
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log('var:', i), 10);
}
// Stampa: var: 3, var: 3, var: 3
// perché var è condivisa da tutte le iterazioni (una sola variabile, function-scoped)

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log('let:', i), 10);
}
// Stampa: let: 0, let: 1, let: 2
// perché let crea un nuovo binding per ogni iterazione del loop

const evita la reasignación vinculante (no hace que el objeto sea inmutable: una matriz u objeto declarado con const aún se puede mutar a su interno). La regla general que siguen casi todos los equipos: const por defecto, let solo cuando se necesita reasignación, var nunca en código nuevo.

4. ¿Qué son los cierres en JavaScript y para qué sirven en la práctica?

Se crea un closure cuando una función "recuerda" las variables de alcance en que fue definido, incluso después de que ese alcance haya terminado de ejecutarse. En la práctica, el La función interna mantiene una referencia viva a las variables de la función externa.

function createCounter() {
  let count = 0; // variabile "chiusa" nella closure

  return {
    increment: () => ++count,
    decrement: () => --count,
    getValue: () => count,
  };
}

const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getValue()); // 2

// count non è accessibile dall'esterno: è uno stato privato reso possibile dalla closure
console.log(counter.count); // undefined

Los cierres están en todas partes en el código Angular real: cada vez que pasas una función de devolución de llamada a un subscribe(), a un setTimeout, o define un computed()/effect() de una Señal, esa función "se cierra" en variables componentes. Comprender los cierres también explica un error frecuente: la captura por hacer referencia a una variable que cambia (por ejemplo, el índice de un bucle) en lugar del valor esperado en hora de la llamada.

5. ¿Qué son los genéricos en TypeScript y por qué son útiles?

Los generics le permiten escribir funciones, clases e interfaces que trabajan con diferentes tipos sin perder seguridad de tipo, sustituyendo la alternativa Lo peor: use any, que efectivamente desactiva la verificación de tipos.

// Senza generics: perdiamo informazione di tipo, il chiamante deve fare un cast manuale
function wrapInArrayUnsafe(value: any): any[] {
  return [value];
}
const result = wrapInArrayUnsafe('hello'); // result è any, nessun autocompletamento

// Con generics: il tipo si propaga automaticamente dall'input all'output
function wrapInArray(value: T): T[] {
  return [value];
}
const strings = wrapInArray('hello'); // TypeScript inferisce string[]
const numbers = wrapInArray(42);      // TypeScript inferisce number[]

// Un caso reale: un servizio HTTP generico
class ApiService {
  constructor(private endpoint: string) {}

  getAll(): Observable {
    return this.http.get(this.endpoint);
  }
}

const productsApi = new ApiService('/api/products');
// productsApi.getAll() restituisce Observable, tipizzato correttamente

En Angular los genéricos están en todas partes: Observable, señal, Componente en pruebas, i Repositorio Lado de NestJS. Saber utilizarlos de primera mano (y no sólo eso) reconocerlos) es lo que distingue a aquellos que escriben código mecanografiado robusto de aquellos que simplemente copiar patrones existentes.

6. ¿Qué es un componente angular y cuáles son sus elementos principales?

Un componente Angular es una clase TypeScript decorada con @Component que controla una parte de la interfaz de usuario. Sus principales elementos son:

  • Decorator @Component: metadatos que describen el selector, la plantilla y el estilo del componente.
  • Plantilla: HTML (en línea o en un archivo separado) con enlace, directivas e interpolación.
  • Clase: contiene estado (propiedades, señales) y comportamiento (métodos, enlaces de ciclo de vida).
  • Estilos: CSS con alcance encapsulado al componente predeterminado (Ver encapsulación).
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-greeting',
  standalone: true, // non richiede più NgModule dal 2023 in poi
  template: `
    

Ciao, {{ name() }}!

Cambia nome `, styles: [`h2 { color: var(--color-primary); }`], }) export class GreetingComponent { name = signal('Mondo'); changeName(): void { this.name.set('Angular'); } }

A partir de 2023 (Angular 14+ para componentes independientes, luego predeterminado desde Angular 17) un componente ya no necesita ser declarado en un NgModule: declara directamente el propias dependencias a través de imports en el decorador. Es el patrón que esperarías ser utilizado en cualquier proyecto nuevo hoy.

Nivel medio: RxJS, componentes, servicios, enrutamiento y gestión de estado

En este nivel el entrevistador comprueba si sabes conectar los conceptos: no es suficiente Para saber qué es un Observable, necesita saber qué operador RxJS usar en cada escenario, y porque una elección incorrecta causa errores reales (condiciones de carrera, pérdidas de memoria, solicitudes duplicados).

7. ¿Cuál es la diferencia entre switchMap, mergeMap, concatMap y exhaustMap en RxJS y cuándo usar cada uno?

Los cuatro son operadores "aplanadores" que gestionan un Observable de Observable, pero con estrategias de competencia opuestas:

OperadorComportamientoCaso de uso típico
switchMapEliminar solicitud anterior incompleta cuando llegue una nuevaCampo de búsqueda con autocompletar (solo última entrada) contar)
mergeMapEjecutar todas las solicitudes en paralelo, sin eliminar nadaMúltiples cargas de archivos independientes entre sí
concatMapPone en cola solicitudes, ejecuta la siguiente solo después de que se completa la anteriorOperaciones que deben ocurrir en orden estricto (por ejemplo, escritura secuencial en un iniciar sesión)
exhaustMapIgnorar nuevos eventos hasta que se complete el actualBotón Enviar: evite envíos con doble clic múltiplos
// L'errore più comune: usare mergeMap per una ricerca con autocomplete
searchInput.valueChanges.pipe(
  debounceTime(300),
  mergeMap(query => this.api.search(query)), // ❌ le risposte possono arrivare fuori ordine!
).subscribe(results => this.results.set(results));

// Se l'utente digita velocemente "an" poi "angular", e la richiesta per "an"
// impiega più tempo a rispondere di quella per "angular", l'utente vede
// i risultati sbagliati (quelli di "an") sovrascrivere quelli corretti.

// La scelta corretta: switchMap cancella la richiesta obsoleta
searchInput.valueChanges.pipe(
  debounceTime(300),
  switchMap(query => this.api.search(query)), // ✅ solo l'ultima richiesta conta
).subscribe(results => this.results.set(results));

Una respuesta sólida de nivel medio menciona explícitamente este error de clasificación: es la razón real, por lo que switchMap es casi siempre la opción correcta para la búsqueda, no una regla arbitraria que debe aprenderse de memoria.

8. ¿Cómo funciona la inyección de dependencia en Angular y cuál es la diferencia entre los proveedores provideIn: 'raíz' y de nivel de componente?

Angular mantiene una jerarquía de injector: un nivel raíz aplicación, y uno para cada componente (y sus hijos, si no se anulan). cuando un componente requiere una dependencia en el constructor (o con inject()), Angular busca un proveedor en la jerarquía desde el inyector de componentes hasta la raíz.

// providedIn: 'root' — un'unica istanza condivisa in tutta l'applicazione (singleton)
@Injectable({ providedIn: 'root' })
export class AuthService {
  private currentUser = signal(null);
}

// providers a livello di componente — una nuova istanza per ogni istanza del componente
@Component({
  selector: 'app-product-form',
  providers: [FormStateService], // ogni  ottiene la SUA istanza
  standalone: true,
})
export class ProductFormComponent {
  private formState = inject(FormStateService);
}

La elección tiene consecuencias reales: si pones un estado que debe ser compartido (p. ej. autenticación) en los proveedores de un componente en lugar de en providedIn: 'root', cada instancia de componente tendrá un estado aislado: un error clásico "¿por qué mi servicio no ve datos actualizados?" que casi siempre surge de esto Confusión entre alcances de inyector.

9. ¿Qué son los Route Guards y qué tipos existen en Angular moderno?

Los guard son funciones (en Angular moderno, funciones puras, ya no clases con interfaces) que deciden si una navegación puede continuar, debe ser redirigida o bloqueado. Los principales son:

  • CanActivateFn: decide si se puede activar una ruta (por ejemplo, verificación de autenticación).
  • CanActivateChildFn: como arriba, pero aplicado a rutas secundarias.
  • CanDeactivateFn: decide si se puede abandonar una ruta (por ejemplo, advertencia de "cambios no guardados").
  • CanMatchFn: decide si una ruta puede coincidir, lo que resulta útil para ocultar secciones enteras cargadas de forma diferida a usuarios sin permisos.
  • ResolveFn: técnicamente no es un guardia, pero precarga datos antes de que se active la ruta.
export const authGuard: CanActivateFn = (route, state) => {
  const auth = inject(AuthService);
  const router = inject(Router);

  if (auth.isAuthenticated()) return true;

  router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
  return false;
};

// registrazione nelle route
export const routes: Routes = [
  { path: 'dashboard', component: DashboardComponent, canActivate: [authGuard] },
];

10. ¿Cómo manejaría el estado compartido entre componentes no relacionados en una aplicación Angular de tamaño mediano?

Para componentes no relacionados (que no tienen una relación directa entre padres e hijos), la solución El estándar es un servicio compartido con alcance providedIn: 'root' que expone el estado a través de Signal (o, en bases de código más antiguas, a través de BehaviorSubject).

@Injectable({ providedIn: 'root' })
export class CartStateService {
  private readonly _items = signal([]);

  readonly items = this._items.asReadonly(); // esposto in sola lettura
  readonly total = computed(() =>
    this._items().reduce((sum, i) => sum + i.price * i.quantity, 0)
  );

  addItem(item: CartItem): void {
    this._items.update(items => [...items, item]);
  }
}

Para aplicaciones de tamaño mediano, este patrón es suficiente: es simple, seguro para escribir, Responsivo y no requiere dependencias externas. Una biblioteca como NgRx sólo se justifica cuando surgen necesidades concretas: depuración de viajes en el tiempo, acciones rastreables con DevTools, Lógica de estado compleja compartida entre docenas de funciones, no "porque sea el estándar" empresa". Introducirlo prematuramente añade texto estándar sin beneficios proporcionales.

11. ¿Qué son las señales en angular y en qué se diferencian de un observable RxJS?

Un Signal es un contenedor reactivo de un valor que notifica automáticamente quien lo lee cuando cambia, sin necesidad de suscribirse/darse de baja. En comparación con un observable RxJS, la diferencia clave es que una señal siempre tiene un valor actual sincrónico y disponible inmediatamente (léelo llamándolo como función: count()), mientras que un Observable representa uno transmisión de eventos a lo largo del tiempo que puede que aún no haya emitido nada.

import { signal, computed, effect } from '@angular/core';

const count = signal(0);
const doubled = computed(() => count() * 2); // si ricalcola automaticamente

effect(() => {
  console.log('Il valore doppio è ora:', doubled()); // si riesegue ad ogni cambiamento
});

count.set(5); // stampa: Il valore doppio è ora: 10
count.update(v => v + 1); // stampa: Il valore doppio è ora: 12

En la práctica: las señales son ideales para el estado sincrónico local de un componente (contadores, formularios estado, datos derivados), mientras que RxJS sigue siendo la herramienta adecuada para flujos asincrónicos complejos (antirrebote en una entrada, reintente en una llamada HTTP, combinando múltiples transmisiones). angulares modern los hace interoperar con toSignal() y toObservable(), por lo que la pregunta no es "cuál" sino "cuál para este caso específico".

12. ¿Cuál es la diferencia entre la tradicional @Input()/@Output() y la nueva input()/output() basada en señales?

Los decoradores tradicionales @Input()/@Output() declaran propiedades normales (o EventEmitter) que Angular completa/observa a través de su mecanismo interno; las nuevas funciones input()/output() (Angular 17.1+) devuelven una señal de solo lectura y un emisor escrito respectivamente, integrando de forma nativa con computed() y effect().

// Stile tradizionale
@Component({ selector: 'app-counter', standalone: true })
export class CounterComponentLegacy {
  @Input() initialValue = 0;
  @Output() valueChange = new EventEmitter();
}

// Stile moderno basato su Signals
@Component({ selector: 'app-counter', standalone: true })
export class CounterComponent {
  initialValue = input(0);                 // Signal, in sola lettura
  initialValueRequired = input.required(); // obbliga il chiamante a passarlo
  valueChange = output();           // tipizzato, senza dover importare EventEmitter

  // Con input() come Signal, puoi derivare stato reattivo direttamente:
  doubledInitial = computed(() => this.initialValue() * 2);
}

La ventaja práctica del nuevo input()/output() no es sólo sintáctico: al ser Signal, se integran automáticamente en el gráfico de reactividad Angular (especialmente útil en aplicaciones sin zona) y input.required() mueve un error que fue descubierto previamente en tiempo de ejecución en una verificación verificable en tiempo de compilación con lo control estricto de la plantilla.

Nivel superior: arquitectura, detección de cambios, renderizado, rendimiento y pruebas

En el nivel Senior, el entrevistador no busca la definición correcta: busca razonamientos. Las preguntas muchas veces no tienen una única respuesta correcta, pero evalúan si puedes argumentar compensación informada.

13. ¿Cómo funciona el mecanismo de detección de cambios de Angular y cuál es la diferencia entre la estrategia Default y OnPush?

Con la estrategia Default, cada vez que Angular ejecuta un ciclo de cambio detección (activada por eventos DOM, temporizadores, llamadas HTTP completadas, a través de Zone.js), cada El componente del árbol se verifica para ver si los enlaces de la plantilla son cambiado, independientemente de dónde ocurrió realmente el evento.

Con OnPush, Angular verifica un componente solo cuando: (1) un @Input()/cambios de entrada de señal por referencia (no por mutación interna del objeto), (2) un evento DOM ocurre dentro de él, (3) un Observable al que es conectado a través de | async genera un nuevo valor, o (4) está marcado explícitamente con markForCheck()/actualizando una señal que lee.

@Component({
  selector: 'app-product-card',
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: true,
  template: `

{{ product().name }} — {{ product().price | currency }}

`, }) export class ProductCardComponent { product = input.required(); } // ❌ Questo NON scatena il change detection su ProductCardComponent con OnPush, // perché muta l'oggetto esistente invece di sostituirlo (stesso riferimento): someProduct.price = 99; // ✅ Questo sì, perché crea un nuovo riferimento: this.products.update(list => list.map(p => p.id === someProduct.id ? { ...p, price: 99 } : p) );

Una respuesta de Senior debe mencionar explícitamente el concepto de igualdad para referencia: esta es la causa número uno del error "Actualicé los datos pero la interfaz de usuario no funciona" actualizar" al cambiar a OnPush sin adoptar patrones inmutables de manera consistente durante todo la aplicación.

14. ¿Qué es Angular sin zonas y cómo cambia el modelo de detección de cambios?

Históricamente, Angular utiliza Zone.js para "parchear monos" API asíncronas navegador (eventos, temporizador, promesa, recuperación) y saber automáticamente cuándo podría ser Es necesario un ciclo de detección de cambios. El modelo zoneless (estable desde Angular 18+) elimina esta dependencia por completo: Angular depende exclusivamente de Signal saber exactamente qué ha cambiado, sin tener que revisar todo el árbol "por si acaso" cada vez que sucede algo asincrónico en segundo plano.

// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideZonelessChangeDetection } from '@angular/core';

bootstrapApplication(AppComponent, {
  providers: [provideZonelessChangeDetection()],
});

Las ventajas prácticas: paquete más pequeño (Zone.js pesa alrededor de 30 KB), detección de cambios significativamente más específicos (solo los componentes que dependen de una señal modificada son actualizado) y una depuración más predecible porque cada actualización de la interfaz de usuario es rastreable hasta un Cambio de señal explícito, no a un evento asincrónico genérico capturado "a paraguas". La desventaja: bibliotecas de terceros anticuadas que esperan Zone.js (algunas versiones de componentes de materiales, bibliotecas de gráficos) pueden requerir una NgZone.run() manual para seguir trabajando correctamente en sin zona.

15. ¿Cómo diagnosticaría y resolvería un problema de rendimiento debido a una detección excesiva de cambios en producción?

El primer paso no es adivinar: es medir con Angular DevTools, pestaña Perfilador. Al registrar una interacción problemática (por ejemplo, escribir en un campo de búsqueda), el Profiler muestra un gráfico de barras donde cada barra es un componente controlado en ese ciclo — si un componente no relacionado con la interacción aparece repetidamente, es la señal faltante OnPush o que hay un patrón reactivo mal diseñado.

La ruta de resolución típica, en orden de impacto:

  1. Aplique ChangeDetectionStrategy.OnPush a los componentes "hoja" más caros para renderizar, asegurando que los datos fluyan de manera inmutable.
  2. Reemplace las mutaciones directas de matriz/objeto con patrones inmutables (spread, map/filter) o cambie a Signal, que hace que la igualdad por referencia sea automática y correcta.
  3. Agregue track correcto (la identificación única, no el índice) en los bloques @for para evitar que Angular destruya y vuelva a crear nodos DOM innecesariamente cuando se reordena una lista.
  4. Evaluar la adopción de zona sin zona para eliminar los ciclos de detección de cambios desencadenados por eventos asincrónicos no relacionados con la interfaz de usuario visible.

Una respuesta senior efectiva siempre menciona primero Angular DevTools Profiler paso: Optimizar por sensación sin datos de perfil reales es un error común incluso en el nivel Senior, y quien realiza la entrevista lo nota inmediatamente.

16. ¿Qué patrones de diseño aplica con más frecuencia en una arquitectura empresarial Angular?

PatrónAplicación en Angular
FacadeUn servicio que oculta la complejidad de múltiples servicios/tiendas subyacentes detrás de una API simple para componentes (por ejemplo, CartFacade que organiza CartService, PricingService, InventoryService).
RepositoryUn servicio dedicado al acceso a datos (HTTP, caché local) que aísla los componentes de los detalles de implementación de la fuente de datos.
StrategyInyección de implementaciones intercambiables a través de InjectionToken, útil para comportamientos que varían según el entorno o la configuración (por ejemplo, estrategias de pago diferente).
Componente inteligente/tontoSeparación entre componentes "contenedor" (administrar estado y efectos secundarios) y componentes "presentacionales" (recibir datos a través de entrada, emitir eventos a través de salida, sin dependencias directas de servicios).
AdapterAislar la forma de los datos externos (API de terceros) detrás de una asignación a los modelos internos de la aplicación, de modo que un cambio en la API externa afecte solo un punto de la código.

Una respuesta de Senior distingue claramente cuando cada patrón agrega valor y cuando en cambio se trata de ingeniería excesiva: introducir una fachada para un único servicio simple, por ejemplo Por ejemplo, agrega indirección sin beneficios reales.

17. ¿Cómo estructura las pruebas de una aplicación Angular compleja y qué compensaciones considera?

Sigo la pirámide de pruebas: muchas pruebas unitarias rápidas y aisladas (servicio, tubería, lógica pura extraída de componentes), un número moderado de pruebas de integración (componentes con TestBed, verificando la interacción real con los servicios), y algunas pruebas E2E Centrado en los flujos que son realmente críticos para el negocio (iniciar sesión, finalizar la compra), no en todos y cada uno de ellos. página.

// Unit test: logica pura, veloce, nessuna dipendenza da Angular
describe('calculateDiscount', () => {
  it('applica correttamente uno sconto percentuale', () => {
    expect(calculateDiscount(100, 20)).toBe(80);
  });
});

// Integration test: componente reale con TestBed, verifica il comportamento visibile
describe('ProductCardComponent', () => {
  it('emette addToCart quando si clicca il pulsante', () => {
    const fixture = TestBed.createComponent(ProductCardComponent);
    fixture.componentRef.setInput('product', mockProduct);
    fixture.detectChanges();

    let emitted: Product | undefined;
    fixture.componentInstance.addToCart.subscribe(p => (emitted = p));
    fixture.debugElement.query(By.css('[data-testid="add-btn"]')).nativeElement.click();

    expect(emitted).toEqual(mockProduct);
  });
});

La principal compensación que siempre hablo en las conversaciones: los E2E brindan la máxima confianza reales (prueban la aplicación como la usaría un usuario) pero son lentas y más frágiles; pruebas unitarias Son muy rápidos pero no detectan problemas de integración. La relación correcta no es fija, depende de la criticidad del dominio: un flujo de pago merece más cobertura E2E que otro Página de información estática.

18. ¿Qué es la sacudida de árboles y cómo le afectan las elecciones arquitectónicas?

tree-shaking es el proceso mediante el cual el paquete (esbuild en Angular 17+/20+) elimina el código que se exportó pero que nunca se importó del paquete final o utilizado, reduciendo el tamaño del JavaScript descargado por el navegador.

Las elecciones arquitectónicas influyen de forma concreta: los standalones componentes con importaciones explícitas hacen que las dependencias sean estáticas y analizable, mejorando la sacudida de los árboles en comparación con el antiguo NgModule con declaraciones declaraciones "generales" que a menudo importaban más de lo necesario. Incluso el barrel file (index.ts que reexporta un módulo completo) puede empeorar la sacudida de árboles si el paquete no puede determinar estáticamente qué exportaciones en realidad se utilizan, lo que lleva a que se incluya código inactivo en el paquete final.

// ❌ Import "largo": può impedire il tree-shaking se lodash non è in formato ESM puro
import _ from 'lodash';
const chunks = _.chunk(array, 3);

// ✅ Import mirato: il bundler include SOLO la funzione realmente usata
import chunk from 'lodash/chunk';
const chunks = chunk(array, 3);

19. ¿Cómo implementaría el almacenamiento en caché del lado del cliente para reducir las llamadas HTTP redundantes de forma escalable?

La solución más elegante en Angular moderno es un HttpInterceptor que intercepta solicitudes GET y devuelve una respuesta almacenada en caché cuando está disponible y no caducado, invalidando específicamente el caché cuando cambian los datos subyacentes.

@Injectable()
export class CacheInterceptor implements HttpInterceptor {
  private cache = new Map; expiry: number }>();

  intercept(req: HttpRequest, next: HttpHandler): Observable> {
    if (req.method !== 'GET') return next.handle(req);

    const cached = this.cache.get(req.urlWithParams);
    if (cached && cached.expiry > Date.now()) {
      return of(cached.response.clone());
    }

    return next.handle(req).pipe(
      tap(event => {
        if (event instanceof HttpResponse) {
          this.cache.set(req.urlWithParams, {
            response: event,
            expiry: Date.now() + 60_000, // 60 secondi
          });
        }
      }),
    );
  }
}

El punto que distingue una respuesta Senior: saber qué cagar y qué no . Los datos del catálogo rara vez cambian y se prestan bien al almacenamiento en caché; datos específicos del usuario autenticado requieren una clave de caché que incluya la identidad del usuario (o debe excluirse por completo); de lo contrario, corre el riesgo de mostrar los datos de un usuario a otro: un error de seguridad, no sólo de rendimiento.

Nivel experto/líder: arquitecturas empresariales, escalabilidad, microfrontend, SSR y seguridad

Las preguntas de expertos/líderes rara vez tienen una respuesta "correcta" única. Evalúan la capacidad de argumentar una compensación, defender una decisión con datos concretos y reconocer cuándo La solución "más sofisticada" en realidad no es la adecuada para el contexto.

20. ¿Cuándo tiene sentido adoptar una arquitectura de microfrontend frente a un monolito angular modular y cuáles son las verdaderas ventajas y desventajas?

Las microfronteras tienen sentido cuando hay un problema organizacional real, No solo técnico: múltiples equipos que necesitan implementarse de forma independiente, pilas o lanzamientos. Diferencia angular entre partes de la aplicación, o la necesidad de aislar completamente la ciclo de liberación de una sección crítica del resto.

AparienciaMonolito modularMicrofrontend
Complejidad operativaBaja: una compilación, una implementaciónAlta: orquestación, control de versiones, contratos de equipo
Implementaciones independientesNoSí, por equipo/sección
Duplicación de dependencias de tiempo de ejecuciónNingunoRiesgo concreto (varias copias de Angular/RxJS) si no se administra con la federación de módulos
Consistencia de UX entre seccionesNaturalRequiere gobernanza explícita (sistema de diseño compartido)
Incorporación de nuevos desarrolladoresRepositorio/arquitectura única y más simpleMás complejo, es necesario comprender los límites entre aplicaciones

Mi posición en una entrevista: un monolito modular bien estructurado con límites de funciones claro (carga diferida, con interfaces explícitas entre módulos) resuelve la mayoría de los problemas problemas que los equipos creen que necesitan resolver con microfrontends, con una fracción del Complejidad operativa. Las microfrontends son una herramienta organizacional para cuestiones de escala de equipos, no una "mejor arquitectura" en abstracto, y debe adoptarse sólo cuando el costo de coordinación entre equipos ya supera el coste de la complejidad técnica adicional.

21. ¿Cómo diseñaría la estrategia SSR/hidratación para una aplicación Angular empresarial con estrictos requisitos de SEO?

Comenzaría desde Angular Universal con provideClientHydration() para la hidratación completa, valorando hidratación incremental (withIncrementalHydration()) para secciones innecesarias con muchas páginas al primer renderizado (gráficos, editores de texto enriquecido, mapas), combinado con @defer para posponga su carga hasta que entren en la ventana gráfica.

// app.config.server.ts
import { provideServerRendering } from '@angular/platform-server';
import { provideClientHydration, withIncrementalHydration } from '@angular/platform-browser';

export const serverConfig = [
  provideServerRendering(),
  provideClientHydration(withIncrementalHydration()),
];
<!-- Il componente si idrata solo quando entra in viewport, non al bootstrap iniziale -->
@defer (on viewport) {
  <app-heavy-dashboard-chart [data]="chartData()" />
} @placeholder {
  <div class="chart-skeleton"></div>
}

Para los estrictos requisitos de SEO, la parte arquitectónica crítica no es solo la renderización: necesita un SeoService centralizado que actualice dinámicamente el título, meta descripción, canónico y JSON-LD durante renderizado del lado del servidor (no después, cuando es demasiado tarde para los rastreadores que no ejecutan JavaScript), además de una estrategia explícita para administrar el código que accede a las API solo del navegador (window, document) detrás de un control isPlatformBrowser(), para evitar fallos durante el renderizado lado del servidor.

22. ¿Qué medidas de seguridad son esenciales en una aplicación empresarial Angular?

  • confiable: nunca por entrada de usuario no válida.
  • CSRF (falsificación de solicitud entre sitios): HttpClientXsrfModule maneja automáticamente el patrón de cookie de doble envío, pero solo si el backend configura correctamente la cookie XSRF-TOKEN.
  • Política de seguridad de contenido (CSP): Encabezado configurado del lado del servidor que limita las fuentes desde las cuales se pueden cargar scripts/estilos, mitigando el impacto incluso de un XSS exitoso.
  • Gestión de tokens JWT: token de acceso de corta duración, actualización de token administrada en el lado del servidor (nunca legible por JavaScript si es posible, a través de una cookie httpOnly) e invalidación real al cerrar sesión.
  • Validación del lado del cliente como UX, no seguridad: cualquier validación del lado del cliente siempre debe replicarse en el lado del servidor; un cliente puede manipularse con herramientas de desarrollo.

Una respuesta de experto/líder debe señalar explícitamente que la seguridad del lado del cliente es una defensa en profundidad, no la única línea de defensa: ¿quién piensa que eso valida/ proteger solo el lado angular es suficiente tiene una brecha conceptual grave para esto nivel.

23. ¿Cómo gestionaría el control de versiones y la migración incremental de una base de código Angular heredada a señales/independientes en un equipo de más de 20 desarrolladores?

No con una "reescritura a lo grande", demasiado arriesgada en un equipo de ese tamaño. la estrategia que adopto es la migración incremental por límites de características: Angular admite la coexistencia de NgModule y componentes independientes en el mismo aplicación, para que pueda migrar un módulo a la vez, verificando que cada paso sea Implementable de forma independiente en producción.

  1. Automatice el primer paso con el schematic ng generate @angular/core:standalone, que convierte automáticamente componentes/directivas/tuberías.
  2. Establezca un límite claro: las nuevas funciones se escriben solo de forma independiente de inmediato, para evitar que la deuda técnica siga creciendo durante la migración.
  3. Migre los módulos heredados en orden de "radio de impacto" creciente: primero las funciones aisladas y de poco tráfico y luego progresivamente el núcleo de la aplicación.
  4. Introducir las Señales en paralelo pero por separado de la migración a standalone: son dos ejes ortogonales, no es necesario hacerlos juntos y mezclarlos aumenta el riesgo por cada cambio.

El punto que busca un entrevistador principal: la capacidad de secuenciar el riesgo, no sólo de Obtenga más información sobre las herramientas de migración. Comunique claramente al equipo por qué está migrando. gradualmente (y no todo a la vez) es una parte tan importante del trabajo como escribir el código.

24. ¿Qué criterios utiliza para decidir entre NgRx, un simple almacén de señales personalizado o ninguna biblioteca de administración estatal?

CriterionSin biblioteca (Señal/servicios)Tienda de señales personalizada de luzNgRx
Complejidad del dominioBaja/mediaMediaAlta, con muchas acciones interconectadas
Necesidad de depuración de viajes en el tiempoNoNoSí
Equipo acostumbrado a los patrones de ReduxNo requeridoNo requeridoVentaja si ya está presente
Repetición aceptableMínimoModeradoSignificativo, mitigado por esquemas oficiales
Comprobabilidad en estado aisladoBuenoExcelenteExcelente, con patrones establecidos

Mi criterio de decisión en una entrevista: siempre parto de la opción más sencilla (Señal en un servicio providedIn: 'root') y lo desarrollo solo cuando surgen síntomas concretos - lógica de estado duplicada entre funciones, necesidad real de depuración avanzada o un equipo que ya funciona bien con patrones Redux en otros proyectos. Introducir NgRx "por defecto" en cada Un nuevo proyecto es una elección que tiende a ralentizar el desarrollo inicial sin beneficios. proporcionada hasta que la complejidad real lo justifique.

25. ¿Cómo equilibraría la velocidad de desarrollo, el rendimiento y la mantenibilidad en una decisión arquitectónica con plazos estrictos?

Un ejemplo concreto de una compensación real: un equipo necesita entregar un panel con gráficos interactivo en dos semanas. La biblioteca de gráficos con mejor rendimiento requiere tres días de integración y configuración avanzadas; una biblioteca más simple, menos optimizada pero con Excelente documentación, toma medio día.

Yo diría que la decisión es usar la biblioteca simple now, pero aislarla detrás una interfaz/adaptador interno explícito (no lo llame directamente desde cada componente), como este que si en el futuro las métricas de desempeño reales (no hipotéticas) muestran que el biblioteca más sofisticada, la sustitución afecta sólo a un punto del código en lugar de ser uno reescritura generalizada.

El principio general que siempre comunico en las conversaciones: velocidad de entrega y La calidad arquitectónica no siempre está en conflicto; a menudo el conflicto real es entre velocidad de entrega y opciones prematuras e irreversibles. Invierta tiempo para mantener Una decisión reversible (a través de una capa de abstracción mínima, no excesiva) permite para cumplir con el plazo sin comprometer la mantenibilidad futura. Decisiones de hecho caro de cambiar más adelante (esquema de base de datos, contratos API públicos, elección de marco) merecen más tiempo de análisis; decisiones fácilmente reversibles (qué biblioteca de diseñadores gráficos) no lo merecen, e insistir en "hacerlo bien de inmediato" a menudo es una pérdida de tiempo. en comparación con el plazo.

Cómo prepararse mejor para una entrevista angular

  • No se limite a memorizar definiciones: Para cada concepto, pregúntese "¿qué error real evita saber esto?" — es exactamente el tipo de respuesta que distingue a un candidato sólido.
  • Practique con código real, no solo teoría: cree un pequeño proyecto que utilice Signal, OnPush, carga diferida y al menos una prueba; el conocimiento práctico siempre emerge en las preguntas de seguimiento.
  • Prepare ejemplos concretos de su trabajo: para preguntas de alto nivel/experto, tener un ejemplo real (incluso uno simplificado) de una solución de compromiso a la que se ha enfrentado vale más que una respuesta teórica impecable.
  • Estudie los errores comunes, no solo las mejores prácticas: saber qué sale mal cuando aplica mal un patrón (por ejemplo, OnPush con mutaciones directas) demuestra una comprensión más profunda que simplemente citar la regla.
  • Manténgase actualizado sobre lanzamientos recientes: La señal, los componentes independientes, sin zona y el nuevo flujo de control (@if/@for) son ahora el estándar esperado en una entrevista de Angular en 2026, no el conocimiento opcional.

Conclusión

Estas 25 preguntas cubren el camino de crecimiento natural de un desarrollador frontend/angular: desde los fundamentos de HTML/CSS/JavaScript/TypeScript, pasando por RxJS y gestión de estado, hasta las decisiones arquitectónicas que un Lead debe ser capaz de motivar frente a un equipo o individuo partes interesadas. Ya sea que se esté preparando para una entrevista o realizando una, el hilo común es siempre lo mismo: comprender por qué una elección técnica es la correcta en un contexto específica, no repita una regla de memoria.

Si te estás preparando para una entrevista Senior o Expert, el consejo más concreto es este: elige tres o cuatro decisiones arquitectónicas reales que hayas tomado (incluso imperfectas, incluso en un proyecto personal) y esté preparado para contarles en detalle: qué eligió, qué alternativas tiene descartados y por qué, qué harías diferente hoy. Es el tipo de respuesta que nadie La definición del libro de texto puede reemplazar.

💬 Notas de los lectores

0 notas

Escribe una nota

Comparte tu opinión, una sugerencia o un cumplido

Últimas notas

Aún no hay notas. ¡Sé el primero en comentar!