22 sty 2026
7 min

Najlepsze praktyki pracy z komponentami w Angularze

Komponenty to podstawowe elementy budujące aplikacje w Angularze. W tym artykule znajdziesz zestaw sprawdzonych praktyk, które pomogą Ci pisać bardziej czytelny kod i lepiej organizować projekt. Omówimy m.in. rolę komponentów, konwencje nazewnicze oraz sposoby na ich efektywne porządkowanie.

Zasada Pojedynczej Odpowiedzialności (SRP)

Stosowanie zasady SRP (ang. Single Responsibility Principle) w projekcie oznacza, że każda klasa (komponent, serwis, pipe, dyrektywa itd.) powinna zajmować się tylko jednym konkretnym zadaniem – np. wyświetlaniem listy elementów, obsługą formularza czy komunikacją z API. Aby wdrożyć zasadę SRP w aplikacji, najpierw trzeba określić, które komponenty są „inteligentne” (smart), a które prezentacyjne” (dummy/prezentacyjne) oraz jakie dokładnie zadanie pełni każdy z nich. Ta zasada przynosi wiele korzyści, m.in.:

  • zmniejsza ilość kodu w poszczególnych plikach, bo każdy komponent pokrywa tylko wąski zakres funkcji,
  • ogranicza powstawanie side-effect’ów i błędów,
  • zmniejsza liczbę zmian koniecznych w klasach dzięki jasnemu podziałowi odpowiedzialności,
  • przyspiesza proces rozwoju aplikacji,
  • ułatwia zrozumienie i utrzymanie kodu

Wyobraźmy sobie aplikację do przechowywania przepisów kulinarnych. Mamy w niej listę dostępnych przepisów oraz sekcję do podglądu, edycji i dodawania nowych. Załóżmy, że mamy dwa komponenty NavbarComponent i ViewComponent. NavbarComponent odpowiada za nazwę serwisu i nawigację między stronami, a ViewComponent obsługuje wyświetlanie listy przepisów, dodawanie, edytowanie i usuwanie przepisów, a także formularz do wprowadzania danych. Problem w tym, że ViewComponent zajmuje się zbyt wieloma rzeczami naraz.

Jak zastosować SRP?

Rozbijamy ViewComponent na 3 mniejsze komponenty:

  • RecipesListComponent – sidebar z listą przepisów, 
  • RecipeCardComponent – sekcja wyświetlająca szczegóły wybranego przepisu, 
  • RecipeFormComponent – formularz do wprowadzania danych od użytkownika.

Dodatkowo możemy stworzyć serwis RecipeManagementService, który będzie odpowiadał za operacje CRUD na przepisach. Dzięki temu AppComponent będzie komunikował się z serwisem i przekazywał dane do komponentów za pomocą sygnałów, a reszta komponentów pozostanie prezentacyjna.

Strategia wykrywania zmian (Change Detection Strategy)

Angular wykorzystuje strategie wykrywania zmian, aby określić, kiedy należy sprawdzić komponent pod kątem aktualizacji widoku. Istnieją dwie strategie: Default oraz OnPush. Można je porównać do pielęgniarek.

Strategia Default  to pielęgniarka, która zagląda do każdego pacjenta, niezależnie od tego, czy coś się zmieniło. Strategia OnPush – to pielęgniarka, która sprawdza tylko tych pacjentów, u których faktycznie nastąpiła zmiana stanu lub została o to poproszona.

W przypadku strategii Default każdy komponent potomny jest sprawdzany, jeśli sprawdzany jest jego komponent nadrzędny. Oznacza to, że gdy tylko w jakiejś części aplikacji coś się zmieni, Angular przechodzi przez całe „drzewo” komponentów i sprawdza je wszystkie. To jest mało wydajne, ponieważ powoduje niepotrzebne sprawdzanie również tych komponentów, które w ogóle się nie zmieniły. Typowe sytuacje, które wyzwalają strategię Default, to: żądania HTTP, timery, eventy użytkownika.

