Co nowego w Angular 19? Zoneless, Signals i @defer - kompletny przewodnik na rozmowę rekrutacyjną w 2025

Sławomir Plamowski 16 min czytania
angular angular-19 change-detection frontend pytania-rekrutacyjne signals typescript zoneless

Angular 19 wprowadził zmiany, które fundamentalnie zmieniają sposób pisania aplikacji - zoneless change detection eliminuje Zone.js z bundla, Signals zastępują tradycyjne podejście do reaktywności, a nowa składnia @defer rewolucjonizuje lazy loading. Na rozmowach rekrutacyjnych w 2026 roku te tematy będą absolutnym standardem, a kandydaci nieznający różnicy między signal() a computed() czy nieroumiejący wyjaśnić dlaczego Zone.js jest problematyczny, zostaną szybko odrzuceni.

W tym przewodniku znajdziesz wszystkie nowości Angular 19 z praktycznymi przykładami kodu i gotowymi odpowiedziami na pytania rekrutacyjne.

1. Zoneless change detection - Angular bez Zone.js

Wyjaśnienie w 30 sekund

Zoneless change detection to tryb w Angular 19, który pozwala uruchamiać aplikacje bez Zone.js. Zamiast automatycznego monkey-patchingu wszystkich asynchronicznych operacji, Angular używa Signals do precyzyjnego wykrywania zmian. Włącza się go przez provideZonelessChangeDetection(). Korzyści to mniejszy bundle (~13KB mniej), lepsza wydajność, prostsze debugowanie i eliminacja "magicznego" zachowania Zone.js.

Wyjaśnienie w 2 minuty

