Kurs Angular · Moduł 6: Signals

Efekty - gdy na dym ma zareagować ktoś spoza murów

17 min czytania ·
W tej lekcji9

Dwa poprzednie postoje zamknęły cały obieg wieści wewnątrz murów Akademii. Sygnał trzyma wartość, skryba z computed() przelicza ją na tablicę, szablon pokazuje jedno i drugie., wszystko to dzieje się w jednym szczelnym obiegu: wartość wchodzi, wartość wychodzi, na końcu ktoś patrzy na mur.

Prędzej czy później trafisz jednak na zadanie, które w tym obiegu się nie mieści. Kronikarz Akademii ma dopisywać do księgi każdą zmianę liczby patroli na trakcie, żeby po latach dało się odtworzyć, co się działo. Wybrany przez ucznia motyw interfejsu ma przetrwać zamknięcie przeglądarki, więc trzeba go gdzieś odłożyć. Straż przy bramie ma wysłać gońca do sąsiedniego zamku, gdy alarm przekroczy próg. Mapa narysowana obcą biblioteką, która o Angularze nigdy nie słyszała, ma się przerysować po zmianie zaznaczonego szlaku.

Żadne z tych zadań nie jest wartością. Nie da się ich policzyć i zwrócić, bo one nie mają wyniku - one po prostu coś robią poza aplikacją: dopisują linijkę do dziennika, wpisują coś do pamięci przeglądarki, wysyłają zapytanie, budzą obcy kod. Programiści nazywają takie działania efektami ubocznymi, po angielsku side effects: to wszystko, co funkcja robi ponad zwrócenie wyniku.

Skryba z poprzedniej lekcji nadaje się do tego fatalnie i warto wiedzieć dlaczego, bo pokusa bywa duża. Po pierwsze, computed() musi coś zwrócić - to jest jego jedyne zadanie. Po drugie, i to jest powód rozstrzygający, skryba pracuje leniwie: na poprzednim postoju sprawdziliśmy, że funkcja podana do computed() nie wykonuje się ani razu, dopóki ktoś nie odczyta wyniku. Kronika prowadzona przez skrybę miałaby więc wpisy wyłącznie wtedy, gdy ktoś akurat na tablicę spojrzał, a w dniu, w którym nikt nie zajrzał na mur, księga zostałaby pusta. Potrzebujesz kogoś, kto rusza sam, bez pytania, i komu wolno wyjść za bramę.

Rozkaz dla kronikarza

Tym kimś jest efekt, a stawia się go funkcją effect() z pakietu @angular/core - tego samego, z którego bierzesz signal(), computed() i dekorator @Component. Podajesz jej jeden argument: funkcję, w której robisz to, co ma się wydarzyć - w najprostszej postaci nie przyjmuje ona żadnych parametrów, a jeden przydatny parametr poznasz pod koniec lekcji. Angular wykona ją sam, w dwóch różnych sytuacjach. Raz na starcie, zaraz po utworzeniu efektu, żeby dziennik od pierwszej chwili był zgodny ze stanem wież. A potem jeszcze raz po każdej zmianie któregokolwiek sygnału odczytanego w jej wnętrzu. Niczego nie zgłaszasz, niczego nie subskrybujesz, nie ma tu żadnej listy zależności do wypisania ręcznie - wystarczy, że w środku wywołasz sygnał nawiasami.

Zacznijmy od najmniejszego możliwego efektu, żeby zobaczyć sam kształt wywołania. Załóżmy, że masz gdzieś sygnał o nazwie count, a jedyne, czego chcesz, to wypisywać jego wartość w konsoli po każdej zmianie.

effect(() => console.log(count()));

Ta jedna linia jest całym efektem i nie brakuje w niej niczego. Zapamiętaj ją jako cztery kawałki układane zawsze w tej samej kolejności: nazwa effect, potem otwarcie wywołania razem ze strzałką bezargumentową, czyli (() =>, potem samo wyrażenie console.log(count()), a na końcu zamykający ). Ciało funkcji jest tu pojedynczym wyrażeniem, więc obyło się bez klamer - to ta sama skrócona postać funkcji strzałkowej, którą znasz z computed(). Zwróć uwagę, czego w tej linii nie ma. Nie ma nazwy sygnału podanej z boku jako zależność, nie ma słowa subscribe, nie ma żadnego this.detectChanges(). Angular dowie się, że efekt patrzy na count, wyłącznie stąd, że przy pierwszym przebiegu ktoś ten sygnał odczytał.