Z kolei OnPush ogranicza warunki sprawdzania, aby unikać zbędnych aktualizacji. W tej strategii działa mechanizm CheckOnce – komponent jest sprawdzany tylko raz, gdy zostanie oznaczony jako „brudny” (dirty), a następnie pomijany w kolejnych cyklach. W dużych aplikacjach OnPush pokazuje wyraźną różnicę w wydajności aplikacji – szczególnie na urządzeniach mobilnych. Gdy komponent potomny ulega zmianie, wszyscy jego przodkowie – jeden po drugim – zostają oznaczeni jako „dirty” i sprawdzani w procesie wykrywania zmian. Na przykład:

AppComponent
    HeaderComponent
    ContentComponent
        TodoListComponent
            TodoComponent

Powyżej widzimy hierarchię drzewa komponentów w projekcie, w którym wszystkie komponenty korzystają ze strategii OnPush. Załóżmy, że jakaś zmiana zaszła wewnątrz TodoComponent:

Root Component -> LViewFlags.Dirty
     |
    ...
     |
 ContentComponent -> LViewFlags.Dirty
     |
     |
 TodoListComponent  -> LViewFlags.Dirty
     |
     |
 TodoComponent (event triggered here) -> markViewDirty() -> LViewFlags.Dirty

Zmiany w TodoComponent są przechwytywane przez Zone.js, które informuje komponent główny. Zmieniony TodoComponent zostaje oznaczony jako „dirty”, a wraz z nim wszyscy jego przodkowie w drzewie komponentów. Dzięki temu Zone.js wie, które komponenty należy sprawdzić. Co ważne, HeaderComponent w ogóle nie zostaje naruszony, ponieważ ani on, ani jego dzieci nie uległy zmianie – i właśnie dlatego, że korzysta z OnPush, zostaje całkowicie pominięty.

W większych aplikacjach ustawienie strategii wykrywania zmian na OnPush znacznie ogranicza liczbę komponentów, które muszą być sprawdzane w przypadku zmian asynchronicznych. Dzięki temu aplikacja działa wydajniej. W praktyce najczęściej zaleca się ustawiać OnPush we wszystkich komponentach aplikacji – nie kosztuje to nic dodatkowego, a pozwala oszczędzić sporo czasu i zasobów.

Więcej szczegółowych informacji i przykładów znajdziesz tutaj i tutaj.

Leniwe Ładowanie (Lazy loading)

Dzięki technice lazy loading możesz sprawić, że określone elementy aplikacji będą ładowane dopiero wtedy, gdy są potrzebne, zamiast w momencie inicjalizacji. Takie podejście zmniejsza początkowy rozmiar pakietu (bundle), co przekłada się na szybsze uruchamianie aplikacji. Trzeba jednak pamiętać, że elementy ładowane w trybie odroczonym mogą pojawić się z lekkim opóźnieniem. Sednem lazy loadingu jest określenie priorytetów – czyli tego, co musi zostać załadowane od razu, a co może poczekać. Wraz z rozwojem aplikacji rośnie też ilość danych, więc ta technika jest dobrą praktyką, aby zapewnić rozsądny czas startu, zamiast ładować wszystko naraz.
Na początek upewnij się, że wszystkie komponenty stron są leniwie ładowane. Możesz to zrobić, stosując metodę loadChildren() w konfiguracji nawigacji:

{
  path: 'heavy',
  loadChildren: () => import('./heavy/heavy.module').then(m => m.HeavyModule)
}

W Angularze możesz także zastosować deferred loading wewnątrz komponentów, korzystając z bloków @defer, np.:

@defer {
  <large-component />
}

Wszystkie komponenty, serwisy i dyrektywy użyte wewnątrz bloku @defer są dzielone na osobne pliki JavaScript, które zostaną załadowane dopiero wtedy, gdy będą potrzebne, po zakończeniu inicjalizacji aplikacji.  Trzeba jednak pamiętać, że działa to wyłącznie dla standalone components. Komponenty, które nie są standalone będą ładowane od razu. Aby załadować moduł w trybie lazy przy użyciu @defer, trzeba stworzyć dla niego standalone wrapper component.

