20 lis 2024
13 min

Angular 19 – co nowego?

Witaj w Angular 19! To najnowsze wydanie wprowadza szereg nowych funkcji i ulepszeń, mających na celu uproszczenie procesu tworzenia aplikacji i poprawę wydajności. Od innowacyjnych reaktywnych prymitywów, takich jak linkedSignal i Resource API, po eksperymentalną funkcję inkrementalnej hydratacji i ulepszenia w Angular Language Service – Angular 19 jest pełne narzędzi, które sprawią, że Twoje aplikacje będą szybsze i bardziej wydajne. W naszym szczegółowym przeglądzie odkryjesz wszystkie ekscytujące nowości i dowiesz się, jak mogą one podnieść Twoje projekty na wyższy poziom.

Nowy (eksperymentalny) reaktywny prymityw: linkedSignal 

LinkedSignal to writable signal, który reaguje na zmiany w sygnale źródłowym i może resetować się na podstawie obliczonej wartości.

export declare function linkedSignal<S, D>(options: {
    source: () => S;
    computation: (source: NoInfer<S>, previous?: {
        source: NoInfer<S>;
        value: NoInfer<D>;
    }) => D;
    equal?: ValueEqualityFn<NoInfer<D>>;
}): WritableSignal<D>;

Początkowa wartość sygnału jest obliczana za pomocą funkcji 'computation’, a następnie wartość sygnału może zostać zmieniona ręcznie za pomocą metody 'set’. Jednak gdy wartość sygnału 'source’ ulegnie zmianie, wartość linked sygnału zostanie ponownie obliczona za pomocą metody 'computation’. 

Spójrzmy na przykład, który powinien to rozjaśnić:

protected readonly colorOptions = signal<Color[]>([{
    id: 1,
    name: 'Red',
  }, {
    id: 2,
    name: 'Green',
  }, {
    id: 3,
    name: 'Blue',
  }]);

  protected favoriteColorId = linkedSignal<Color[], number | null>({
    source: this.colorOptions,
    computation: (source, previous) => {
      if(previous?.value) {
        return source.some(color => color.id === previous.value) ? previous.value : null;
      }
      return null;
    }
  });

  protected onFavoriteColorChange(colorId: number): void {
    this.favoriteColorId.set(colorId);
  }

  protected changeColorOptions(): void {
    this.colorOptions.set([
      {
        id: 1,
        name: 'Red',
      },
      {
        id: 4,
        name: 'Yellow',
      },
      {
        id: 5,
        name: 'Orange',
      }
    ])
  }}

Mamy sygnał `colorOptions`, który przechowuje listę kolorów do wyboru przez użytkownika (każdy z nich ma ID i nazwę). Mamy również linked sygnał o nazwie `favoriteColorId`, który reprezentuje wybrany przez użytkownika kolor z listy. Początkowa wartość tego sygnału to wynik funkcji `computation`, która początkowo będzie miała wartość `null` (ponieważ poprzedni stan powiązanego sygnału nie jest zdefiniowany). Ten linked sygnał, podobnie jak każdy inny writable sygnał, udostępnia metodę `set`, która pozwala ustawić ID wybranego koloru użytkownika (zobacz funkcję `onFavoriteColorChange`).

Załóżmy, że po wybraniu koloru z jakiegoś powodu lista dostępnych kolorów zostaje zmieniona (zobacz metodę `changeColorOptions`). W wyniku zmiany wartości sygnału `colorOptions` wartość linked sygnału `favoriteColorId` zostanie ponownie obliczona za pomocą metody `computation`. W powyższym przykładzie, jeśli wybrany kolor nadal znajduje się na nowej liście dostępnych kolorów, wartość sygnału pozostaje bez zmian. Z drugiej strony, jeśli wcześniej wybrany kolor nie znajduje się na nowej liście, wartość sygnału zostaje ustawiona na `null`.

Nowe (eksperymentalne) API: resource – zasób

Angular wprowadza eksperymentalne API o nazwie `resource()`, zaprojektowane do zarządzania operacjami asynchronicznymi. Posiada wbudowane mechanizmy zapobiegające race conditions, śledzenia stanu ładowania, obsługi błędów, ręcznego aktualizowania wartości oraz wyzwalania pobierania danych ręcznie, gdy zajdzie taka potrzeba.