Jednej rzeczy ta linia jednak nie zniesie: nie wolno jej postawić gdzie popadnie. Efekt musi powstać w kontekście wstrzykiwania - czyli tam, gdzie Angular wie, do czyjego życia go przypisać. W praktyce oznacza to konstruktor klasy albo inicjalizator pola, dokładnie te same miejsca, w których w module o gildiach wywoływałeś inject(). Sprawdziłem na uruchomionym kodzie, co się dzieje poza nimi: wywołanie kończy się błędem NG0203 z komunikatem, że effect() może być użyty tylko w kontekście wstrzykiwania, takim jak konstruktor, funkcja fabrykująca lub inicjalizator pola.

Kronikarz w komponencie

Zbudujmy więc prawdziwego kronikarza. Komponent pilnuje jednej liczby: ilu patroli wysłano na trakt. Sygnał nazwiemy patrols, zaczyna od zera. Metoda sendPatrol() podnosi go o jeden i korzysta z update() z poprzedniej lekcji, bo nowa wartość wynika ze starej. Nowy jest wyłącznie konstruktor, a w nim jedno wywołanie effect(), które wpisuje kolejny stan do dziennika. Resztę zapisu znasz z pierwszego modułu: dekorator @Component z selektorem, znacznikiem komponentu samodzielnego i szablonem, oraz wiązanie zdarzenia (click).

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

@Component({
  selector: 'app-patrol-log',
  standalone: true,
  template: '<button (click)="sendPatrol()">Wyslij patrol</button>'
})
export class PatrolLogComponent {
  patrols = signal(0);

  constructor() {
    effect(() => console.log('Patroli na trakcie:', this.patrols()));
  }

  sendPatrol(): void {
    this.patrols.update(v => v + 1);
  }
}

Uruchomiłem ten komponent i przebieg jest dokładnie taki, jak zapowiedziałem. Zaraz po pierwszym wyświetleniu, zanim ktokolwiek dotknął przycisku, w dzienniku pojawia się wpis z zerem. To ważne i często zaskakuje: efekt wykonuje się co najmniej raz sam z siebie, bo inaczej dziennik zaczynałby się od pierwszej zmiany i nie wiedziałbyś, od czego się zaczęło. Po kliknięciu przycisku dochodzi drugi wpis, z jedynką.

Najciekawsze jest to, co pozostało nietknięte. Metoda sendPatrol() jest identyczna jak w komponentach z poprzednich postojów - podnosi sygnał o jeden i na tym kończy, o istnieniu kronikarza nie wie nic i nie ma w niej ani jednej linii o dzienniku. Sygnał patrols też się nie zmienił: dalej jest zwykłym sygnałem zapisywalnym, dalej czytasz go nawiasami, dalej możesz go pokazać w szablonie. Nie przybyła też żadna metoda cyklu życia z drugiego modułu - nie ma tu ngOnInit ani niczego, co trzeba by wywołać ręcznie. Efekt dopiął się z boku i patrzy.

Ta jednostronność jest cechą, a nie brakiem. Sygnał nie wie, ilu ma obserwatorów, i nie musi wiedzieć; dopisanie drugiego, trzeciego i dziesiątego efektu czytającego patrols nie wymaga w nim żadnej zmiany. Tak samo działała wieża z pierwszej lekcji: strażnik zapala dym i wraca do swoich spraw, a to, kto na ten dym patrzy, jest już zupełnie inną historią.

Co efekt śledzi, a czego nie

Skoro Angular buduje listę zależności z tego, co efekt faktycznie odczytał, to lista ta powstaje na nowo przy każdym przebiegu. Konsekwencje bywają zaskakujące, więc zobaczmy je na przykładzie. Dołóżmy do komponentu drugi sygnał, chronicleOpen, czyli znacznik mówiący, czy księga jest w tej chwili otwarta. Kiedy jest zamknięta, kronikarz niczego nie zapisuje - a więc również nie zagląda na wieżę z patrolami.

constructor() {
  effect(() => {
    if (this.chronicleOpen()) {
      console.log('Patroli na trakcie:', this.patrols());
    }
  });
}

Sprawdziłem to na uruchomionym efekcie i wynik jest jednoznaczny. Dopóki chronicleOpen zwraca prawdę, każda zmiana patrols uruchamia efekt ponownie, bo w ostatnim przebiegu ten sygnał został odczytany. Ale wystarczy ustawić chronicleOpen na fałsz, żeby warunek przestał wpuszczać wykonanie do środka - i od tej chwili zmiany patrols nie budzą efektu ani razu, bo w ostatnim przebiegu nikt tej wieży nie czytał. Zależność nie jest przypisana raz na zawsze do sygnału; jest zapisem tego, na co efekt patrzył ostatnim razem.

