30 maj 2025
11 min

Angular 20 – Co nowego

Angular 20 właśnie się ukazał, wprowadzając zupełnie nowe API, usprawnienia w developer experience, lepsze sprawdzanie typów, bardziej rozbudowane diagnostyki w CLI oraz stabilizację funkcji z wcześniejszych wersji Angulara. Aby ułatwić Ci nawigację po tych zmianach, artykuł podzieliłem na cztery sekcje:

Nowe funkcje: przegląd nowych API w Angular 20

Developer Experience: aktualizacje CLI, diagnostyki i narzędzi do łapania błędów

Zmiany stabilności API: które API są już produkcyjnie gotowe, a które wciąż eksperymentalne

Złamania zgodności: kluczowe kwestie przy migracji projektu do Angular 20

Nowe Funkcje

Przegląd najważniejszych nowych API w Angular 20.

Obsługa nowych konstrukcji w szablonach

Angular 20 wprowadza kilka nowych możliwości w kompilatorze szablonów, które mają zbliżyć składnię wyrażeń w szablonach do czystego TypeScriptu. Docelowo wszystkie wyrażenia w Angularowych szablonach będą działać jak w TypeScript – w kolejnych wydaniach spodziewamy się wsparcia dla funkcji strzałkowych oraz pełnej obsługi optional chaining. Szczegóły: issue na GitHubie.

Poniżej najistotniejsze nowości: literały szablonowe, operator potęgowania, operator in oraz operator void. Omówimy każdą z nich.

Literały szablonowe (Template literals)

Dotychczas łączenie stringów w szablonach Angulara bywało rozwlekłe. Teraz możesz używać w nich czystych, JavaScriptowych literałów szablonowych bezpośrednio w HTML komponentu.

Uwaga: literały szablonowe w Angularze nie działają w przypadku osadzonego HTML-a wewnątrz TypeScriptowego template literal. Więcej na ten temat w tym issue.

Literały niezadeklarowane (Untagged Template Literals)

user-avatar.ts

@Component({
 selector: 'app-user-avatar',
 imports: [NgOptimizedImage],
 templateUrl: './user-avatar.html',
})
export class UserAvatar {
 readonly userId = input.required<string>();
}

user-avatar.html

<img
 [ngSrc]="`https://i.pravatar.cc/150?u=${userId()}`"
 width="100"
 height="100"
/>
<p>{{ `User id: ${userId()}` }}</p>

Wynik renderowania:

Literały z tagiem (Tagged Template Literals):
@Component({
 selector: 'app-user-details',
 template: '<p>{{ greet`Hello, ${name()}` }}</p>',
})
export class UserDetails {
 readonly name = input<string>('John');


 greet(strings: TemplateStringsArray, name: string) {
   return strings[0] + name + strings[1] + '!';
 }
}

Wynik renderowania:

Takie podejście znacznie upraszcza i uczytelnia złożone interpolacje.

Operator potęgowania

Angular 20 dodaje obsługę operatora (**) w szablonach, dzięki czemu możesz podnosić liczby do potęgi bez potrzeby tworzenia własnych pipe’ów.

Dla przykładu:

@Component({
 template: '{{2 ** 3}}'
})
export class AppComponent {}

To wyrażenie wyrenderuje 8, bo 2 do potęgi 3 jest równe 8.

Operator in

Operator in pozwala sprawdzić, czy obiekt posiada określoną własność, zanim ją wyświetlisz. Przydaje się to do zawężania typów lub warunkowego renderowania pól.

Dla przykładu:

combat-logs.ts

@Component({
 selector: 'app-combat-logs',
 templateUrl: './combat-logs.html',
})
export class CombatLog {
 readonly attacks = [
   { magicDamage: 10 },
   { physicalDamage: 10 },
   { magicDamage: 10, physicalDamage: 10 },
 ];
}

combat-logs.html

@for (attack of attacks; track attack) {
 @let hasMagicDamage = 'magicDamage' in attack;
 @if (hasMagicDamage) {
   <p>{{ `Dealt ${attack.magicDamage} points of magic damage.` }}</p>
 }
 @let hasPhysicalDamage = 'physicalDamage' in attack;
 @if (hasPhysicalDamage) {
   <p>{{ `Dealt ${attack.physicalDamage} points of physical damage.` }}</p>
 }
}

Wynik renderowania:

Operator void

Ostatnim z nowych operatorów jest void. Użyj go, aby jawnie zignorować wartość zwracaną przez powiązany listener, co zapobiegnie przypadkowemu wywołaniu event.preventDefault(), jeśli Twój handler zwróci false.

