20 sie 2025
6 min

Angular 20.2 – ostatnie zmiany

Angular właśnie doczekał się aktualizacji, a wraz z nią pojawiło się sporo nowych funkcjonalności wartych uwagi. Przyjrzyjmy się im razem.

Zmiana statusu Zoneless Angulara-  jest teraz stabilny!

Czekaliśmy na to od dawna i w końcu się doczekaliśmy. W najnowszej wersji Angulara tryb Zoneless jest już stabilny, więc śmiało można go używać w swoich projektach. Temat Zoneless w Angularze jest jednak dość obszerny, dlatego w tym artykule skupimy się na innych, świeżych zmianach. Jeśli chcesz dowiedzieć się, jak to działa, kliknij tutaj.

Lepsze wsparcie dla TypeScript (5.9).

W najnowszej wersji Angulara możemy w pełni korzystać z możliwości TypeScript 5.9, który jest teraz w pełni wspierany. Otwiera to drzwi do wielu nowoczesnych usprawnień języka, które mogą poprawić zarówno wydajność, jak i komfort pracy programistów.

Na przykład, teraz możliwe jest zaimportowanie modułu bez natychmiastowego wywołania jego wykonania:

import defer * as myFeature from "./common-module.js"
//no side effect//

//Here code is executed//
const value = myFeature.count();

Warto również wspomnieć, że TypeScript 5.9 wprowadza niewielkie, ale istotne usprawnienie — możliwość podglądu pełnych typów interfejsów w podpowiedziach edytora oraz podczas inspekcji po najechaniu kursorem. To ulepszenie znacząco poprawia komfort pracy programisty, ułatwiając zrozumienie złożonych typów bez konieczności przeskakiwania między wieloma definicjami. Spójrz na poniższy przykład:

Więcej informacji o nowej wersji TypeScript można znaleźć tutaj.

Ulepszone wsparcie dla właściwości redirect.

Na początek wyjaśnijmy, w czym ta zmiana może nam pomóc. Przede wszystkim umożliwia ona dodanie własnej logiki opartej na tym czy żądanie zostało przekierowane bądź też nie. Zobacz poniższy przykład:

export class RedirectServiceExample {
  private readonly _httpClient = inject(HttpClient);
  private readonly _userHandler = inject(UserHandler);
 
  getUser(Id: string) {
    return this._httpClient.get(`/api/users/${Id}`, { observe: 'response'     }).pipe(
      map((response: HttpResponse<any>) => {
        if (response.redirected) 
          return this._userHandler.processRedirectedUser(id)
        else return this._userHandler.processUser(response.body)
      })
    );
  }
}

Widzimy, że nasza logika jest dodawana warunkowo, co daje nam pełną kontrolę nad tym, jak w tej sytuacji obsługiwane są żądania. Oznacza to, że w zależności od tego, czy odpowiedź została przekierowana, możemy zastosować różne strategie obsługi –  na przykład rejestrowanie przekierowania do celów analitycznych, wdrożenie dodatkowych kontroli bezpieczeństwa lub modyfikację przebiegu działania aplikacji.

Po drugie, zmiana ta poprawia spójność z natywnym Fetch API. Dzięki udostępnieniu właściwości redirected obiekt HttpClient zachowuje się teraz bardziej jak Fetch API, oferując programistom znajomy i przewidywalny interfejs.

Co więcej, nowa funkcjonalność pozwala lepiej zadbać o bezpieczeństwo naszych aplikacji. Prosty przykład: możemy blokować nieautoryzowane przekierowania lub wprowadzać dodatkową weryfikację w momencie, gdy w aplikacji wykryte zostanie przekierowanie. Jest to bez wątpienia przydatne w kontekście polityk między domenowych (cross-origin).

Dane analityczne w kontekście właściwości redirected są istotne, ponieważ pozwalają śledzić, jak często i w jakich okolicznościach następuje przekierowanie w aplikacji. Pomaga to ocenić wydajność, stabilność oraz zgodność przebiegu żądań z zamierzoną architekturą.

Nowe API animacji