Nie zmieniło się przy tym nic w samych sygnałach - obie wieże stoją, obie mają aktualne wartości, a odczyt this.patrols() w dowolnym innym miejscu programu dalej zwraca prawdziwą liczbę. Uśpiony jest wyłącznie kronikarz.

Warto poznać jeszcze trzy zachowania, wszystkie sprawdzone na działającym kodzie, bo oszczędzą Ci godzin zdziwienia. Po pierwsze, zapisanie do sygnału tej samej wartości, którą już miał, nie uruchamia efektu ponownie - Angular porównuje nową wartość ze starą i przy braku różnicy nikogo nie budzi. Po drugie, jeśli w jednej turze zmienisz dwa sygnały, na które efekt patrzy, wykona się on jeden raz, a nie dwa; zmiany są zbierane i rozliczane razem. Po trzecie i najbardziej podstępne: śledzone są tylko odczyty wykonane od razu w ciele efektu. Sygnał odczytany w środku setTimeout, w odpowiedzi z sieci albo w innej funkcji wywołanej później zależnością nie zostanie, bo w chwili, gdy Angular obserwował przebieg, nikt go jeszcze nie zawołał.

Efekt to nie skryba

Teraz najczęstsza pomyłka, jaką popełniają wszyscy po pierwszym zetknięciu z efektami. Komponent ma pokazywać liczbę strażników na służbie, a wynika ona z dwóch wież: z liczby patroli i z liczebności jednego patrolu. Skoro efekt uruchamia się po każdej zmianie, kusi, żeby użyć go do przeliczenia i wpisania wyniku do trzeciego sygnału.

// TAK NIE ROBIMY - efekt w roli wartosci pochodnej
guardsOnDuty = signal(0);

constructor() {
  effect(() => {
    this.guardsOnDuty.set(this.patrols() * this.guardsPerPatrol());
  });
}

Uczciwie przyznam: ten kod działa. Sprawdziłem go na uruchomionym efekcie w Angularze 19 - zapis do sygnału z wnętrza efektu przechodzi bez błędu, a guardsOnDuty dostaje poprawną liczbę i aktualizuje się po każdej zmianie składników. A mimo to jest to rozwiązanie złe i chcę, żebyś wiedział dokładnie dlaczego, bo "działa" i "jest dobre" to dwie różne rzeczy.

Powodów jest trzy. Pierwszy: masz teraz dwa źródła prawdy zamiast jednego. Liczba strażników istnieje w osobnej wieży, którą ktoś inny może w każdej chwili nadpisać wywołaniem set() z zupełnie innego miejsca, i nic tego nie zabroni. Drugi: wynik jest przez chwilę nieaktualny. Efekt nie wykonuje się w tej samej milisekundzie co zapis do patrols, więc istnieje moment, w którym patroli jest już więcej, a strażników jeszcze tylu, ilu było. Trzeci: żeby zobaczyć poprawną wartość, ktoś musi wpisać ją do sygnału - a więc dołożyłeś do aplikacji krok, o którym trzeba pamiętać. Cała lekcja o skrybie polegała na tym, żeby takich kroków nie mieć.

Właściwe rozwiązanie zajmuje jedną linię i znasz je z poprzedniego postoju.

guardsOnDuty = computed(() => this.patrols() * this.guardsPerPatrol());

Tu nie ma czego pilnować. Wartość liczy się sama z dwóch wież, nikt jej nigdy nie ustawia i nikt nie może jej nadpisać, bo metody set() na wartości pochodnej po prostu nie ma. Nie ma też momentu rozjazdu: odczyt guardsOnDuty() zawsze zwraca liczbę zgodną z aktualnym stanem składników. Moja rada jest w tej sprawie bezwarunkowa: jeśli to, co chcesz zrobić, da się zapisać jako "wynik policzony z sygnałów", to nie jest zadanie dla efektu, tylko dla computed(). Do efektu sięgasz dopiero wtedy, gdy nie chodzi o wynik, tylko o działanie poza aplikacją.