Dla przykładu:

@Directive({
 host: { '(mousedown)': 'void handleMousedown()' },
})
export class MouseDownDirective {
 handleMousedown(): boolean {
   // Business logic...
   return false;
 }
}

Asynchroniczna funkcja przekierowania

Angular 20 pozwala teraz na asynchroniczne funkcje przekierowania. Właściwość redirectTo może zwracać Promise lub Observable typu string | UrlTree. Dzięki temu możesz zbudować logikę przekierowania, która poczeka na dane (np. z serwera) zanim zdecyduje, gdzie skierować użytkownika.

Dla przykładu:

export const ROUTES: Routes = [
 …,
 {
   path: '**',
   redirectTo: () => {
     const router = inject(Router);
     const authService = inject(AuthService);


     return authService.isAuthorized$.pipe(
       map((isAuthorized) =>
         router.createUrlTree([`/${isAuthorized ? 'home' : 'login'}`]),
       ),
     );
   },
 },
];

Przerwanie Przekierowania

Angular 20 dodaje nową metodę Router.getCurrentNavigation()?.abort(), umożliwiającą anulowanie trwających nawigacji. To pozwala na lepszą integrację z Navigation API przeglądarki – np. zatrzymanie zmiany route’a, gdy użytkownik kliknie przycisk „Stop” w przeglądarce.

Nowości w NgComponentOutlet

NgComponentOutlet to dynamiczny sposób na tworzenie i renderowanie komponentów wewnątrz szablonów. Działa podobnie do RouterOutlet, ale nie wymaga konfiguracji routera, dzięki czemu świetnie nadaje się do scenariuszy z renderowaniem dynamicznie ładowanych komponentów. Wcześniej jednak wymagało to dużo ręcznego kodu, co bywało uciążliwe.

NgComponentOutlet przed Angular 20:

@Component({
 template: `<ng-container #container />`
})
export class AppComponent {
 private _cmpRef?: ComponentRef<MyComponent>;
 private readonly _container = viewChild('container', {
    read: ViewContainerRef
 });


 createComponent(title: string): void {
    this.destroyComponent(); // Otherwise it would create second instance


    this._cmpRef = this._container()?.createComponent(MyComponent);
    this._cmpRef?.setInput('title', title);
 }


 destroyComponent(): void {
    this._container()?.clear();
 }
}

W Angular 20 API NgComponentOutlet zostało rozbudowane o wygodne inputy, które same zajmują się ręczną konfiguracją. Teraz dyrektywa ngComponentOutlet obsługuje następujące inputy:

  • ngComponentOutlet określa typ tworzonego komponentu.
  • ngComponentOutletInputs przekazuje wartości inputów bezpośrednio do komponentu.
  • ngComponentOutletContent definiuje węzły do projekcji treści (content projection).
  • ngComponentOutletInjector przekazuje niestandardowy injector do tworzonego komponentu.

Dzięki temu kod do dynamicznego tworzenia komponentów jest dużo bardziej czytelny.

Nowe API NgComponentOutlet w Angular 20+:

@Component({
 selector: 'app-root',
 imports: [NgComponentOutlet],
 template: `
   <ng-container
     [ngComponentOutlet]="myComponent"
     [ngComponentOutletInputs]="myComponentInput()"
     [ngComponentOutletContent]="contentNodes()"
     [ngComponentOutletInjector]="myInjector"
     #outlet="ngComponentOutlet"
   />


   <ng-template #emptyState>
     <p>Empty State</p>
   </ng-template>


   <button (click)="createComponent()">Create Component</button>
   <button (click)="destroyComponent()">Destroy Component</button>
 `,
})
export class App {
 private readonly _vcr = inject(ViewContainerRef);
 private readonly _injector = inject(Injector);


 protected myComponent: Type<DynamicComponent> | null = null;
 protected readonly myComponentInput = signal({ title: 'Example Title' });


 private readonly _emptyStateTemplate =
   viewChild<TemplateRef<unknown>>('emptyState');


 readonly contentNodes = computed(() => {
   if (!this._emptyStateTemplate()) return [];


   return [
     this._vcr.createEmbeddedView(this._emptyStateTemplate()!).rootNodes,
   ];
 });


 readonly myInjector = Injector.create({
   providers: [{ provide: MyService, deps: [] }],
   parent: this._injector,
 });


 createComponent(): void {
   this.myComponent = DynamicComponent;
 }