Poniżej przykład użycia `resource`: 

  fruitId = signal<string>('apple-id-1');

  fruitDetails = resource({
    request: this.fruitId,
    loader: async (params) => {
      const fruitId = params.request;
      const response = await fetch(`https://api.example.com/fruit/${fruitId}`, {signal: params.abortSignal});
      return await response.json() as Fruit;
    }
  });

  protected isFruitLoading = this.fruitDetails.isLoading;
  protected fruit = this.fruitDetails.value;
  protected error = this.fruitDetails.error;


  protected updateFruit(name: string): void {
    this.fruitDetails.update((fruit) => (fruit ? {
      ...fruit,
      name,
    } : undefined))
  }

  protected reloadFruit(): void {
    this.fruitDetails.reload();
  }

  protected onFruitIdChange(fruitId: string): void {
    this.fruitId.set(fruitId);
  }

Zacznijmy od deklaracji zasobu. Opcjonalny parametr `request` przyjmuje sygnał wejściowy, do którego powiązany jest asynchroniczny zasób (w naszym przykładzie jest to `fruitId`, ale równie dobrze może to być sygnał obliczeniowy składający się z wielu wartości). Definiujemy również funkcję `loader`, która asynchronicznie pobiera dane (funkcja powinna zwracać `Promise`). Utworzony zasób o nazwie `fruitDetails` umożliwia nam m.in.:

  • dostęp do bieżącej wartości sygnału (który również zwraca `undefined`, gdy zasób nie jest dostępny w danym momencie),
  • dostęp do sygnału statusu (jeden z następujących: `idle`, `error`, `loading`, `reloading`, `resolved`, `local`),
  • dostęp do dodatkowych sygnałów, takich jak `isLoading` lub `error`,
  • ponowne wywołanie funkcji `loader` (za pomocą metody `reload`),
  • aktualizowanie lokalnego stanu zasobu (za pomocą metody `update`).

Zasób będzie automatycznie przeładowany, jeśli zmieni się sygnał `request` (w naszym przypadku `fruitId`). Funkcja `loader` jest również wywoływana, gdy zasób jest tworzony po raz pierwszy.

A co z interoperacyjnością z RxJS? Angular udostępnia również odpowiednik metody `resource` w RxJS, o nazwie `rxResource`. W tym przypadku metoda `loader` zwraca `Observable`, ale wszystkie pozostałe właściwości pozostają sygnałami.

  fruitDetails = rxResource({
    request: this.fruitId,
    loader: (params) => this.httpClient.get<Fruit>(`https://api.example.com/fruit/${params.request}`)
  })

Aktualizacje funkcji effect()

W najnowszej wersji Angulara, wersji 19, funkcja `effect()` otrzymała kluczowe aktualizacje, oparte na obszernym feedbacku społeczności.

Istotną zmianą jest usunięcie flagi `allowSignalWrites`. Początkowo flaga ta miała na celu ograniczenie możliwości ustawiania sygnałów wewnątrz `effect()`, zachęcając deweloperów do używania `computed()` w określonych scenariuszach. Jednak okazało się, że to ograniczenie częściej stanowiło przeszkodę niż pomoc, uniemożliwiając efektywne korzystanie z `effect()` tam, gdzie miało to sens. W odpowiedzi na to, Angular 19 pozwala na ustawianie sygnałów domyślnie wewnątrz `effect()`, eliminując niepotrzebną złożoność i koncentrując się na lepszych sposobach wspierania dobrych praktyk programistycznych (zobacz `linkedSignal`, API `Resource`).

effect(
   () => {
       console.log(this.users());
   },
   //Ta flaga zostanie usunięta w nowej wersji
   { allowSignalWrites: true }
);

Dodatkowo, nastąpiła istotna zmiana w czasie wykonywania efektów. Porzucono poprzednie podejście, w którym efekty były kolejkowane jako mikrozadania, a teraz będą one wykonywane jako część cyklu detekcji zmian w hierarchii komponentów. Ta zmiana ma na celu naprawienie problemów związanych z tym, że efekty uruchamiały się zbyt wcześnie lub zbyt późno, zapewniając bardziej logiczną kolejność wykonywania, zgodną z drzewem komponentów.  

Te usprawnienia mają na celu poprawę zarówno funkcjonalności, jak i użyteczności funkcji `effect()`, sprawiając, że stanie się ona bardziej dostosowana do potrzeb programistów. Choć funkcja `effect()` nadal będzie dostępna w fazie developer preview w wersji 19, umożliwia to dalsze udoskonalenia na podstawie doświadczeń deweloperów z nowymi funkcjami.