Tu też leży odpowiedź na pytanie, które lubi się pojawiać: nie, efekt nie służy do łączenia wielu sygnałów w jeden. Od łączenia jest właśnie computed(), bo ono zwraca wartość. Funkcja podana do effect() nie ma jak niczego zwrócić - jej wynik nigdzie nie trafia i nikt go nie czyta, a samo wywołanie effect() oddaje uchwyt do efektu, a nie żadną liczbę.

Sprzątanie po sobie

Bywa, że efekt nie tylko coś zapisuje, ale uruchamia coś, co żyje dalej: odmierzanie czasu, otwarte połączenie, nasłuch w obcej bibliotece. Wtedy pojawia się kłopot, którego nie da się obejść samą uwagą. Skoro efekt wykona się ponownie po każdej zmianie, to przy dziesiątej zmianie działałoby już dziesięć odmierzaczy naraz, każdy z innego przebiegu, i żaden nie wiedziałby o pozostałych.

Dlatego funkcja efektu może przyjąć jeden parametr - Angular podaje jej w nim funkcję rejestrującą sprzątanie. Nazywa się ją zwyczajowo onCleanup i wywołujesz ją, podając własną funkcję z instrukcjami porządkowymi. Angular wykona ją tuż przed następnym przebiegiem oraz przy końcu życia efektu. W poniższym przykładzie wartownik co sekundę powtarza meldunek o liczbie patroli. Do odmierzania użyję przeglądarkowej funkcji setInterval, która co zadaną liczbę milisekund wywołuje podaną funkcję i zwraca uchwyt do siebie, oraz clearInterval, która ten uchwyt wyłącza. Zwróć uwagę, że liczbę patroli odczytuję przed uruchomieniem odmierzania i zapamiętuję w zwykłej stałej.

effect((onCleanup) => {
  const current = this.patrols();
  const timer = setInterval(() => console.log('Na trakcie patroli:', current), 1000);

  onCleanup(() => clearInterval(timer));
});

Prześledźmy cały cykl po kolei, bo to jeden z tych ciągów, które warto umieć wyrecytować, i sprawdziłem go na uruchomionym efekcie krok po kroku. Najpierw efekt odczytuje sygnał i rejestruje go jako zależność - to dzieje się w linii z this.patrols(). Potem uruchamia właściwe działanie uboczne, czyli setInterval, i przy okazji zgłasza, jak po nim posprzątać. Następnie ktoś wysyła kolejny patrol i zmiana sygnału wyzwala ponowne uruchomienie efektu. I dopiero wtedy, jako pierwsza czynność tego nowego przebiegu, wykonuje się funkcja z onCleanup i gasi poprzedni zasób wywołaniem clearInterval, a zaraz po niej ciało efektu rusza od nowa z aktualną liczbą. W dzienniku widać to jak taśmę: ciało dla stanu pierwszego, sprzątanie po stanie pierwszym, ciało dla stanu drugiego, sprzątanie po stanie drugim, ciało dla stanu trzeciego. Nigdy dwa odmierzacze naraz.

Nie zmieniło się przy tym nic w sposobie działania samego sygnału ani w liczbie przebiegów efektu - sprzątanie nie jest dodatkowym uruchomieniem i niczego nie wywołuje samo z siebie. Zwróć jeszcze uwagę na tę stałą current. Odczytałem sygnał od razu w ciele efektu właśnie dlatego, że jak wiesz z poprzedniej sekcji, odczyt wykonany później, w środku setInterval, nie zostałby zależnością - efekt nigdy by się nie odnowił, a wartownik do końca świata powtarzałby pierwszy meldunek.

Kiedy efekt się kończy

Skoro efekt raz postawiony pracuje w kółko, naturalne jest pytanie, kto go w końcu wyłącza. Odpowiedź jest wygodniejsza, niż się spodziewasz: nikt, bo Angular robi to sam. Efekt utworzony w konstruktorze albo w inicjalizatorze pola przypisuje się do życia tego komponentu i ginie razem z nim. Sprawdziłem to na uruchomionym komponencie: po jego zniszczeniu wysłałem jeszcze dwa patrole i w dzienniku nie pojawił się ani jeden nowy wpis, mimo że w kodzie nie było ani jednej linii poświęconej sprzątaniu.

To warto zapamiętać osobno, bo w starszych przykładach z sieci spotkasz komponenty, które ręcznie pilnują zniszczenia efektu przy pomocy DestroyRef. Taki zapis nie jest błędny, ale jest zbędny - powtarza to, co Angular i tak zrobi, a początkującego uczy odruchu pisania rytuałów bez potrzeby. W dokumentacji Angulara 19 stoi to wprost przy opcji manualCleanup: gdy jej nie podasz, efekt sam rejestruje się do posprzątania.