 destroyComponent(): void {
   this.myComponent = null;
 }
}

Wsparcie dla bindowania Input, Output oraz dyrektyw w dynamicznie tworzonych komponentach

Angular pozwala teraz bezpośrednio aplikować inputy, outputy, two-way binding oraz dyrektywy hosta podczas tworzenia komponentów dynamicznie.

Dzięki funkcjom pomocniczym takim jak inputBinding, twoWayBinding i outputBinding oraz tablicy directives, możesz zainstalować komponent z powiązaniami podobnymi do tych z szablonów oraz dołączyć dyrektywy w jednym wywołaniu ViewContainerRef.createComponent.

Ta zmiana znacznie wzmacnia API dynamicznych komponentów.

Dla przykładu:

@Component({
...
})
export class AppWarningComponent {
 readonly canClose = input.required<boolean>();
 readonly isExpanded = model<boolean>();
 readonly close = output<boolean>();
}
@Component({
 template: ` <ng-container #container></ng-container> `,
})
export class AppComponent {
 readonly vcr = viewChild.required('container', { read: ViewContainerRef });
 readonly canClose = signal(true)
 readonly isExpanded = signal(true)


 createWarningComponent(): void {
   this.vcr().createComponent(AppWarningComponent, {
       bindings: [
           inputBinding('canClose', this.canClose),
           twoWayBinding('isExpanded', this.isExpanded),
           outputBinding<boolean>('close', (isConfirmed) => console.log(isConfirmed))
       ],
       directives: [
           FocusTrap,
           {
               type: ThemeDirective,
               bindings: [inputBinding('theme', () => 'warning')]
           }
       ]
   })
 }
}

Udostępnienie Injector.destroy dla Injector tworzonego przez Injector.create

W Angular 20 metoda destroy() została udostępniona na instancjach Injector stworzonych za pomocą Injector.create(), co pozwala na niszczenie własnych injectorów:

const injector = Injector.create({
 providers: [{ provide: LOCALE_ID, useValue: 'en-US' }],
});


// API exposed in Angular 20
injector.destroy();

Wsparcie dla opcji keepalive w żądaniach fetch

Angular dodał obsługę flagi keepalive z Fetch API w zapytaniach HttpClient, umożliwiając wykonywanie asynchronicznych operacji podczas zamykania strony (np. wysyłania danych analitycznych).

Włączając { keepalive: true } w wywołaniach opartych na pobieraniu, Angular pozwala tym żądaniom działać do końca nawet podczas eventu unload strony.

@Injectable({ providedIn: 'root' })
export class AnalyticsService {
 private readonly _http = inject(HttpClient);


 sendAnalyticsData(data: AnalyticsData): Observable<unknown> {
   return this._http.post('/api/analytics', data, { keepalive: true });
 }
}

Opcje przewijania w ViewportScroller

ViewportScroller w Angularze teraz akceptuje obiekt ScrollOptions w swoich metodach scrollToAnchor i scrollToPosition, dając większą kontrolę nad zachowaniem przewijania.

Formularze oparte na sygnałach i komponenty selectorless

W Angular 20 nie otrzymamy:

  • Formularzy sygnałowych – nowy sygnałowy sposób budowania formularzy
  • Komponentów selectorless – zmiany w użyciu komponentów i dyrektyw w HTML

Obie te funkcje są wciąż w fazie rozwoju i nie zostały jeszcze wydane.

Developer Experience

Zmiany w CLI, diagnostyce oraz narzędziach do wykrywania błędów.

Sprawdzanie typów w powiązaniach hosta

Angular 20 wprowadza walidację typów we wszystkich wyrażeniach metadanych host komponentów i dyrektyw oraz we funkcjach oznaczonych @HostBinding i @HostListener.

Angular Language Service potrafi teraz:

  • Pokazywać w podpowiedziach (hover) typy powiązanych zmiennych lub funkcji
  • Automatycznie aktualizować referencje host-binding podczas zmiany nazwy zmiennej lub metody

Dzięki temu zredukowane zostaną błędy w czasie wykonywania, a refaktoryzacja powiązań hosta stanie się przyjemniejsza.

Rozszerzona diagnostyka błędnego użycia operatora nullish coalescing

In TypeScript, mixing the nullish coalescing operator (??) with logical OR (||) or logical AND (&&) without parentheses is a compile-time error. Previously, Angular templates allowed this without any warning.