Dzięki nowemu API animacji możemy uniknąć importowania modułu animacji, co ma istotny wpływ na naszą aplikację. Przede wszystkim może to zmniejszyć ostateczny rozmiar aplikacji. Ponadto łatwiej jest obsługiwać animacje, korzystając z czystych plików stylów, a nie z bibliotek specyficznych dla Angulara.

Kolejną zmianą wartą uwagi jest wprowadzenie nowych funkcjonalności: leave i enter, które pozwalają animować elementy HTML bez konieczności importowania modułu animacji (jak wspomniano wyżej). Takie podejście ułatwia także tworzenie animacji osobom, które dopiero zaczynają swoją przygodę z Angularem. Zobacz, jak działa to w najnowszej aktualizacji:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss'
})
export class AppComponent {
  private readonly _menuOpen = signal(false);
  protected readonly isMenuOpen = computed(() => this._menuOpen());


  protected toggleMenu() {
    this._menuOpen.update(value => !value);
  }


  protected closeMenu() {
    this._menuOpen.set(false);
  }
}

Plik html:

<button (click)="toggleMenu()">Toggle Menu</button>


@if (isMenuOpen()) {
  <div
    animate.enter="animate-in"
    animate.leave="animate-out"
    (click)="closeMenu()"
  >
    <!-- Your content -->
    <p>Click to close</p>
  </div>
}

I finalnie nasze style:

@keyframes animate-in {
  from {
    transform: translateY(50%);
  }
  to {
    transform: translateY(0);
  }
}


.animate-in {
  animation: animate-in 0.35s ease-in-out;
}


@keyframes animate-out {
  from {
    transform: translateY(0);
  }
  to {
    transform: translateY(50%);
  }
}


.animate-out {
  animation: animate-out 0.35s ease-in-out;
}


Moim zdaniem jest to lepsze rozwiązanie pod kątem doświadczenia programisty. Zobacz ten przykład z użyciem animationModules:
<h2>Old Angular Animations</h2>
<button (click)="toggleMenu()">Toggle Menu</button>


@if (isMenuOpen()) {
  <div
    @menu
    (click)="closeMenu()"
  >
    <p>Click to close</p>
  </div>
}



@Component({
 /…/,
 animations: [
    trigger('menu', [
      transition(':enter', [
        style({ transform: 'translateY(120%)' }),
        animate('0.35s ease-in-out', style({ transform: 'translateY(0)' }))
      ]),
      transition(':leave', [
        animate('0.35s ease-in-out', style({ transform: 'translateY(120%)' }))
      ])
    ])
})
export class AppComponent {
  private readonly _menuOpen = signal(false);
  protected readonly isMenuOpen = computed(() => this._menuOpen());


  protected toggleMenu() {
    this._menuOpen.update(value => !value);
  }


  protected closeMenu() {
    this._menuOpen.set(false);
  }
}

Abstrahując od zastosowanych stylów inline, wyraźnie widać, że nowe funkcjonalności są przyjemniejsze w użyciu. Co więcej, dzięki najnowszym zmianom wydajność może ulec poprawie w przypadku aplikacji działających np. na urządzeniach mobilnych. Jak wiemy, niektóre animacje z biblioteki są wykonywane przy użyciu CPU zamiast GPU, co może negatywnie wpływać na wydajność aplikacji.

Większe możliwości stosowania @else if

Wraz z wprowadzeniem nowej wersji Angulara możemy używać słowa kluczowego as, aby wyodrębnić interesujące nas pola obiektu i używać ich bezpośrednio w templatce HTML. Teraz dotyczy to warunku @else if. Spójrz –  to naprawdę proste:

@Component({
 //…//
 template: `
    @if(user(); as user) {
      <p>{{user.name}}</p>
    } @else if(admin(); as admin) {
      <p>Hi admin {{admin.nickName}} </p>
    }
 `,
})
export class App {
  //…//
})

W tym bardzo prostym przypadku możemy po prostu odczytać typ użytkownika i, korzystając z zawężania typów, warunkowo wyświetlać różną zawartość w szablonie.

Rozszerzona diagnostyka dla niewywołanej funkcji

Przed wydaniem Angulara 20.2 nie było dodatkowego sprawdzenia w sytuacji, gdy funkcja była umieszczona wewnątrz interpolacji tekstu. Wcześniej taki mechanizm ostrzegania został zaimplementowany dla sygnałów:

@Component({
 template: `
   {{text}}
 `,
})
export class App {
 protected readonly text = signal<string>('Hello angular 20.2');
}

W takiej sytuacji następujący błąd jest wyświetlany:

[WARNING] NG8109: text is a function and should be invoked: text() [plugin angular-compiler]

Podobnie, wraz z wprowadzeniem Angulara 20.2 błąd jest wyświetlany gdy funkcja nie jest wywołana:

@Component({
  //…//
  template: `
    {{text}}
 `,
})
export class App {
  protected text(): string {
   return ‘Hello Angular 20.2’;
  }
}

Jednak mam nadzieję, że unikasz bezpośredniego wywoływania funkcji w szablonach. To dobrze znany antywzorzec w Angularze, który może prowadzić do niepotrzebnych cykli detekcji zmian i pogorszenia wydajności aplikacji. Zamiast tego warto rozważyć użycie getterów lub wcześniej obliczonych właściwości w klasie komponentu.

Zastąpienie metody getCurrentNavigation

Od Angulara 20.2 reaktywność naszej aplikacji nieco wzrosła. Teraz możemy wywołać currentNavigation() z instancji Router i obliczyć jej wartość, zamiast korzystać z metody getCurrentNavigation, która zwraca obiekt Navigation lub null. currentNavigation jest sygnałem, więc możemy wyciągnąć jego stan z użyciem computed. Pamiętaj, że od najnowszej aktualizacji metoda getCurrentNavigation jest przestarzała (deprecated).

@Component({
  //…//
})
export class App {
  private readonly _router = inject(Router);
  protected readonly navigated = computed(() =>
    this._router.currentNavigation()
  );
}

Zmodyfikowane API FormArray.

Od wydania Angular 20.2 API FormArray zostało zmienione. Wcześniej, jeśli chciałeś dodać tablicę kontrolerów do FormArray, musiałeś iterować po tablicy i w każdej iteracji dodawać element do FormArray:

export class App implements OnInit {
 protected formArray = new FormArray<FormControl<string | null>>([]);
 private readonly _controls = [
   new FormControl({ value: '', disabled: false }),
   new FormControl({ value: 'Name', disabled: false }),
 ];


 ngOnInit() {
   // before Angular 20.2 release
   this._controls.forEach((control) => {
     this.formArray.push(control);
   })
 }

Obecnie możesz modyfikować formArray w ten sposób:

 ngOnInit() {
   // after Angular 20.2 release
   this.formArray.push(this._controls);
 }

Gdy dodajesz elementy do FormArray w pętli, przy każdej iteracji wyzwalane jest osobne zdarzenie. W nowej metodzie zdarzenie jest wyzwalane tylko raz – po dodaniu wszystkich kontrolerów – co poprawia wydajność Angulara.

Udostępnienie opcji referrer i integrity dla httpResource

Z każdą nową wersją Angulara (zob. artykuł o Angular 20.1) funkcjonalności httpResource były stopniowo rozwijane, a w najnowszej wersji została rozszerzone o opcje referrer i integrity. Warto wspomnieć, dlaczego te możliwości są potrzebne i jak można je wykorzystać.

Dzięki opcji referrer możemy określić lub ukryć stronę źródłową, z której wykonywane jest zewnętrzne żądanie HTTP. Natomiast opcja integrity pozwala sprawdzić, czy dany zasób nie został zmodyfikowany – co ma istotny wpływ na bezpieczeństwo naszych aplikacji.

Poniżej znajduje się prosty przykład.

@Component({
  //…//
})
export class App {
   protected readonly resource = httpResource(() => ({
   //rest of settings//
   referrer: 'no-referrer',
   integrity: 'your_sha_key',
 }));
};

Dozwolone wiązanie z atrybutem ARIA.

Od najnowszej wersji Angulara możemy bezpośrednio wiązać atrybuty ARIA bez konieczności używania przedrostka attr.. Zmiana ta została wprowadzona nie tylko w celu uproszczenia pracy programistów, ale przede wszystkim w celu poprawy renderowania po stronie serwera.

Poniżej znajduje się przykład:

@Component({
 //…//
 template: `
   <button [ariaLabel]=”label”>Click me</button> //New way
   <button [attr.aria.label]=”label”>Click me</button> //Old way
 `,
})
export class App {
 protected readonly label = `New aria property binding`;
}

Podsumowanie

Moim zdaniem powinieneś rozważyć aktualizację wersji Angular w swoim projekcie do wersji 20.2. Ostatnie zmiany wprowadziły wiele usprawnień, które wpływają zarówno na wydajność aplikacji, jak i na komfort pracy dewelopera, co z pewnością warto odnotować. Aby zobaczyć, co zostało zmienione w poprzednich wersjach Angular, zachęcam do przeczytania tego artykułu:

Najczęściej zadawane pytania

Jak animować elementy w Angularze bez importowania modułu animacji?

W Angularze 20.2 możesz używać atrybutów animate.enter i animate.leave w szablonie, aby animować elementy HTML bez importowania modułu animacji. Wartości tych atrybutów to nazwy klas CSS, a animacje definiujesz zwykłymi @keyframes w plikach stylów. Takie rozwiązanie może zmniejszyć rozmiar aplikacji i jest wygodniejsze niż trigger() i transition() z animations w komponencie, zwłaszcza gdy animacje mają być wyzwalane przez @if.

Dlaczego Angular pokazuje ostrzeżenie NG8109 dla funkcji w interpolacji?

Ostrzeżenie NG8109 pojawia się, gdy w interpolacji {{ }} podasz funkcję lub sygnał bez wywołania, np. {{text}} zamiast {{text()}}. Od Angulara 20.2 kompilator zgłasza ten sam błąd także dla zwykłych metod, a wcześniej dotyczył tylko sygnałów. Sam błąd naprawisz, dodając nawiasy, jednak lepiej unikać wywoływania funkcji w szablonie, bo może to prowadzić do niepotrzebnych cykli detekcji zmian. Zamiast tego użyj gettera lub wcześniej obliczonej właściwości w klasie komponentu.

Jak dodać wiele kontrolek naraz do FormArray w Angularze?

Od Angulara 20.2 możesz przekazać całą tablicę kontrolek do metody push() obiektu FormArray zamiast dodawać je pojedynczo w pętli. Metoda push() przyjmuje wtedy tablicę kontrolek, a zdarzenie jest wyzwalane tylko raz, po dodaniu wszystkich elementów. Poprawia to wydajność, bo przy każdej iteracji pętli nie jest wyzwalane osobne zdarzenie. Przed tą zmianą trzeba było wywoływać push() dla każdego FormControl osobno.

Czym zastąpić metodę getCurrentNavigation w Angularze Router?

Zamiast przestarzałej metody getCurrentNavigation() użyj sygnału currentNavigation() z instancji Router, który zwraca obiekt Navigation lub null. Ponieważ jest to sygnał, możesz opakować go w computed() i reagować na zmiany stanu nawigacji w sposób reaktywny. Wywołanie inject(Router) w komponencie to standardowy sposób uzyskania dostępu do routera. Od Angulara 20.2 getCurrentNavigation jest oznaczona jako deprecated.

Jak bindować atrybuty ARIA w szablonie Angulara bez przedrostka attr.?

Od Angulara 20.2 możesz wiązać atrybuty ARIA bezpośrednio, np. [ariaLabel]='label', bez przedrostka attr., którego wymagała dotychczasowa składnia. Zmiana została wprowadzona przede wszystkim po to, aby poprawić renderowanie po stronie serwera, a nie tylko uprościć kod. Przykład z artykułu pokazuje, że wartość wiązania może pochodzić z właściwości komponentu, tak jak każde inne wiązanie w szablonie.

Jak wyodrębnić wartość z warunku @else if w szablonie Angulara?

Składnia as działa także w bloku @else if, dzięki czemu możesz od razu użyć wyodrębnionej wartości w szablonie. Przykład: @if (user(); as user) oraz @else if (admin(); as admin), gdzie każda gałąź otrzymuje własną zmienną. Dzięki zawężaniu typów w szablonie masz dostęp do pól obiektu, np. admin.nickName, bez dodatkowych wyrażeń w komponencie. Ta możliwość pojawiła się w Angularze 20.2.

Podziel się artykułem

Zapisz się na nasz newsletter

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