Bywa jednak, że chcesz go uciszyć wcześniej, jeszcze za życia komponentu - na przykład wtedy, gdy uczeń zamyka księgę i dalsze wpisy są tylko szumem. Do tego służy uchwyt, który zwraca samo wywołanie effect(). W dokumentacji Angulara jego typ nazywa się EffectRef, a jedyne, co potrafi, to metoda destroy() bez argumentów.

export class PatrolLogComponent {
  patrols = signal(0);

  private logRef = effect(() => console.log('Patroli na trakcie:', this.patrols()));

  closeChronicle(): void {
    this.logRef.destroy();
  }
}

Efekt siedzi tu w inicjalizatorze pola, a nie w konstruktorze, i to jest w pełni dozwolone - inicjalizator pola również jest kontekstem wstrzykiwania. Po wywołaniu closeChronicle() kronikarz milknie na dobre: sprawdziłem to na uruchomionym efekcie i po destroy() żadna kolejna zmiana sygnału nie wywołuje już ciała, a zarejestrowane sprzątanie wykonuje się ostatni raz przy samym zniszczeniu. Nie zmienia się przy tym absolutnie nic poza kronikarzem: sygnał patrols dalej można zapisywać i odczytywać, szablon dalej pokazuje aktualną liczbę, a inne efekty patrzące na tę samą wieżę pracują bez zakłóceń. Zamknięta została jedna księga, nie wieża.

Opcja, którą zobaczysz w starszym kodzie

Efekt przyjmuje jeszcze drugi, nieobowiązkowy argument: obiekt opcji. W kodzie pisanym kilka lat temu spotkasz w nim najczęściej jedną pozycję, allowSignalWrites, i lepiej wiedzieć, co znaczy, zanim skopiujesz ją do własnego projektu.

effect(() => {
  // tu cialo efektu
}, { allowSignalWrites: true });

Historia jest krótka. W Angularze 16, 17 i 18 zapis do sygnału z wnętrza efektu był domyślnie zabroniony i kończył się błędem; ta opcja była furtką, którą trzeba było otworzyć świadomie. Od Angulara 19, na którym pracujesz, zapis jest dozwolony zawsze, a sama opcja została oznaczona jako przestarzała z adnotacją mówiącą wprost, że nie jest już potrzebna. Sprawdziłem plik z typami w Angularze 19: pole nadal tam jest, więc kod się skompiluje, ale nie robi nic. Nie dopisuj go do nowego kodu a gdy natkniesz się na niego w cudzym projekcie, potraktuj jak ostrzeżenie: ktoś tam pisał do sygnałów z efektu, a to zwykle znaczy, że tak naprawdę chciał wartości pochodnej.

Gdzie efekt jest na miejscu

Skoro wiesz już, czym efekt nie jest, zobaczmy zadanie skrojone dokładnie pod niego - i akurat to, na którym najłatwiej zrozumieć, po co on w ogóle istnieje. Uczeń wybiera motyw interfejsu, jasny albo ciemny. Wybór ma przetrwać zamknięcie karty, więc trzeba go odłożyć do pamięci przeglądarki. Służy do tego localStorage z metodą setItem, która przyjmuje dwa napisy: klucz, pod którym coś chowamy, i wartość. To kod spoza Angulara, przeglądarkowy i najzupełniej zwyczajny - i właśnie dlatego jest to robota dla efektu, a nie dla wartości pochodnej.

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

@Component({
  selector: 'app-theme',
  standalone: true,
  template: '<button (click)="toggleTheme()">Zmien motyw</button>'
})
export class ThemeComponent {
  theme = signal('light');

  constructor() {
    effect(() => {
      localStorage.setItem('theme', this.theme());
    });
  }

  toggleTheme(): void {
    this.theme.update(t => t === 'light' ? 'dark' : 'light');
  }
}

Uruchomiłem ten komponent i zachowanie jest dokładnie takie, jakiego oczekujesz. Zaraz po pierwszym wyświetleniu w pamięci przeglądarki leży już napis "light", bo efekt wykonał się raz sam z siebie. Po pierwszym kliknięciu leży "dark", po drugim znowu "light" - metoda toggleTheme() przerzuca wartość sygnału, a kronikarz odkłada ją do schowka przeglądarki bez proszenia. Znów zwróć uwagę na to, co pozostało bez zmian: theme jest zwykłym sygnałem zapisywalnym, toggleTheme() używa update() z poprzedniej lekcji i nie ma w niej ani słowa o pamięci przeglądarki, a szablon czyta motyw tak samo jak każdą inną wartość. Gdybyś jutro postanowił zapisywać go na serwerze zamiast w przeglądarce, zmieniłbyś jedną linię w efekcie i nic więcej.