W TypeScript mieszanie operatora (??) z logicznym OR (||) lub logicznym AND (&&) bez nawiasów jest błędem kompilacji, lecz wcześniej Angularowe szablony nie ostrzegały o tym.

Od Angular 20 kompilator zgłasza diagnostykę, gdy połączysz te operatory bez grupowania, i sugeruje dodanie odpowiednich nawiasów. Dla przykładu:

@Component({
 template: `
   <button [disabled]="hasPermission() && (task()?.disabled ?? true)">
     Run
   </button>
 `,
})
class MyComponent {
 hasPermission = input(false);
 task = input<Task|undefined>(undefined);
}

Rozszerzona diagnostyka nieużytych funkcji track

Podczas migracji z *ngFor na odpowiednik z nowszego control flow @for, przekazanie funkcji track bez jej wywołania (np. track trackByName) powodowało odtwarzanie listy przy każdej zmianie.

W Angular 20 kompilator ostrzeże Cię, gdy zapomnisz wywołać funkcję track w bloku @for, co pomoże zachować optymalną wydajność.

Niepoprawnie (wywoła ostrzeżenie):

@for (item of items; track trackByName) {}

Poprawnie (brak ostrzeżenia):

@for (item of items; track trackByName(item)) {}

Wykrywanie brakujących importów dyrektyw strukturalnych

Przed Angular 20 kompilator zgłaszał brak importu wbudowanych dyrektyw strukturalnych (*ngIf, *ngFor), ale nie sugerował importu własnych dyrektyw strukturalnych – co bywało problematyczne zwłaszcza w procesie migracji do standalone components, gdzie łatwo można zapomnieć o imporcie dyrektywy.

Angular 20 ostrzeże Cię, gdy użyjesz niestandardowej dyrektywy strukturalnej bez importu.

Dla przykładu:

@Component({
 selector: 'app-root',
 template: `<div *appFeatureFlag="true"></div>`,
})
export class App {}

W takiej sytuacji zobaczysz ostrzeżenie:

Zmiany stabilności API

API związane z sygnałami

W Angular 20 zespół angulara kontynuuje dojrzewanie swoich API opartych na sygnałach:

toSignal i toObservable

Te narzędzia konwersji są teraz stabilne, dzięki czemu możesz swobodnie łączyć sygnały z obserwablami w środowisku produkcyjnym, bez obaw o łamanie zgodności w przyszłych wydaniach angulara.

linkedSignal

To API – służące do tworzenia zapisywalnego sygnału, którego wartość wyprowadza się z innego sygnału – również zostało oznaczone jako stabilne w wersji v20.

effect

API efektów, pozwalające uruchamiać logikę uboczną za każdym razem, gdy zmieni się wartość sygnału, jest teraz stabilne. Przeszło ono kilka poprawek w fazie podglądu dla deweloperów, więc jego unormowanie stanowi ważny kamień milowy.

afterRender afterEveryRender

Aby uczynić jego przeznaczenie bardziej czytelnym, stary hook afterRender został przemianowany na afterEveryRender. W Angular 20 zarówno afterEveryRender, jak i towarzyszący mu afterNextRender są oficjalnie stabilne.

Kolejny krok w kierunku Angulara Zoneless

Removing zone.js has been a major focus for the Angular team over the past year, and they’ve made significant progress. In Angular 20, the API for zoneless change detection has moved from experimental to developer preview.

Usunięcie zone.js było jednym z głównych celów zespołu Angular w ostatnim roku i poczyniono w tej sprawie znaczne postępy. W Angular 20 API dla detekcji zmian bez zone.js (zoneless change detection) zostało przeniesione z etapu eksperymentalnego do developer preview.

W ramach tej zmiany nazwa providera została zmieniona z:

provideExperimentalZonelessChangeDetection

na:

provideZonelessChangeDetection

Kilka osób z społeczności raportuje, że przejście na detekcję zmian bez zone.js podniosło im wyniki w narzędziu Lighthouse o kilka punktów. Google również wdraża tę technikę w kolejnych aplikacjach – na przykład aplikacja Google Fonts działa bez zone.js od siedmiu miesięcy (stan na dzień pisania artykułu).

Podczas tworzenia nowych aplikacji Angular komendą ng new przy użyciu Angular CLI w wersji 20, pojawi się teraz opcja wyboru aplikacji zoneless „bez zone.js”.