@Component({
  standalone: true,
  selector: 'lazy-heavy',
  template: '<ng-container *ngComponentOutlet="cmp()"></ng-container>',
  imports: [CommonModule],
})
export class LazyHeavyComponent {
  cmp = signal<Type<unknown> | null>(null);

  async ngOnInit() {
    const { HeavyComponent } = await import('./heavy/heavy.component');
    this.cmp.set(HeavyComponent);
  }
}

Dzięki temu taki komponent wrapper można następnie użyć wewnątrz bloku @defer.

Konwencje nazewnicze

Ta część dotyczy konwencji nazewniczych, które poprawiają czytelność kodu. Angular posiada własny style guide, który jest automatycznie stosowany do plików tworzonych za pomocą Angular CLI. Najpierw omówimy Angular Style Guide, a następnie wspomnę o kilku zasadach nazewnictwa, które warto zapamiętać.

  • Nazwy plików

Jeżeli nazwa komponentu składa się z więcej niż jednego słowa, należy w nazwie pliku używać myślników „-” między słowami, a na końcu dodać odpowiednie rozszerzenie pliku, np.: user-profile.ts, user-profile.html, itd. Ważne jest, aby wszystkie pliki w folderze komponentu miały spójną nazwę, a także żeby odpowiadały nazwie klasy w TypeScript.

  • Nazwy klas:

Od Angulara 20 pliki tworzone przez CLI mogą pomijać rozszerzenia component czy service w nazwach plików i klas. Jednak, aby łatwiej było je identyfikować, zaleca się samodzielne dodanie tych rozszerzeń albo zmianę konfiguracji. Przykłady:

  • user-profile.ts  —> user-profile.component.ts
  • UserProfile  —>  UserProfileComponent
  • user-management.ts  —> user-management.service.ts
  • UserManagement —> UserManagementService

Angular nadal wspiera obie formy, więc bez obaw możesz używać rozszerzeń 🙂.

  • Selektory klas

Dla selektorów komponentów najczęściej stosuje się nazwy odpowiadające nazwom plików, poprzedzone prefiksem app, np.: app-user-profile. Jest to domyślny styl selektora (można go zmienić), ale najlepiej się go trzymać, aby zachować spójność z Angular style guide. 🙂

Dla pipes utworzonych przez CLI nazwy są pisane w formacie camelCase, np.: customPipe. To również jest domyślna konwencja i camelCase jest szeroko stosowany, dlatego najlepiej się tego trzymać.

Dyrektywy mają selektory atrybutów pisane camelCase, np. appUserProfile.

W kodzie klas szeroko preferuje się stosowanie camelCase dla nazw właściwości i funkcji. Nie powinno się używać camelCase dla nazw klas, ale należy go stosować dla wszystkiego, co znajduje się wewnątrz klasy. Dla przykładu:

// user-profile.component.ts
import { Component, input, output } from '@angular/core';
import { CommonModule } from '@angular/common';
import { User } from './user.model';

@Component({
  selector: 'app-user-profile',
  templateUrl: './user-profile.component.html',
  styleUrl: './user-profile.component.css',
  imports: [CommonModule],
})
export class UserProfileComponent {
  readonly user = input.required<User>();
  editRequest = output<number>();

  private _lastEditRequestTime: Date | null = null;

  onEditClick(): void {
    this._logEditRequest();
    this.editRequest.emit(this.user().id);
  }

  private _logEditRequest(): void {
    this._lastEditRequestTime = new Date();
    console.log(`Edit requested at: ${this._lastEditRequestTime.toISOString()}`);
  }
}

Dobrą praktyką jest dodawanie podkreślenia “_” albo hasha “#” na początku nazw prywatnych właściwości i metod, aby odróżniały się od elementów publicznych. Zwróć uwagę, że zarówno właściwości, jak i metody (publiczne i prywatne) są tutaj logicznie pogrupowane, co poprawia czytelność kodu.

Organizacja plików projektu