Do czego jeszcze efekt się nadaje? Do wszystkiego, co jest działaniem, a nie wartością, i co ma się wydarzyć po zmianie stanu. Do prowadzenia dziennika i logowania zmian, jak nasz kronikarz. Do odkładania stanu w pamięci przeglądarki, jak przed chwilą. Do wywołania zewnętrznego API, gdy zmiana ma dotrzeć poza aplikację. I do współpracy z bibliotekami, które o Angularze nie wiedzą nic - mapą, wykresem, edytorem tekstu - bo taką bibliotekę trzeba po prostu zawołać i powiedzieć jej, że dane się zmieniły.

Czego efekt nie robi

Wokół efektów krąży kilka opisów, które brzmią wiarygodnie i są nieprawdziwe. Skoro znasz już mechanikę, obalmy je po kolei, bo pomyłka w tym miejscu skutkuje kodem, który działa dziwnie i nie wiadomo dlaczego.

Efekt nie tworzy animacji. Do animowania Angular ma osobny pakiet, a najprostsze przejścia i tak robi się w CSS. Efekt nie zna szablonu, nie zna elementów na stronie i nie potrafi niczego poruszyć - wywołuje wyłącznie funkcję, którą sam mu podałeś. Owszem, mógłbyś w tej funkcji zawołać obcą bibliotekę animacji, ale wtedy to ona animuje, a efekt jest jedynie posłańcem informującym, że coś się zmieniło.

Efekt nie łączy wielu sygnałów. Mówiliśmy o tym przy skrybie: łączenie oznacza policzenie jednej wartości z kilku innych, a wartości zwraca computed(). Funkcja efektu nie ma jak zwrócić wyniku, bo nikt tego wyniku nie odbiera.

Efekt nie optymalizuje renderowania. Wydajność w tym module bierze się z czegoś zupełnie innego: z tego, że sygnał sam melduje zmianę, więc Angular odświeża tylko te fragmenty widoku, które go czytały, zamiast obchodzić wszystkie zamki po kolei. Efekt niczego z tego nie przyspiesza - przeciwnie, każdy dopisany efekt to dodatkowa funkcja do wykonania po zmianie. Jest narzędziem do robienia rzeczy, nie do przyspieszania ich.

Efekt nie renderuje list ani niczego innego na ekranie. Wyświetlaniem zajmuje się szablon, a od przejścia po kolekcji masz w nim pętlę @for z obowiązkowym track, którą znasz z modułu o komponentach. Efekt nie ma dostępu do widoku i nie umie dopisać do strony ani jednego znaku.

I ostatnie, najważniejsze przy dużych aplikacjach: efekt nie jest miejscem na złożoną logikę biznesową. Kusi, bo uruchamia się sam, ale ma trzy cechy, które przy poważnym kodzie stają się kosztowne. Nie zwraca wyniku, więc nie da się go wywołać w teście i sprawdzić, co policzył. Uruchamia się w chwili wybranej przez Angulara, a nie przez Ciebie, więc kolejność zdarzeń wymyka się spod kontroli. I jest przywiązany do życia komponentu, więc logika w nim zamknięta znika razem z ekranem. Reguły domeny i wspólne operacje mieszkają w serwisie, czyli w gildii z wcześniejszego modułu - efekt zostaw do meldunków wysyłanych na zewnątrz.

Pamiętaj: skryba liczy i wywiesza wynik na tablicy, a kronikarz nic nie liczy - wychodzi za bramę i zapisuje w księdze, że dym się zmienił.

Hasła z dokumentacji w tej lekcji

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Do czego służy effect() w Angular Signals?

  2. 2. Które zadanie jest dobrym zastosowaniem effect()?

Zadania praktyczne w grze

  • Układanie w pionie

    Uporządkuj etapy działania effectu z czyszczeniem zasobu (np. setInterval).

  • Edytor kodu

    Uzupełnij komponent tak, by przy każdej zmianie motywu zapisywał go do localStorage.

  • Klikanie w kolejności

    Ułóż wywołanie effectu logującego wartość signala count.

Przydatne artykuły