Jeśli planujesz migrację swojego projektu do detekcji zmian bez zone.js, warto przyjrzeć się dawnej funkcji provideExperimentalCheckNoChangesForDebug. W Angular 20 nosi ona nazwę provideCheckNoChangesConfig i również jest w fazie developer preview. Ten provider pomaga wykryć wszelkie zmiany, które nie wywołały detekcji zmian – to doskonały sposób, by zweryfikować gotowość aplikacji do pracy w trybie zoneless.

Pending Tasks w Angular SSR

Jeśli korzystasz z renderowania po stronie serwera (SSR), ucieszy Cię wiadomość, że API PendingTasks jest teraz stabilne w Angular 20. To API pozwala zarządzać stabilnością aplikacji, opóźniając odpowiedź SSR do momentu zakończenia określonych zadań.

Równocześnie niestandardowy operator RxJS pendingUntilEvent – który korzysta z PendingTasks – został z fazy eksperymentalnej przeniesiony do developer preview.

Złamania zgodności (Breaking Changes)

Rzeczy, na które warto zwrócić uwagę podczas aktualizacji projektu do Angular 20

Zależności (Peer Dependencies)

W Angular 20 wymagane peer dependencies to:

  • Node: ^20.11.1 || ^22.11.0 || ^24.0.0
  • TypeScript: >=5.8.0 <5.9.0
  • RxJs: ^6.5.3 || ^7.4.0

Wsparcie dla Node 18 oraz dla wersji TypeScript poniżej 5.8 zostało usunięte.

Atrybuty Ng-Reflect

Od Angular 20 framework przestał emitować w trybie deweloperskim atrybuty ng-reflect-*. Po aktualizacji wszystkie testy, które polegały na tych atrybutach, zaczną się niepowodzeniami.

Aby przeanalizować impact tej zmiany spójrzmy komponent app-child jako przykład:

@Component({
 selector: 'app-child',
 template: ` <h2>Child Component property: {{ property() }}</h2> `,
})
export class AppChildComponent {
 readonly property = input('');
}

Przed aktualizacją DOM wyglądał tak (w trybie developerskim):

Po aktualizacji do Angular 20 DOM będzie wyglądał tak (w trybie developerskim):

Jeśli potrzebujesz tymczasowo przywrócić atrybuty ng-reflect-*, możesz dodać providera provideNgReflectAttributes() do głównych providerów aplikacji. Jednak zdecydowanie zalecam refaktoryzację testów, aby używały stabilnych, własnych atrybutów – na przykład data-cy lub data-test-id – które nie zostaną zmienione przy kolejnych aktualizacjach Angulara.

Usunięcie InjectFlags

W Angularze 20 usunięto wcześniej przestarzałe API InjectFlags. Aby ułatwić migrację, Angular dostarcza schemat migracyjny, który automatycznie przekształci Twój kod z:

import { inject, InjectFlags, Directive, ElementRef } from '@angular/core';
@Directive()
export class Dir {
 el = inject(ElementRef, InjectFlags.Optional | InjectFlags.Host | InjectFlags.SkipSelf);
}

na składnię z obiektem opcji:

import { inject, Directive, ElementRef } from '@angular/core';
@Directive()
export class Dir {
 el = inject(ElementRef, { optional: true, host: true, skipSelf: true });
}

Deprecacja HammerJS

Oficjalne wsparcie dla HammerJS zostało uznane za przestarzałe i zostanie usunięte w przyszłej dużej wersji (Angular 21). Jeśli Twoja aplikacja korzysta z gestów dotykowych opartych na HammerJS, zaplanuj własną implementację lub alternatywne rozwiązanie.

Podsumowanie

Angular 20 wprowadza liczne nowe możliwości w szablonach, stabilizuje kluczowe API oparte na sygnałach i RxJS oraz wzmacnia developer experience dzięki lepszej diagnostyce i usprawnieniom w CLI.

To także istotny krok w kierunku aplikacji zoneless i głębszej integracji SSR. Choć formularze oparte na sygnałach i komponenty bez selektora wciąż są w fazie rozwoju, migracja do Angular 20 przebiega płynnie.

Nie przegap również mniejszych wydań między Angular 19 a 20:

  • Angular 19.1 – HMR dla szablonów, subPath w aplikacjach wielojęzycznych i inne
  • Angular 19.2 – eksperymentalne httpResource, literały szablonowe bez tagów w wyrażeniach i więcej

Która funkcja Angulara 20 jest Twoją ulubioną? Podziel się swoimi spostrzeżeniami poniżej!

Podziel się artykułem

Zapisz się na nasz newsletter

Dołącz do community Angular.love i bądź na bieżąco z trendami.