W dużych projektach chaotyczna struktura folderów szybko prowadzi do problemów z zarządzaniem kodem. Dlatego dobrą praktyką jest ich odpowiednie uporządkowanie. W tej części omówimy kilka zasad, których warto się trzymać przy organizacji plików projektu. Na początek – cały kod aplikacji powinien znajdować się wewnątrz folderu src, w tym pliki konfiguracyjne (app.config.ts) oraz pliki odpowiedzialne za bootstrap aplikacji. Plik main.ts służy do uruchamiania aplikacji. Po stworzeniu projektu za pomocą CLI, w katalogu src znajdziesz folder app, zawierający komponenty bootstrappowane w pliku main.ts. Choć to app jest folderem głównym dla komponentów, każdy nowy komponent, który stworzysz, powinien znaleźć się we własnym folderze wewnątrz app. Dla przykładu:

src/
|
├── app/
|   |
|   ├── core/
|   |   └── services/
|   |       └─ logger.service.ts
|   |
|   ├── features/
|   |   └── user-profile/
|   |       ├── components/
|   |       |   └── user-profile.component.ts
|   |       ├── models/
|   |       |   └─ user.model.ts 
|   |       └── services/
|   |           └─ user.service.ts  
|   |
|   ├── shared/
|   |   ├── components/
|   |   |   └── header/  
|   |   |       ├── header.component.html
|   |   |       └── header.component.ts
|   |   └── services/
|   |       └─ analytics.service.ts
|   |
|   ├── app.component.css
|   ├── app.component.html
|   ├── app.component.ts
|   ├── app.config.ts
|   └── app.routes.ts
|
├── environments/
|   ├── environment.development.ts
|   └── environment.ts
|
├── index.html
├── main.ts
└── styles.css

Na marginesie – jeśli korzystasz z plików testowych, powinieneś umieszczać je w folderze odpowiadającego im komponentu. Być może pomyślisz, że ta struktura folderów jest zbyt „wysoka”, ponieważ dla wszystkiego istnieje osobny plik. Nie ma się jednak czym martwić – właśnie o to chodzi, aby jedna koncepcja miała swój własny plik (np. nie należy trzymać modeli, komponentów i serwisów w jednym pliku).

Wyjątek od tej zasady pojawia się wtedy, gdy niektóre klasy są bardzo małe – w takim przypadku można umieścić kilka z nich w jednym pliku, o ile są ze sobą powiązane. Jest to dopuszczalna praktyka szczególnie w bardzo dużych projektach, gdzie zmniejszenie liczby plików może zwiększyć efektywność pracy. Jednak jako początkujący programista powinieneś najpierw czuć się swobodnie z obowiązującymi konwencjami, zanim przejdziesz do bardziej złożonych projektów.

Debugowanie

Narzędzia deweloperskie (F12 lub Ctrl+Shift+I w Windows/Linux; Fn+F12 lub Cmd+Option+I na Mac) to świetny zestaw funkcji do inspekcji działania aplikacji. Angular oferuje też rozszerzenie przeglądarkowe Angular DevTools, które udostępnia bardziej zaawansowane możliwości, pozwalając wygodnie debugować każdy komponent. Więcej informacji znajdziesz tutaj.

Podsumowanie

Komponenty to podstawowe elementy budujące projekty w Angularze. Być może zauważyłeś tu kilka wskazówek, które w mniejszych aplikacjach łatwo przeoczyć, ale które mogą okazać się niezwykle pomocne przy pracy nad większymi projektami. Te praktyki obejmują stosowanie zasady pojedynczej odpowiedzialności, czyli ograniczanie zadań komponentu wyłącznie do jego własnych, dobieranie odpowiedniej strategii wykrywania zmian, korzystanie z lazy loadingu, poznawanie konwencji nazewniczych, wskazówki dotyczące organizacji projektu oraz używanie bardziej zaawansowanych narzędzi do debugowania. Łącznie pozwalają one budować aplikacje w Angularze, które są czytelniejsze, łatwiejsze do zrozumienia i bardziej odporne na błędy. Powodzenia w kodowaniu!

Podziel się artykułem

Zapisz się na nasz newsletter

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