Nowa funkcja porównująca w interoperacyjności z rxjs

Funkcja `toSignal` w Angularze została ulepszona, aby wspierać niestandardową funkcję porównywania wartości, dając programistom większą kontrolę nad tym, jak porównania wartości wyzwalają aktualizacje. Wcześniej `toSignal` działało z podstawowym sprawdzeniem równości, nie dając programistom elastyczności w definiowaniu, co stanowi równość w ich specyficznych przypadkach. Często prowadziło to do niepotrzebnych aktualizacji komponentów.  

W najnowszej aktualizacji programiści mogą teraz określić niestandardową funkcję porównania wartości, która decyduje, kiedy aktualizacje powinny się odbywać, optymalizując wydajność, zapewniając, że aktualizacje są wywoływane tylko przez istotne zmiany danych. Ta nowa funkcjonalność nie tylko umożliwia dostosowane porównania wartości, ale również standaryzuje stosowanie sprawdzania równości tam, gdzie wcześniej było ono nieobecne, czyniąc zachowanie sygnałów bardziej przewidywalnym i efektywnym.

// Stwórz Subject, aby emitować wartości tablicy
const arraySubject$ = new Subject<number[]>();


// Zdefiniuj własną funkcję porównującą, aby porównać tablice według ich zawartości
const arraysAreEqual = (a: number[], b: number[]): boolean => {
   return a.length === b.length && a.every((value, index) => value === b[index]);
};


// Przekonwertuj Subject na sygnał z niestandardową funkcją porównującą
const arraySignal = toSignal(arraySubject$, {
   initialValue: [1, 2, 3],
   equals: arraysAreEqual, // Niestandardowa funkcja porównująca tablice
});

Nowa funkcja afterRenderEffect

Funkcja `afterRenderEffect` w Angularze jest eksperymentalnym API zaprojektowanym do obsługi efektów ubocznych, które powinny wystąpić dopiero po zakończeniu renderowania komponentu. Efekt jest uruchamiany po każdym cyklu renderowania, jeśli jego zależności ulegną zmianie, umożliwiając programistom reagowanie na zmiany stanu dopiero po zaktualizowaniu DOM-u.  

W przeciwieństwie do `afterRender` i `afterNextRender`, ten efekt śledzi określone zależności i ponownie je wykonuje po każdym cyklu renderowania, gdy tylko ulegną zmianie, co sprawia, że jest idealny do zadań bieżących, następujących po renderowaniu związanych z reaktywnymi danymi.  

`afterRender` i `afterNextRender` nie śledzą żadnych zależności i zawsze planują wywołanie funkcji zwrotnej po cyklu renderowania.

counter = signal(0);

  constructor() {
    afterRenderEffect(() => {
      console.log('after render effect', this.counter());
    })

    afterRender(() => {
      console.log('after render', this.counter())
    })
  }

W podanym przykładzie, funkcja zwrotna `afterRender` zostanie wykonana po każdym cyklu renderowania. Z kolei funkcja zwrotna `afterRenderEffect` zostanie wykonana po cyklach renderowania tylko wtedy, gdy wartość sygnału `counter` ulegnie zmianie.

Nowa składnia zmiennej szablonowej @let

Angular wprowadził składnię `@let` w wersji 18.1, a w wersji 19.0 uczynił ją stabilną. Ta nowa funkcjonalność upraszcza proces definiowania i ponownego używania zmiennych w szablonach. Dodanie tej funkcji odpowiada na istotne żądanie społeczności, umożliwiając programistom przechowywanie wyników wyrażeń bez konieczności stosowania wcześniejszych obejść, które były mniej ergonomiczne.

Oto jak można wykorzystać składnię `@let` w swoich szablonach Angular:

@let userName = 'Jane Doe';
<h1>Welcome, {{ userName }}</h1>


<input #userInput type="text">
@let greeting = 'Hello, ' + userInput.value;
<p>{{ greeting }}</p>


@let userData = userObservable$ | async;
<div>User details: {{ userData.name }}</div>

`@let` umożliwia definiowanie zmiennych bezpośrednio w szablonie, które następnie mogą być używane w całym tym szablonie. Należy pamiętać, że zmienne zdefiniowane za pomocą `@let` są tylko do odczytu i mają zakres ograniczony do bieżącego szablonu oraz jego potomków – nie można ich ponownie przypisać ani uzyskać dostępu z komponentów nadrzędnych czy rodzeństwa. Ta ograniczona i niemutowalna natura zmiennych zapewnia, że szablony pozostają przewidywalne i łatwiejsze do debugowania.