Zone.js to biblioteka, która przez lata była sercem change detection w Angular. Działa poprzez monkey-patching natywnych API przeglądarki - setTimeout, Promise, addEventListener - żeby Angular wiedział, kiedy uruchomić wykrywanie zmian. Problem? Zone.js dodaje ~13KB do bundla, spowalnia asynchroniczne operacje i sprawia, że debugowanie jest koszmarem (stack trace'y są nieczytelne).

Angular 19 wprowadza alternatywę - aplikacje mogą działać całkowicie bez Zone.js:

// Konfiguracja aplikacji zoneless
import { bootstrapApplication } from '@angular/platform-browser';
import { provideZonelessChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';

bootstrapApplication(AppComponent, {
  providers: [
    provideZonelessChangeDetection(), // Włączamy tryb zoneless
    provideRouter(routes),
    provideHttpClient(),
  ]
}).catch(err => console.error(err));

Po włączeniu zoneless, Angular nie wykrywa automatycznie zmian po każdej asynchronicznej operacji. Zamiast tego, change detection uruchamia się gdy:

  • Zmieni się wartość Signal używanego w szablonie
  • Wywołamy ChangeDetectorRef.markForCheck()
  • Użyjemy PendingTasks do śledzenia operacji asynchronicznych
import { Component, ChangeDetectionStrategy, signal, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { PendingTasks } from '@angular/core';

interface User {
  id: number;
  name: string;
}

@Component({
  selector: 'app-user-list',
  template: `
    <h2>Użytkownicy</h2>
    @for (user of users(); track user.id) {
      <div class="user">{{ user.name }}</div>
    }
    <button (click)="loadUsers()">Odśwież</button>
  `,
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserListComponent {
  private http = inject(HttpClient);
  private pendingTasks = inject(PendingTasks);

  users = signal<User[]>([]);

  loadUsers() {
    // Dla SSR: śledzimy oczekujące operacje asynchroniczne
    const taskCleanup = this.pendingTasks.add();

    this.http.get<User[]>('/api/users').subscribe({
      next: (data) => {
        // Aktualizacja Signal automatycznie triggeruje change detection
        this.users.set(data);
        taskCleanup();
      },
      error: () => taskCleanup(),
    });
  }
}

Klasyczne pytanie rekrutacyjne

Pytanie: Dlaczego Zone.js jest problematyczny i jak zoneless rozwiązuje te problemy?

Odpowiedź: Zone.js ma trzy główne problemy. Po pierwsze, dodaje ~13KB do bundla i spowalnia każdą operację asynchroniczną przez dodatkowy overhead. Po drugie, monkey-patching wszystkich API sprawia, że stack trace'y są nieczytelne - zamiast prostego wywołania widzisz dziesiątki ramek Zone.js. Po trzecie, Zone.js uruchamia change detection po każdej asynchronicznej operacji, nawet jeśli nie zmieniła się żadna dana używana w widoku.

Zoneless rozwiązuje to wszystko - nie ma dodatkowego kodu w bundle, operacje asynchroniczne działają natywnie z czystymi stack trace'ami, a change detection uruchamia się tylko gdy faktycznie zmieni się Signal używany w szablonie.

2. Signals - reaktywność bez RxJS

Wyjaśnienie w 30 sekund

Signals to reaktywne prymitywy wprowadzone w Angular 16, które stały się fundamentem Angular 19. Signal to wrapper na wartość, który powiadamia Angular o zmianach. Mamy trzy typy: signal() do wartości zapisywalnych, computed() do wartości obliczanych, i effect() do side effects. Signals są synchroniczne, automatycznie integrują się z change detection i eliminują potrzebę subscribe/unsubscribe.

Wyjaśnienie w 2 minuty

Signals to odpowiedź Angulara na sukces reaktywności w innych frameworkach (Vue, Solid, Svelte). Zamiast RxJS do prostej reaktywności, mamy teraz lekkie prymitywy:

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

interface Product {
  id: number;
  name: string;
  price: number;
  quantity: number;
}

@Component({
  selector: 'app-shopping-cart',
  template: `
    <h2>Koszyk</h2>

    <div class="cart">
      @for (item of cartItems(); track item.id) {
        <div class="cart-item">
          <span>{{ item.name }}</span>
          <span>{{ item.price }} zł</span>
          <input
            type="number"
            [value]="item.quantity"
            (input)="updateQuantity(item.id, $event)">
          <button (click)="removeItem(item.id)">Usuń</button>
        </div>
      }
    </div>

    <div class="summary">
      <p>Liczba produktów: {{ totalItems() }}</p>
      <p>Suma: {{ subtotal().toFixed(2) }} zł</p>
      <p>VAT (23%): {{ tax().toFixed(2) }} zł</p>
      <p><strong>Razem: {{ total().toFixed(2) }} zł</strong></p>
    </div>

    <button (click)="checkout()" [disabled]="isEmpty()">
      Zamów
    </button>
  `,
  standalone: true,
})
export class ShoppingCartComponent {
  // Writable signal - można modyfikować
  cartItems: WritableSignal<Product[]> = signal([
    { id: 1, name: 'Kurs Angular', price: 199.99, quantity: 1 },
    { id: 2, name: 'Kurs TypeScript', price: 149.99, quantity: 2 },
  ]);

  // Computed signals - automatycznie przeliczane gdy zmienią się zależności
  totalItems = computed(() =>
    this.cartItems().reduce((sum, item) => sum + item.quantity, 0)
  );

  subtotal = computed(() =>
    this.cartItems().reduce((sum, item) => sum + (item.price * item.quantity), 0)
  );

  tax = computed(() => this.subtotal() * 0.23);

  total = computed(() => this.subtotal() + this.tax());

  isEmpty = computed(() => this.cartItems().length === 0);

  constructor() {
    // Effect - wykonuje side effects gdy zmienią się zależności
    effect(() => {
      console.log(`Koszyk zaktualizowany: ${this.totalItems()} produktów`);

      // Zapisz do localStorage
      localStorage.setItem('cart', JSON.stringify(this.cartItems()));
    });

    // Wczytaj koszyk z localStorage
    const savedCart = localStorage.getItem('cart');
    if (savedCart) {
      this.cartItems.set(JSON.parse(savedCart));
    }
  }

  updateQuantity(itemId: number, event: Event) {
    const input = event.target as HTMLInputElement;
    const newQuantity = parseInt(input.value, 10);

    // update() pozwala na modyfikację na podstawie poprzedniej wartości
    this.cartItems.update(items =>
      items.map(item =>
        item.id === itemId
          ? { ...item, quantity: Math.max(1, newQuantity) }
          : item
      )
    );
  }

  removeItem(itemId: number) {
    this.cartItems.update(items => items.filter(item => item.id !== itemId));
  }

  checkout() {
    if (!this.isEmpty()) {
      console.log('Przetwarzanie zamówienia, suma:', this.total());
      this.cartItems.set([]);
    }
  }
}

Diagram porównujący Signals vs RxJS:

flowchart LR subgraph "Signals" S1[signal] -->|synchroniczny| S2[computed] S2 -->|automatyczny| S3[Template] S1 -->|effect| S4[Side Effects] end subgraph "RxJS" R1[BehaviorSubject] -->|pipe| R2[Operators] R2 -->|async pipe| R3[Template] R1 -->|subscribe| R4[Side Effects] R4 -->|unsubscribe!| R5[Memory Leak Risk] end style S1 fill:#e8f5e9 style S2 fill:#e8f5e9 style S3 fill:#e8f5e9 style R5 fill:#ffcdd2

Klasyczne pytanie rekrutacyjne

Pytanie: Kiedy używać Signals, a kiedy RxJS?

Odpowiedź: Signals są idealne do synchronicznego stanu komponentu - formularzy, UI state, prostych wartości obliczanych. Są prostsze (bez subscribe/unsubscribe), automatycznie integrują się z change detection i mają lepszą wydajność.

RxJS nadal jest lepszy do złożonych scenariuszy asynchronicznych - strumieni WebSocket, debounce/throttle na inputach, łączenia wielu źródeł danych (combineLatest, merge), retry logiki dla HTTP. Wzorzec, który mi się sprawdził: Signals dla "co wyświetlamy", RxJS dla "skąd to pobieramy".

3. Signal inputs i outputs - nowe API komponentów

Wyjaśnienie w 30 sekund

input() i output() to nowe funkcje zastępujące dekoratory @Input() i @Output(). Kluczowa różnica: input() zwraca Signal, więc jest reaktywny i automatycznie triggeruje change detection. Wspiera input.required<T>() dla wymaganych inputów, transformacje wartości i lepsze typowanie. output() używa OutputEmitterRef zamiast EventEmitter.

Wyjaśnienie w 2 minuty

Nowe API input() i output() rozwiązuje kilka problemów starych dekoratorów. Po pierwsze, inputy są teraz Signals, więc możesz używać ich w computed() i effect(). Po drugie, masz lepsze typowanie - input.required<T>() wymusza przekazanie wartości. Po trzecie, możesz definiować transformacje inline. Oto kompleksowy przykład:

import { Component, input, output, computed } from '@angular/core';

interface User {
  id: number;
  name: string;
}

@Component({
  selector: 'app-user-profile',
  template: `
    <div class="profile">
      <h2>{{ displayName() }}</h2>
      <p>ID: {{ userId() }}</p>
      @if (isAdmin()) {
        <span class="badge">Administrator</span>
      }
      <button (click)="handleEdit()">Edytuj profil</button>
      <button (click)="handleDelete()">Usuń</button>
    </div>
  `,
  standalone: true,
})
export class UserProfileComponent {
  // Signal input - wymagany, reaktywny
  user = input.required<User>();

  // Opcjonalny input z domyślną wartością
  showId = input(true);

  // Input z transformacją wartości
  role = input('user', {
    transform: (value: string) => value.toLowerCase()
  });

  // Signal output - emituje zdarzenia
  userEdit = output<User>();
  profileDeleted = output<void>();

  // Computed signals - reagują na zmiany inputów
  displayName = computed(() => {
    const currentUser = this.user();
    return `${currentUser.name} (#${currentUser.id})`;
  });

  userId = computed(() => this.showId() ? this.user().id : null);

  isAdmin = computed(() => this.role() === 'admin');

  handleEdit() {
    // Emituj zdarzenie z danymi
    this.userEdit.emit(this.user());
  }

  handleDelete() {
    // Emituj zdarzenie bez danych
    this.profileDeleted.emit();
  }
}

// Użycie w komponencie rodzica:
// <app-user-profile
//   [user]="currentUser"
//   [showId]="true"
//   role="Admin"
//   (userEdit)="onEditUser($event)"
//   (profileDeleted)="onDelete()" />

Porównanie starego i nowego API:

// ❌ Stary sposób - dekoratory
@Component({ ... })
export class OldComponent {
  @Input() user!: User;
  @Input() showId = true;
  @Output() userEdit = new EventEmitter<User>();

  // Brak reaktywności - trzeba używać ngOnChanges
  ngOnChanges(changes: SimpleChanges) {
    if (changes['user']) {
      this.updateDisplayName();
    }
  }
}

// ✅ Nowy sposób - signal inputs
@Component({ ... })
export class NewComponent {
  user = input.required<User>();
  showId = input(true);
  userEdit = output<User>();

  // Reaktywność wbudowana - computed automatycznie reaguje
  displayName = computed(() => `${this.user().name}`);
}

4. Nowy control flow - @if, @for, @switch

Wyjaśnienie w 30 sekund

Angular 17+ wprowadził nową składnię control flow: @if, @for, @switch zamiast *ngIf, *ngFor, *ngSwitch. Nowa składnia jest szybsza (optymalizacje kompilatora), prostsza (bez ng-template), wspiera @else if bezpośrednio, i dodaje @let do deklaracji zmiennych lokalnych. Jest type-safe i ma lepsze komunikaty błędów.

Wyjaśnienie w 2 minuty

Nowa składnia control flow to nie tylko kosmetyczna zmiana - kompilator może ją lepiej optymalizować, bo zna strukturę w compile-time. Dodatkowo @else if działa bez dodatkowych ng-template, a @for wymaga track co eliminuje problemy wydajnościowe. Porównaj stary i nowy sposób:

@Component({
  selector: 'app-product-list',
  template: `
    <!-- @if z @else if i @else -->
    @if (loading()) {
      <div class="spinner">Ładowanie...</div>
    } @else if (error()) {
      <div class="error">Błąd: {{ error() }}</div>
    } @else if (products().length === 0) {
      <div class="empty">Brak produktów</div>
    } @else {
      <!-- @for z track i @empty -->
      @for (product of products(); track product.id) {
        <div class="product">
          <h3>{{ product.name }}</h3>
          <p>{{ product.price }} zł</p>

          <!-- @switch dla statusu -->
          @switch (product.status) {
            @case ('available') {
              <span class="badge green">Dostępny</span>
            }
            @case ('low-stock') {
              <span class="badge yellow">Ostatnie sztuki</span>
            }
            @case ('out-of-stock') {
              <span class="badge red">Niedostępny</span>
            }
            @default {
              <span class="badge gray">Nieznany status</span>
            }
          }
        </div>
      } @empty {
        <p>Lista jest pusta</p>
      }
    }

    <!-- @let do deklaracji zmiennych lokalnych -->
    @let total = calculateTotal();
    @let discountedTotal = total * 0.9;

    <div class="summary">
      <p>Suma: {{ total }} zł</p>
      <p>Po rabacie: {{ discountedTotal }} zł</p>
    </div>
  `,
  standalone: true,
})
export class ProductListComponent {
  loading = signal(false);
  error = signal<string | null>(null);
  products = signal<Product[]>([]);

  calculateTotal() {
    return this.products().reduce((sum, p) => sum + p.price, 0);
  }
}

Porównanie starej i nowej składni:

<!-- ❌ Stary sposób - *ngIf z ng-template -->
<div *ngIf="loading; else notLoading">Ładowanie...</div>
<ng-template #notLoading>
  <div *ngIf="error; else showContent">{{ error }}</div>
</ng-template>
<ng-template #showContent>
  <div *ngFor="let item of items; trackBy: trackById">
    {{ item.name }}
  </div>
</ng-template>

<!-- ✅ Nowy sposób - @if/@for -->
@if (loading) {
  <div>Ładowanie...</div>
} @else if (error) {
  <div>{{ error }}</div>
} @else {
  @for (item of items; track item.id) {
    <div>{{ item.name }}</div>
  }
}

Klasyczne pytanie rekrutacyjne

Pytanie: Dlaczego track w @for jest wymagany, a trackBy w *ngFor był opcjonalny?

Odpowiedź: Angular wymusił track w nowej składni, ponieważ brak trackowania jest częstym źródłem problemów wydajnościowych. Bez track, Angular musi porównywać obiekty przez referencję i przebudowywać całą listę przy każdej zmianie. Z track, Angular wie które elementy się zmieniły i aktualizuje tylko je. Wymuszenie track od początku eliminuje całą klasę bugów wydajnościowych.

5. @defer - lazy loading na poziomie szablonu

Wyjaśnienie w 30 sekund

@defer to nowa składnia do lazy loadingu komponentów bezpośrednio w szablonie. Pozwala opóźnić ładowanie ciężkich komponentów do momentu spełnienia warunku - widoczność w viewport, interakcja użytkownika, timer lub custom condition. Składnia zawiera bloki @placeholder, @loading i @error dla różnych stanów.

Wyjaśnienie w 2 minuty

@defer to game-changer dla wydajności aplikacji Angular. Zamiast ładować wszystko na starcie, możesz opóźnić ładowanie ciężkich komponentów do momentu gdy są naprawdę potrzebne. Angular automatycznie tworzy osobny chunk i ładuje go w odpowiednim momencie. Oto praktyczne przykłady różnych triggerów:

@Component({
  selector: 'app-dashboard',
  template: `
    <h1>Dashboard</h1>

    <!-- Podstawowy @defer - ładuje gdy przeglądarka jest idle -->
    @defer {
      <app-heavy-chart [data]="chartData()" />
    } @placeholder {
      <div class="skeleton">Przygotowywanie wykresu...</div>
    } @loading (minimum 500ms) {
      <div class="spinner">Ładowanie wykresu...</div>
    } @error {
      <div class="error">Nie udało się załadować wykresu</div>
    }

    <!-- @defer on viewport - ładuje gdy element jest widoczny -->
    @defer (on viewport) {
      <app-comments [postId]="postId()" />
    } @placeholder {
      <div class="comments-placeholder">
        Przewiń aby zobaczyć komentarze
      </div>
    }

    <!-- @defer on interaction - ładuje po kliknięciu -->
    <button #loadBtn>Pokaż statystyki</button>
    @defer (on interaction(loadBtn)) {
      <app-statistics />
    } @placeholder {
      <p>Kliknij przycisk aby załadować statystyki</p>
    }

    <!-- @defer on hover - ładuje gdy użytkownik najedzie myszką -->
    <div #hoverArea class="hover-trigger">
      Najedź myszką aby załadować podgląd
    </div>
    @defer (on hover(hoverArea)) {
      <app-preview-card />
    }

    <!-- @defer on timer - ładuje po określonym czasie -->
    @defer (on timer(3s)) {
      <app-recommendations />
    } @placeholder {
      <p>Rekomendacje załadują się za chwilę...</p>
    }

    <!-- @defer when - ładuje gdy warunek jest spełniony -->
    @defer (when isLoggedIn()) {
      <app-user-settings />
    } @placeholder {
      <p>Zaloguj się aby zobaczyć ustawienia</p>
    }

    <!-- Kombinacja triggerów -->
    @defer (on viewport; on timer(5s); prefetch on idle) {
      <app-newsletter-signup />
    }
  `,
  standalone: true,
})
export class DashboardComponent {
  chartData = signal([]);
  postId = signal(1);
  isLoggedIn = signal(false);
}

Diagram działania @defer:

stateDiagram-v2 [*] --> Placeholder: Komponent renderowany Placeholder --> Loading: Trigger spełniony Loading --> Loaded: Bundle pobrany Loading --> Error: Błąd ładowania Loaded --> [*]: Komponent wyświetlony Error --> [*]: Błąd wyświetlony note right of Placeholder @placeholder Wyświetlany od razu end note note right of Loading @loading minimum/after opcje end note note right of Error @error Obsługa błędów end note

Dostępne triggery @defer:

Trigger Opis Przykład
on idle Gdy przeglądarka jest idle (domyślny) @defer (on idle)
on viewport Gdy element jest widoczny @defer (on viewport)
on interaction Po kliknięciu/focus @defer (on interaction(btn))
on hover Po najechaniu myszką @defer (on hover(element))
on timer Po określonym czasie @defer (on timer(2s))
when Gdy warunek jest true @defer (when isReady())
prefetch Preload przed triggerem @defer (prefetch on idle)

6. Migracja z Angular 18 do Angular 19

Checklist migracji

  1. Zaktualizuj Angular CLI i pakiety:
ng update @angular/core@19 @angular/cli@19
  1. Włącz zoneless (opcjonalnie):
// main.ts
bootstrapApplication(AppComponent, {
  providers: [
    provideZonelessChangeDetection(),
    // ... inne providery
  ]
});
  1. Migruj @Input/@Output na signal inputs:
ng generate @angular/core:signal-input-migration
  1. **Zamień ngIf/ngFor na @if/@for:
ng generate @angular/core:control-flow-migration
  1. Usuń Zone.js z bundla (jeśli zoneless):
// angular.json - usuń zone.js z polyfills
"polyfills": [
  // "zone.js"  <- usuń tę linię
]

7. Computed vs Effect - kiedy którego użyć

Wyjaśnienie w 30 sekund

computed() służy do tworzenia wartości obliczanych z innych Signals - jest czysty, bez side effects, i automatycznie cachuje wynik. effect() służy do wykonywania side effects (logowanie, localStorage, API calls) gdy zmienią się Signals. Złota zasada: jeśli potrzebujesz wartości, użyj computed; jeśli potrzebujesz akcji, użyj effect.

Wyjaśnienie w 2 minuty

Różnica jest fundamentalna: computed() to czysta funkcja która zwraca wartość, effect() to procedura która wykonuje akcje. Computed automatycznie śledzi zależności i cachuje wynik - przelicza się tylko gdy zmienią się źródłowe Signals. Effect wykonuje się za każdym razem gdy zmieni się którykolwiek z używanych Signals. Typowe zastosowania:

@Component({
  selector: 'app-example',
  template: `
    <input [value]="searchTerm()" (input)="updateSearch($event)">
    <p>Wyniki dla: {{ normalizedSearch() }}</p>
    <p>Znaleziono: {{ filteredResults().length }} wyników</p>
  `,
})
export class ExampleComponent {
  searchTerm = signal('');
  allItems = signal<Item[]>([]);

  // ✅ computed - czysta funkcja, zwraca wartość
  normalizedSearch = computed(() =>
    this.searchTerm().trim().toLowerCase()
  );

  // ✅ computed - filtrowanie na podstawie innych signals
  filteredResults = computed(() => {
    const search = this.normalizedSearch();
    if (!search) return this.allItems();

    return this.allItems().filter(item =>
      item.name.toLowerCase().includes(search)
    );
  });

  constructor() {
    // ✅ effect - side effect (logowanie, analytics)
    effect(() => {
      const term = this.searchTerm();
      if (term.length > 2) {
        console.log('Użytkownik szuka:', term);
        this.analytics.trackSearch(term);
      }
    });

    // ✅ effect - synchronizacja z zewnętrznym systemem
    effect(() => {
      localStorage.setItem('lastSearch', this.searchTerm());
    });
  }

  updateSearch(event: Event) {
    const input = event.target as HTMLInputElement;
    this.searchTerm.set(input.value);
  }
}

Błędy, których należy unikać:

// ❌ ŹLE - modyfikowanie Signal w computed
badComputed = computed(() => {
  this.someSignal.set(123); // BŁĄD! computed musi być czysty
  return this.otherSignal() * 2;
});

// ❌ ŹLE - tworzenie effect wewnątrz computed
anotherBadComputed = computed(() => {
  effect(() => console.log('test')); // BŁĄD! NG0602
  return this.value();
});

// ❌ ŹLE - toSignal wewnątrz computed
yetAnotherBad = computed(() => {
  const data = toSignal(this.http.get('/api')); // BŁĄD!
  return data();
});

// ✅ DOBRZE - toSignal poza computed
dataSignal = toSignal(this.http.get('/api'));
processedData = computed(() => this.dataSignal()?.map(/* ... */));

Na co rekruterzy naprawdę zwracają uwagę

Po przeprowadzeniu wielu rozmów rekrutacyjnych z pytaniami o Angular 19, mogę powiedzieć że rekruterzy sprawdzają:

Czy rozumiesz "dlaczego" zoneless - nie wystarczy wiedzieć jak włączyć. Kandydaci, którzy robią wrażenie, potrafią wyjaśnić problemy Zone.js (bundle size, performance overhead, nieczytelne stack traces) i jak Signals je rozwiązują.

Czy znasz różnicę między signal() a computed() a effect() - to fundamentalne pytanie. Signal to wartość zapisywalna, computed to wartość obliczana (czysta, cachowana), effect to side effect. Jeśli ktoś myli te koncepty, od razu widać brak praktycznego doświadczenia.

Czy potrafisz migrować istniejący kod - pytania typu "masz komponent z @Input i ngOnChanges, jak go zmigrujesz na signal inputs?" są bardzo popularne. Sprawdzają czy kandydat naprawdę pracował z nowym API.

Czy wiesz kiedy @defer ma sens - lazy loading wszystkiego to antywzorzec. Dobry kandydat wie, że @defer jest dla ciężkich komponentów below the fold, a nie dla każdego elementu.


Zobacz też


Chcesz więcej pytań z Angulara?

Ten artykuł to tylko wierzchołek góry lodowej. Mamy ponad 150 pytań z Angular z szczegółowymi odpowiedziami, przykładami kodu i wyjaśnieniami - od podstaw po zaawansowane tematy jak Signals, Dependency Injection i optymalizacja wydajności.

Sprawdź Pełny Zestaw Pytań Angular →

Lub wypróbuj nasz darmowy podgląd pytań Angular, żeby zobaczyć więcej pytań w tym stylu.


Napisane przez zespół Flipcards, na podstawie oficjalnej dokumentacji Angular 19 i doświadczeń z rozmów rekrutacyjnych w firmach takich jak Google, Microsoft i wiodących software house'ach.

Chcesz więcej pytań rekrutacyjnych?

To tylko jeden temat z naszego kompletnego przewodnika po rozmowach rekrutacyjnych. Uzyskaj dostęp do 800+ pytań z 13 technologii.

Kup pełny dostęp Zobacz bezpłatny podgląd
Powrót do blogu

Zostaw komentarz

Pamiętaj, że komentarze muszą zostać zatwierdzone przed ich opublikowaniem.