Eksperymentalna inkrementalna hydratacja 

Po Deferrable Views w wersji 17 i replayowaniu eventów w wersji 18, zespół Angulara prezentuje podgląd funkcjonalności **Incremental Hydration** (nowy sposób na hydratację części aplikacji na żądanie).

Aby to włączyć, należy dodać odpowiednią konfigurację do aplikacji:

export const appConfig: ApplicationConfig = {
  providers: [
    provideClientHydration(
      withIncrementalHydration()
    )
    ...
  ]
};

Implementacja inkrementalnej hydratacji opiera się na bloku `defer`. Aby z niego skorzystać, musimy dodać nowy wyzwalacz `hydrate` do tego bloku.

@defer (hydrate on hover) {
 <app-hydrated-cmp />
}

Obsługiwane typy wyzwalaczy incremental hydration to:

  • `idle`
  • `interaction`  
  • `immediate`  
  • `timer(ms)`  
  • `hover`  
  • `viewport`  
  • `never` (komponent pozostanie “odwodniony” na stałe)  
  • `when {{ condition }}`

Umożliwienie częściom aplikacji renderowanej po stronie serwera selektywnej rehydratacji po stronie klienta poprawia czasy ładowania i interaktywność, aktywując początkowo tylko te komponenty, które są rzeczywiście potrzebne.

Nowy input routerOutletData dla RouterOutlet

W Angularze 19 dodano nowy input `routerOutletData` do `RouterOutlet`, który zapewnia uproszczony sposób przesyłania danych z komponentów nadrzędnych do komponentów podrzędnych routowanych przez outlet. Gdy `routerOutletData` jest ustawione, powiązane dane stają się dostępne w komponentach podrzędnych za pośrednictwem tokena `ROUTER_OUTLET_DATA`, który używa typu Signal. Takie podejście umożliwia dynamiczne aktualizacje, zapewniając, że zmiany w danych wejściowych automatycznie odzwierciedlają się w komponencie podrzędnym, eliminując potrzebę statycznych przypisań.

Komponent nadrzędny:

<router-outlet [routerOutletData]="routerOutletData()" />

Komponent podrzędny zroutowany poprzez outlet:

export class ChildComponent {
  readonly routerOutletData: Signal<MyType> = inject(ROUTER_OUTLET_DATA);
}

Od wersji 18.1 dyrektywa `RouterLink` przyjmuje również obiekt typu `UrlTree` jako input.

<a [routerLink]="homeUrlTree">Home</a>

W ten sposób wszystkie dodatkowe opcje (takie jak query parametry, strategia obsługi query parametrów, `relativeTo` itp.) mogą być przekazywane bezpośrednio w obiekcie `UrlTree`.

Podczas próby przekazania obiektu `UrlTree` przez input `routerLink` przy jednoczesnym użyciu innych inputów, takich jak `queryParams` czy `fragment`, Angular zgłosi błąd, wyjaśniając, że nie jest to dozwolone:

'Cannot configure queryParams or fragment when using a UrlTree as the routerLink input value.'

Domyślna strategia obsługi query parametrów

Teraz można ustawić domyślną strategię obsługi parametrów zapytania dla wszystkich ścieżek bezpośrednio w konfiguracji `provideRouter()`.

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes, withRouterConfig({defaultQueryParamsHandling: 'preserve'}))
  ]
};

Podczas gdy domyślną strategią Angulara jest strategia `replace`, można teraz wybrać `preserve` lub `merge`. Przedtem strategię można było ustawiać indywidualnie dla każdej nawigacji, za pomocą opcji `RouterLink` lub `router.navigate`.

Domyślnie Standalone

Wraz z wydaniem Angulara w wersji 19, `standalone: true` stanie się domyślnym ustawieniem dla komponentów, dyrektyw i rur (pipes).

Poniższy komponent będzie uznawany za samodzielny (standalone):

@Component({
  imports: [],
  selector: 'home',
  template: './home-component.html',
  // standalone w Angular 19!
})
export class HomeComponent {…}

Dla komponentów, które nie są samodzielne (non-standalone), należy podać flagę:

@Component({
  selector: 'home',
  template: './home-component.html',
  standalone: false
  // non-standalone w Angular 19!
})
export class HomeComponent {…}

To oznacza istotną ewolucję od wprowadzenia możliwości standalone komponentów w wersji 14. Ta zmiana upraszcza framework Angular, czyniąc go bardziej dostępnym dla nowych programistów oraz poprawiając takie funkcje jak lazy loading i kompozycja komponentów. Dla istniejących projektów, automatyczna migracja podczas aktualizacji za pomocą `ng update` dostosuje ustawienia flagi `standalone`, zapewniając kompatybilność i ułatwiając płynne przejście do nowych domyślnych ustawień.

Nowe migracje dla API standalone i wstrzykiwania zależności

Angular wprowadził opcjonalną migrację, która poprawia wstrzykiwanie zależności, przechodząc z wstrzykiwania przez konstruktor na używanie funkcji `inject()`.

ng g @angular/core:inject

Ta migracja upraszcza kod, zastępując składnię konstruktora:

constructor(private productService: ProductService) {}

bardziej uproszczonym podejściem:

private productService = inject(ProductService);

Po migracji możesz napotkać problemy z kompilacją, szczególnie w testach, gdzie instancje są tworzone bezpośrednio. Narzędzie migracji oferuje kilka opcji dostosowania procesu, takich jak obsługa klas abstrakcyjnych, utrzymanie konstruktorów zgodnych wstecznie oraz zarządzanie ustawieniami nullable, co zapewnia płynne przejście bez psucia istniejącego kodu.

Dodatkowo, osobna migracja umożliwia lazy loading standalone komponentów w konfiguracjach routingu, przekształcając bezpośrednie odniesienia do komponentów w dynamiczne importy, co optymalizuje wydajność, ładując komponenty tylko wtedy, gdy są potrzebne.

ng g @angular/core:route-lazy-loading

Bezpośrednie odniesienie przed migracją:

{
  path: 'products',
  component: ProductsComponent
}

Dynamiczny import (lazy loading) po migracji:

{
  path: 'products',
  loadComponent: () => import('./products/products.component').then(m => m.ProductsComponent)
}

Funkcje dostawców inicjalizatorów (Initializer provider functions)

W Angularze v19 wprowadzono nowe funkcje pomocnicze:

  • `provideAppInitializer`
  • `provideEnvironmentInitializer`
  • `providePlatformInitializer`

Te funkcje upraszczają proces konfiguracji inicjalizatorów, oferując czystszą alternatywę dla tradycyjnych tokenów `APP_INITIALIZER`, `ENVIRONMENT_INITIALIZER` i `PLATFORM_INITIALIZER`. Działają one jako „cukier składniowy”, “osładzając” programistom konfigurowanie inicjalizatorów na poziomie aplikacji, środowiska i platformy w bardziej czytelny i prostszy sposób.

export const appConfig: ApplicationConfig = {
  providers: [
    provideAppInitializer(() => {
      console.log('app initialized');
    })
  ]
};

Dodatkowo, Angular v19 zawiera narzędzie migracyjne, które pomaga przejść z istniejących inicjalizatorów do nowego formatu, ułatwiając przyjęcie zaktualizowanego podejścia bez konieczności ręcznego refaktoryzowania kodu.

Automatyczne flush() w fakeAsync

W Angularze v19 funkcja `flush()` w testach `fakeAsync()` jest wykonywana automatycznie na zakończenie testu. Wcześniej programiści musieli ręcznie wywołać `flush()` lub `discardPeriodicTasks()`, aby usunąć zaległe asynchroniczne zadania, w przeciwnym razie pojawiał się błąd dotyczący pozostałych okresowych timerów. Ta ręczna czynność została usunięta w nowszej wersji, co upraszcza kod testów i zapobiega powszechnym błędom związanym z czyszczeniem zadań.

it('async test description', fakeAsync(() => {
  // ...
  flush(); // not needed in Angular 19!
}));

Nowe narzędzia diagnostyczne angulara

Angular’s Extended Diagnostics to zaawansowane, działające w czasie rzeczywistym kontrole kodu, które identyfikują potencjalne problemy i poprawiają jakość kodu podczas jego tworzenia. Wykraczają one poza standardowe błędy i ostrzeżenia, wykrywając subtelne problemy, takie jak nieużywane funkcje, brakujące importy oraz inne naruszenia najlepszych praktyk, pomagając programistom wczesnym etapie wychwytywać błędy i utrzymywać aplikacje Angulara w czystości oraz efektywności.

W Angularze 19 dostępne są dwa nowe narzędzia diagnostyczne:

  •  **Niewywołane funkcje** – wskazuje przypadki, w których funkcja jest używana w event-bindingu, ale nie jest wywoływana, często z powodu brakujących nawiasów w szablonie. Aby to naprawić, upewnij się, że funkcje w event-bindingu są zakończone nawiasami, dzięki czemu będą poprawnie wykonywane, a nie traktowane jako właściwości.
  •  **Nieużywane importy standalone komponentów** – identyfikuje przypadki, w których standalone komponenty, dyrektywy lub rury (pipes) są importowane, ale nie są wykorzystywane w module lub komponencie. Zwykle ma to miejsce, gdy te standalone elementy są dodane do tablicy `imports`, ale nie są odniesione w szablonie ani kodzie. Aby rozwiązać ten problem, upewnij się, że wszystkie importowane samodzielne komponenty, dyrektywy lub rury są aktywnie używane w aplikacji; w przeciwnym razie rozważ usunięcie nieużywanych importów, aby utrzymać czysty i efektywniejszy kod.

Flaga strictStandalone

Angular wprowadził flagę `strictStandalone` w opcjach kompilatora `angularCompilerOptions`, aby wymusić używanie komponentów, dyrektyw i rur jako samodzielnych (standalone). Domyślnie `strictStandalone` jest ustawione na `false`, więc brak wymuszenia, chyba że flagę tę aktywuje się ręcznie.

Łącząc to z faktem, że od wersji 19 wszystkie komponenty są domyślnie samodzielne (standalone), wynik jest taki, że ta flaga zabrania posiadania komponentów, dyrektyw i rur, które są jawnie oznaczone jako **niesamodzielne** (non-standalone).

✘ [ERROR] TS-992023: Only standalone components/directives are allowed when 'strictStandalone' is enabled. [plugin angular-compiler]

Wsparcie Playwright w Angular CLI 

Po uruchomieniu polecenia CLI `ng e2e` bez skonfigurowanego celu e2e w projekcie, pojawi się zapytanie, którego pakietu e2e chciałbyś użyć. Począwszy od wersji 19, jednym z dostępnych opcji będzie **Playwright**. Pod spodem jest to schematic stworzony przez społeczność, który może być również wywołany bezpośrednio (nawet dla projektów Angular poniżej wersji 19) za pomocą następującego polecenia:

ng add playwright-ng-schematics

Wsparcie Typescript

Angular v18.1 dodał wsparcie dla wersji TypeScript 5.5. Od wersji v19.0, wsparcie obejmuje wersję 5.6, podczas gdy wsparcie dla wersji starszych niż 5.5 zostało porzucone. Poniżej znajdują się wyróżnione funkcje:

– **Wnioskowane predykaty typów (Inferred Type Predicates)** – TypeScript automatycznie wnioskuje predykaty typów i zawęża typy w miejscach, gdzie wcześniej musieliśmy wyraźnie zdefiniować predykaty.

const availableProducts = productIds
  .map(id => productCatalog.get(id))
  .filter(product => product !== undefined);
/*  TypeScript teraz wie, że availableProducts nie są już potencjalnie undefined */
availableProducts.forEach(product => product.displayDetails());

Zawężanie przepływu sterowania dla stałych dostępów indeksowanych (Control Flow Narrowing for Constant Indexed Accesses) – TypeScript teraz może zawężać wyrażenia takie jak `obj[key]`, gdy zarówno `obj`, jak i `key` są praktycznie stałymi.

function logUpperCase(key: string, dictionary:Record<string, unknown>): void {
   if(typeof dictionary[key] === 'string') {
        /* valid since ts 5.5 */
        console.log(dictionary[key].toUpperCase());
  }
}

Zabronione warunki nullish i truthy – TypeScript zgłosi błąd, gdy warunki truthy lub nullish zawsze będą oceniane na `true` (co w sensie składni JavaScript jest poprawne, ale zwykle oznacza błąd logiczny). Poniższe przykłady spowodują błąd:

if(/^[a-z]+$/) {
  /* missing .test(value) call, regex itself is always truthy  */ 
}

if (x => 0) {
    /* "x => 0" is an arrow function, always truthy */ 
}

Wsparcie dla izolowanych modułów TypeScript

Angular 18.2 wprowadził wsparcie dla opcji isolatedModules w TypeScript, co umożliwia przyspieszenie czasu budowy produkcyjnej o nawet 10%, pozwalając na transpilację kodu przez bundler. To optymalizuje konstrukty TypeScript i redukuje liczbę przejść opartych na Babelu.

Aby włączyć wsparcie dla isolatedModules w projekcie Angular, zaktualizuj swoją konfigurację TypeScript (plik tsconfig.json) w następujący sposób:

"compilerOptions": { 
... 
"isolatedModules": true
}

Wprowadza to kilka dodatkowych ograniczeń, takich jak brak wnioskowania typów między plikami, umożliwiając eksport tylko stałych enumeracji (`const enums`) oraz wymuszając jawne deklaracje eksportów typu (z użyciem składni `import type`).

Bez `isolatedModules` pełna kontrola typów jest wykonywana dla całej bazy kodu podczas procesu kompilacji. W przeciwieństwie do tego, gdy `isolatedModules` jest włączone, każdy plik jest kompilowany niezależnie, a pewne analizy typów między plikami są pomijane.

Ulepszenia Angular Language Service

Najnowsza wersja Angular Language Service wspiera najnowsze funkcje, takie jak:

  • Diagnostyka Angulara dla nieużywanych importów samodzielnych (standalone imports),
  • Migracja z `@input` do `signal-input`,
  • Migracja do zapytań typu signal (`signal queries`),
  • Autouzupełnianie w szablonach dla wszystkich dyrektyw, które nie zostały jeszcze zaimportowane.

Można również spodziewać się przydatnych schematów refaktoryzacji zintegrowanych z wybranym środowiskiem IDE.

Server Route Configuration

Angular wprowadza nowe Server Route Configuration API, aby zwiększyć elastyczność w hybrydowym renderowaniu. Dzięki temu deweloperzy mogą definiować, w jaki sposób określone ścieżki powinny być renderowane — na serwerze, jako pre-rendered lub na kliencie. Nowa konfiguracja ułatwia optymalizację wydajności poprzez wybór najbardziej odpowiedniego trybu renderowania dla każdej trasy.
Oto przykład, jak będzie wyglądała konfiguracja tras serwera:

import {RenderMode, ServerRoute} from '@angular/ssr';

export const serverRouteConfig: ServerRoute[] = [
  { path: '/login', renderMode: RenderMode.Server },
  { path: '/fruits', renderMode: RenderMode.Prerender },
  { path: '/**', renderMode: RenderMode.Client }
];
W tej konfiguracji:
  • Ścieżka /login będzie korzystać z renderowania po stronie serwera (SSR), co zapewni renderowanie najnowszych danych przy każdym żądaniu.
  • Ścieżka /fruits jest ustawiona na generowanie statycznej witryny (SSG), co oznacza, że treść zostanie wygenerowana podczas procesu budowania, co przyspieszy ładowanie.
  • Wszystkie pozostałe ścieżki domyślnie korzystają z renderowania po stronie klienta (CSR).
Proponowane rozwiązanie umożliwi także definiowanie funkcji do rozwiązywania parametrów ścieżki w dynamicznych trasach w trybie prerenderowania:
export const serverRouteConfig2: ServerRoute[] = [
  {
    path: '/fruit/:id',
    renderMode: RenderMode.Prerender,
    async getPrerenderParams() {
      const fruitService = inject(FruitService);
      const fruitIds = await fruitService.getAllFruitIds();
      return fruitIds.map(id => ({id}));
    },
  },
];

Podsumowanie

Angular 19 wprowadza szereg potężnych aktualizacji mających na celu poprawę wydajności aplikacji, uproszczenie reaktywności i zwiększenie kontroli dewelopera. Ta wersja zawiera bardziej intuicyjne zarządzanie stanem, czystsze opcje konfiguracji oraz ulepszenia, które sprawiają, że Angular jest szybszy i łatwiejszy w użyciu. Dodatkowo, z ekscytującymi funkcjami eksperymentalnymi, takimi jak Incremental Hydration i Server Route Configuration, Angular nadal się rozwija, obiecując jeszcze większą elastyczność i wydajność w przyszłych wersjach.

Chętnie poznamy Twoje zdanie – daj nam znać, jak te aktualizacje pasują do Twoich potrzeb deweloperskich i co sądzisz o kierunku, w którym zmierza Angular!

Podziel się artykułem

Zapisz się na nasz newsletter

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