04 lut 2025
11 min

Angular Material: Tworzenie motywu aplikacji z Material 3

Angular Material to świetne narzędzie do tworzenia intuicyjnych, responsywnych i atrakcyjnych interfejsów użytkownika. Oferuje gotowe, reużywalne komponenty UI, które są zgodne z wytycznymi Google Material Design.

Od wersji 18 Angular w pełni wspiera specyfikację Material Design 3, który opiera się na tokenach zaimplementowanych jako zmiennych CSS. Takie podejście pozwala definiować właściwości na najwyższym poziomie, które następnie propagują się do komponentów, które z nich korzystają. Dzięki tej aktualizacji możesz szczegółowo nadpisywać właściwości stylów bez zwiększania specyficzności selektorów CSS ani ingerowania w wewnętrzne selektory komponentów Angular Material.

Wersja 19 wprowadziła nowe API do swojego zaawansowanego systemu komponowania motywu, które umożliwia deweloperom dostosowanie wyglądu i stylu aplikacji. Oferuje on uporządkowane podejście do definiowania kolorów, typografii i stylów komponentów, zapewniając spójność w całej aplikacji przy jednoczesnym zachowaniu elastyczności w dostosowywaniu do preferencji marki i projektu.

Na pierwszy rzut oka może to wydawać się skomplikowane, więc podzielmy cały proces na części. Ten artykuł przeprowadzi Cię przez podstawy tworzenia motywów w Angular Material, pokazując, jak tworzyć, konfigurować i stosować własne motywy. 

Wymaganie wstępne: Podstawy Sass

Sass to potężny preprocesor CSS, który rozszerza możliwości standardowego CSS, umożliwiając programistom pisanie czystszych, bardziej modularnych i łatwiejszych w utrzymaniu stylów. Angular Material wykorzystuje Sass w swojej strukturze, dlatego ważne jest, aby zdefiniować podstawowe pojęcia.

Zagnieżdżanie

System zagnieżdżania w Sass pozwala pisać style CSS w hierarchiczny i intuicyjny sposób, odzwierciedlający strukturę HTML. Zamiast wielokrotnego powtarzania selektorów, możesz zagnieżdżać style dla elementów podrzędnych bezpośrednio wewnątrz bloku ich elementu nadrzędnego.

Dokumentacja: https://sass-lang.com/documentation/style-rules/declarations/#nesting

.header {
  width: 100vw;

  &__logo {
    margin-left: auto;
  }

  &--dark {
    background-color: #3a2125;
    color: white;
  }

  h1 {
    font-size: 2rem;
    line-height: 2.8rem;

    &:hover {
      text-decoration: underline;
    }
  }
}

Zmienne

Uprość zarządzanie arkuszami stylów, przechowując wartości takie jak kolory, odstępy, rozmiary i inne elementy projektu w wielokrotnie używanych, nazwanych zmiennych.

Dokumentacja: https://sass-lang.com/documentation/variables/

$my-color: #12ea8c;
$selector: primary;

.card {
  $main-color: $my-color;
  background-color: $main-color;
  padding: 1rem;
}

// #{$variable} - string interpolation
.link-#{selector} {
  color: $my-color;
}

Mapy

Mapa pozwala przechowywać i zarządzać danymi w postaci par klucz-wartość. Podobnie jak obiekty w JavaScript, mapa umożliwia organizowanie wartości w sposób strukturalny i łatwy do utrzymania

Dokumentacja: .https://sass-lang.com/documentation/values/maps/

$sizes: (
  sm: 10rem;
  md: 15rem;
  lg: 20rem;
  xl: 25rem;
);

@each $key, $value in $sizes {
  .card--#{$key} {
    width: $value;
  }
}

.user-card {
  width: map-get($sizes, md);
}

Listy

Listy służą do grupowania wielu wartości w proste struktury danych. Listy SCSS są elastyczne i mogą być oddzielone przecinkami (1px, 2px, 3px) lub spacjami (1px 2px 3px).

Dokumentacja: https://sass-lang.com/documentation/values/lists/

$size-prefixes: sm, md, lg;

.panel-#{nth($size-prefixes,2)} {
  color: $primary-color;
  width: 15rem;
}

Funkcje

Dzięki funkcjom możesz wykonywać operacje i zwracać wartości bezpośrednio w arkuszu stylów. Umożliwiają one dynamiczne stylowanie poprzez przetwarzanie danych, takich jak kolory, liczby, ciągi znaków, jednostki oraz struktury danych, jak mapy i listy. Obsługują również podstawowe koncepcje, takie jak logika boole’owska i pętle.

Dokumentacja: https://sass-lang.com/documentation/values/functions/

@function fade-out($color, $alpha: 0.5) {
  @if $alpha < 0 or $alpha > 1 {
    @error “Provide value between 0 and 1”;
  }

  @return rgba($color, $alpha);
}

@function sum($numbers...) {
  $sum: 0;
  @each $number in $numbers {
    $sum: $sum + $number;
  }
  @return $sum;
}

.card {
  background-color: fade-out($primary-color);
  border: solid 1px fade-out($accent-color, 0.3);
  width: sum(50px, $base-width, 10vw);
}

Mixins

Mixin to reużywalny blok CSS, który możesz zdefiniować raz i stosować tam, gdzie jest to potrzebne, redukując redundancję i sprawiając, że kod jest łatwiejszy do utrzymania. Mixiny są szczególnie przydatne do obsługi powtarzalnych wzorców, skomplikowanych kombinacji właściwości lub kompatybilności między przeglądarkami, ponieważ mogą przyjmować parametry do dostosowywania swojego zachowania.

Dokumentacja: https://sass-lang.com/documentation/values/mixins/

@mixin reset-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

@mixin horizontal-list($primary-element-color: currentColor) {
  @include reset-list;
  display: flex;
  gap: 1rem;

  li.primary {
    color: $primary-element-color;
  }
}

Moduły

Moduły pomagają skuteczniej organizować i zarządzać stylami, szczególnie w większych projektach. Dwie główne dyrektywy, @import i @use, pozwalają na dołączanie stylów z innych plików, ale różnią się funkcjonalnością i zastosowaniem.

Starsza dyrektywa, @import, pozwala na dołączanie stylów z jednego pliku do drugiego. Jednak ma pewne ograniczenia, takie jak wolniejsza kompilacja i potencjalne problemy z duplikowaniem stylów (kod Sass jest wykonywany przy każdym wywołaniu @import).

Dokumentacja: https://sass-lang.com/documentation/at-rules/import/

Wprowadzona jako nowoczesna alternatywa, dyrektywa @use oferuje lepszy sposób importowania stylów z przestrzenią nazw. Unika konfliktów, wymagając jawnych odniesień do importowanych zmiennych lub mixinów.

Dokumentacja: https://sass-lang.com/documentation/at-rules/use/

@import “./variables.scss”;
@use “./functions.scss”;
@use “./my-awesome-mixins.scssas mixins;

.options-list {
  @include mixins.horizontal-list(functions.fade-out($primary-color));
}

Definiowanie własnego motywu

Nowe API Angular Material znacznie upraszcza proces tworzenia motywów. Aby skorzystać z podstawowego motywu, wystarczy użyć mixinu theme w stylach z zakresem HTML, aby zapewnić jego zastosowanie w całej aplikacji. Mixin akceptuje mapę definiującą kolory, typografię i gęstość, a następnie generuje zestaw zmiennych CSS, które kontrolują wygląd i układ komponentów.

@use “@angular/materialas mat;

html {
  @include mat.theme(
    (
      color: mat.$violet-palette,
      typography: Roboto,
      density: 0,
    )
  );
}

Oczywiście, możliwości dostosowania są o wiele większe. Najpierw przyjrzyjmy się dodatkowym funkcjom, jakie oferuje mixin theme.

Kolory

Kolory w motywie określają style kolorystyczne komponentów, takie jak kolor wypełnienia przycisków lub kolor obramowania pól tekstowych. Angular Material rozpoznaje następujące role kolorów:

  • Primary (główny) – Jest to główny kolor marki, używany w najbardziej widocznych komponentach interfejsu użytkownika. Angular Material korzysta z „bazowego” koloru głównego oraz kilku jaśniejszych i ciemniejszych odcieni. Przykłady: przyciski, aktywne elemety
  • Secondary (akcentujący) – Kolor służący do wyróżniania lub podkreślania elementów. Zwykle jest żywy i kontrastuje z kolorem głównym. Używany oszczędnie, aby nie przytłaczać użytkownika. Przykłady: przycisk FAB, aktywna zakładka
  • Tertiary (trzeciorzędny) – Używany do kontrastowych akcentów, które równoważą kolory główny i akcentujący lub zwracają większą uwagę na określony element, taki jak pole tekstowe.
  • Warn (ostrzegawczy) – Służy do komunikowania stanów błędów, np. w przypadku wprowadzenia nieprawidłowego hasła w polu tekstowym.

Każda rola opisana jest przez paletę kolorów – zestaw podobnych kolorów o różnych odcieniach, od ciemnych (najniższy indeks) po jasne (najwyższy indeks). Angular Material wykorzystuje te palety do tworzenia schematów kolorów, które komunikują hierarchię, stany oraz tożsamość marki w aplikacji.

Aby skomponować swój motyw, możesz skorzystać z jednej z wbudowanych palet kolorów: red (czerwony), green (zielony), blue (niebieski), yellow (żółty), cyan (błekitny), magenta (purpurowy), orange (pomarańczowy), chartreuse (jasnozielony), spring-green (wiosenna zieleń), azure (lazurowy), violet (fioletowy), rose (różany).

Aby uzyskać dostęp do palety, użyj zmiennej o nazwie “${{nazwaPalety}}-palette” z biblioteki @angular/material.

Jeśli potrzebujesz kolorów idealnie dopasowanych do Twojej marki, możesz stworzyć własną paletę, korzystając z tego generatora:

ng generate @angular/material:theme-color

Teraz możesz skonfigurować kolory, używając wygenerowanej palety głównej:

@use “@angular/materialas mat;
@use “./theme-colorsas theme;

html {
  @include mat.theme(
    (
      color: theme.$primary-palette,
      typography: Roboto,
      density: 0,
    )
  );
}

Lub skonfiguruj paletę trzeciorzędną osobno, aby dodać wyraźny kolor akcentu do niektórych komponentów:

@use “@angular/materialas mat;
@use “./theme-colorsas theme;

html {
  @include mat.theme(
    (
      color: (
        primary: theme.$primary-palette,
        tertiary: theme.$tertiary-palette
      ),
      typography: Roboto,
      density: 0,
    )
  );
}

Typografia

Typografia to system, który definiuje zestaw stylów fontów, aby zapewnić spójny i atrakcyjny wizualnie tekst w całej aplikacji. Tutaj możesz wybrać prosty sposób, definiując tylko rodzaj fontu:

@use “@angular/materialas mat;
@use “./theme-colorsas theme;

html {
  @include mat.theme(
    (
      color: theme.$primary-palette,
      typography: Poppins,
      density: 0,
    )
  );
}

lub rozróżnić fonty dla tekstu podstawowego (używanego w większości tekstów aplikacji) i tekstu brandingowego (używanego w nagłówkach i tytułach):

@use “@angular/materialas mat;
@use “./theme-colorsas theme;

html {
  @include mat.theme(
    (
      color: theme.$primary-palette,
      typography: (
        plain-family: Poppins,
        brand-family: Montserrat,
      ),
      density: 0,
    )
  );
}

Jeśli szukasz wysokiej jakości fontów, odwiedź Google Fonts, gdzie możesz zapoznać się z szerokim wyborem czcionek, korzystając z pomocnych narzędzi do podglądu. Wybierz potrzebne fonty i osadź wygenerowany kod do pobrania ich w sekcji <head>  pliku index.html.

Kolejnym konfigurowalnym aspektem typografii jest grubość fontu (font weight), która pozwala na zdefiniowanie konkretnych grubości dla tekstu regularnego, średniego i pogrubionego:

@use “@angular/materialas mat;
@use “./theme-colorsas theme;

html {
  @include mat.theme(
    (
      color: theme.$primary-palette,
      typography: (
        plain-family: Poppins,
        brand-family: Montserrat,
        bold-weight: 800,
        medium-weight: 500,
        regular-weight: 300,
      ),
      density: 0,
    )
  );
}

Gęstość

Wartość gęstości (density) określa odstępy wewnątrz komponentów, takie jak odstępy wokół tekstu przycisku lub wysokość pól formularzy.

Przyjmuje wartości całkowite od 0 do -5, gdzie 0 oznacza domyślne odstępy, a -5 zapewnia najbardziej zwarty i kompaktowy układ. Każdy kolejny krok w dół (-1, -2 itd.) zmniejsza odpowiednie rozmiary o 4px, aż do minimalnego rozmiaru wymaganego do spójnego renderowania komponentów, co powoduje, że większość z nich zawiera mniej pustej przestrzeni w swoim układzie.

Motyw dostosowany do kontekstu

Nie musisz ograniczać swojej aplikacji do jednego motywu. Jeśli chcesz, aby konkretna sekcja wyróżniała się i przyciągała uwagę, możesz zastosować do niej inny motyw.

@use “@angular/materialas mat;
@use “./theme-colorsas theme;

html {
  @include mat.theme(
    (
      color: theme.$primary-palette,
      typography: Poppins,
      density: 0,
    )
  );
}

.azure-section {
  @include mat.theme(
    (
      color: mat.$azure-palette,
      typography: Poppins,
      density: 0,
    )
  );
}

Tryb ciemny

Tryb ciemny (Dark mode) to powszechnie używana funkcja, a Angular Material ułatwia jego implementację. Domyślnie wykorzystuje funkcję light-dark, która pozwala na określenie dwóch kolorów dla danej właściwości. Ta funkcja zwraca jeden z dwóch kolorów w zależności od tego, czy aktywny jest jasny czy ciemny schemat kolorów. Schemat jest określany na podstawie konfiguracji zdefiniowanej przez programistę lub preferencji użytkownika ustawionych w systemie operacyjnym lub przeglądarce.

Dodatkowo właściwość CSS color-scheme umożliwia zastąpienie schematu kolorów użytkownika na jasny lub ciemny. Ta elastyczność ułatwia tworzenie spójnych wizualnie i przyjaznych dla użytkownika interfejsów.

@use “@angular/materialas mat;
@use “./theme-colorsas theme;

@mixin apply-dark-mode {
  color-scheme: dark;
}

html {
  @include mat.theme(
    (
      color: theme.$primary-palette,
      typography: Poppins,
      density: -2,
    )
  );
}

body {
  margin: 0;

  &.dark-mode {
    @include apply-dark-mode;
  }
}

Dzięki tak zdefiniowanym stylom możesz zaimplementować przełącznik do zmiany między trybem jasnym i ciemnym, dynamicznie dodając lub usuwając klasę dark-mode z elementu <body>. Aby zapewnić jak najlepsze UX, początkowy tryb powinien być zgodny z ustawieniami urządzenia użytkownika.

export type ColorMode = 'light' | 'dark';

export const PREFERRED_COLOR_MODE = new InjectionToken<Signal<ColorMode>>(
  'PREFERRED_COLOR_MODE',
  {
    providedIn: 'root',
    factory: () => {
      const destroyRef = inject(DestroyRef);
      const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

      const colorMode = signal<ColorMode>(
        mediaQuery.matches ? 'dark' : 'light',
      );

      const preferredColorModeChangeListener = (event: MediaQueryListEvent): void => {
        event.matches ? colorMode.set('dark') : colorMode.set('light');
      };

      mediaQuery.addEventListener('change', preferredColorModeChangeListener);

      destroyRef.onDestroy(() =>
        mediaQuery.removeEventListener('change', colorSchemeChangeListener),
      );

      return colorMode;
    },
  },
);

// Renderer2 cannot be directly injected into singleton service
export const injectRenderer2 = (): Renderer2 =>
  inject(RendererFactory2).createRenderer(null, null);

@Injectable({ providedIn: 'root' })
export class DarkModeService {
  private readonly DARK_MODE_CLASS = 'dark-mode';

  private readonly _renderer = injectRenderer2();
  private readonly _document = inject(DOCUMENT);
  private readonly _preferredColorMode = inject(PREFERRED_COLOR_MODE);

  private readonly _mode = linkedSignal(() => this._preferredColorMode());
  readonly mode = this._mode.asReadonly();
  readonly isDarkMode = computed(() => this.mode() === 'dark');

  constructor() {
    effect(() => {
      this._applyDarkModeClass(this.isDarkMode());
    });
  }

  toggleDarkMode(): void {
    this._mode.update((mode) => (mode === 'light' ? 'dark' : 'light'));
  }

  setDarkMode(enabled: boolean): void {
    this._mode.set(enabled ? 'dark' : 'light');
  }

  private _applyDarkModeClass(enabled: boolean): void {
    if (enabled) {
      this._renderer.addClass(this._document.body, this.DARK_MODE_CLASS);
    } else {
      this._renderer.removeClass(this._document.body, this.DARK_MODE_CLASS);
    }
  }
}

Jeśli wolisz nie polegać na domyślnych wyborach kolorów Angular Material w trybie ciemnym, możesz stworzyć osobne motywy, definiując właściwość theme-type w mapie kolorów i stosując je według potrzeb.

@use “@angular/materialas mat;
@use “./light-themeas light-theme;
@use “./dark-themeas dark-theme;

@mixin apply-light-theme {
  @include mat.theme(
    (
      color: (
        primary: light-theme.$primary-palette,
        tertiary: light-theme.$tertiary-palette,
        theme-type: light
      ),
      typography: Poppins,
      density: 0,
    )
  );
}

@mixin apply-dark-theme {
  @include mat.theme(
    (
      color: (
        primary: dark-theme.$primary-palette,
        tertiary: dark-theme.$tertiary-palette,
        theme-type: dark
      ),
      typography: Poppins,
      density: 0,
    )
  );
}

Tokeny systemowe

Jak wspomniano wcześniej, implementacja Material 3 opiera się na tokenach, zaimplementowanych jako zmienne CSS, aby zapewnić granularne i elastyczne stylowanie. Mixin theme generuje znaczną liczbę tokenów. Możesz je sprawdzić, otwierając narzędzia deweloperskie w przeglądarce. Aby były bardziej rozpoznawalne, wszystkie zaczynają się od prefiksu „mat-sys”. Opiszmy je pokrótce.

Kolory

Mixin theme generuje sporo tokenów dla kolorów, takich jak:

  • –mat-sys-primary jako najczęściej używany kolor przez komponenty
  • –mat-sys-surface jako kolor tła
  • –mat-sys-error do zaalarmowania użytkownika
  • –mat-sys-outline dla obramowań i separatorów
  • kilka wariantów alternatywnych
  • kilka odcieni powierzchni tła

Możesz przejrzeć je w dokumentacji. Ogólna zasada jest taka, aby zastosować wybrany kolor do elementu i użyć odpowiadającego tokenu „mat-sys-on” dla tekstu, ikon oraz innych elementów wizualnych, aby zapewnić odpowiednią dostępność i czytelność. Na przykład: przycisk używa –mat-sys-primary jako koloru tła i –mat-sys-on-primary jako koloru tekstu na nim.

Typografia

Material Design definuje pięć kategorii typów fontów:

  • Body – Te style są używane do dłuższych fragmentów tekstu. Unikaj ekspresyjnych lub dekoracyjnych czcionek w tekście głównym, ponieważ mogą być trudniejsze do odczytania w mniejszych rozmiarach.
  • Display – Jako największy tekst na ekranie, style display są zarezerwowane dla krótkich, ważnych tekstów, szczególnie na dużych ekranach. W przypadku tekstu display warto rozważyć użycie bardziej ekspresyjnej czcionki, np. stylów odręcznych lub skryptowych.
  • Headline – Najlepiej nadają się do krótkich, wyróżniających się tekstów na mniejszych ekranach. Style headline są idealne do oznaczania głównych fragmentów tekstu lub kluczowych obszarów treści.
  • Label – Style label są mniejsze i mają charakter użytkowy. Są używane do tekstów wewnątrz komponentów lub bardzo małych tekstów w treści, takich jak podpisy. Na przykład przyciski zazwyczaj używają stylu „label large”.
  • Title – Mniejsze niż style headline, style title są używane do tekstów o średnim wyróżnieniu, które są stosunkowo krótkie. Są idealne do dzielenia drugoplanowych fragmentów tekstu lub drugorzędnych obszarów treści.

Każda kategoria fontów obejmuje trzy rozmiary: small, medium i large. Daje to w sumie 15 konfiguracji fontów, do których można uzyskać dostęp za pomocą tokenu –mat-sys-{{kategoria}}-{{rozmiar}}. Dodatkowo, konkretne części definicji fontu można uzyskać indywidualnie, dodając sufiksy, takie jak: “font”, “line-height”, “size”, “tracking” lub “weight”.

--mat-sys-body-medium: 400 0.875rem / 1.25rem Roboto, sans-serif;
--mat-sys-body-medium-font: Roboto, sans-serif;
--mat-sys-body-medium-line-height: 1.25rem;
--mat-sys-body-medium-size: 0.875rem;
--mat-sys-body-medium-tracking: 0.016rem;
--mat-sys-body-medium-weight: 400;

Podwyższenie

Podwyższenie (elevation) ma na celu zapewnienie poczucia głębi oraz organizację elementów w interfejsie:

  • umożliwia powierzchniom poruszanie się przed i za innymi powierzchniami,
  • odzwierciedla relacje przestrzenne,
  • skupia uwagę na najwyższym poziomie

Do tej pory można było wprowadzić ten efekt za pomocą klasy “mat-elevation-z”. Nowa implementacja definiuje sześć poziomów jako tokeny od –mat-sys-level0 do –mat-sys-level5, które są zdefiniowane jako style box-shadow w CSS.

Stosowanie tokenów systemowych

Jeśli używasz zmiennych CSS, wiesz, jak proste i elastyczne jest ich użycie. Możesz je zastosować w dowolnym miejscu i łatwo stylować aplikację.

body {
  background-color: var(--mat-sys-surface);
  color: var(--mat-sys-on-surface);
}

h1 {
  font: var(--mat-sys-headline-large);
}

h2 {
  font: var(--mat-sys-headline-medium);
}

Personalizacja tokenów

Komponenty Angular Material obsługują ukierunkowaną personalizację określonych tokenów za pomocą mixinów overrides. Pozwala to na precyzyjne modyfikowanie zmiennych motywu na poziomie systemowym, a także na poziomie poszczególnych komponentów.

To API zapewnia, że modyfikowane  tokeny są poprawnie zapisane, zapewniając walidację i kompatybilność wsteczną, jeśli tokeny zostaną dodane, przeniesione lub zmienione w przyszłych wersjach Angular Material.

Angular zdecydowanie odradza i nie wspiera bezpośredniego nadpisywania CSS komponentów poza API. Struktura DOM i klasy CSS komponentów są uważane za prywatne szczegóły implementacyjne, które mogą ulec zmianie bez uprzedzenia. Zamiast tego zmienne CSS używane przez komponenty  powinny być definiowane i modyfikowane za pomocą API overrides.

Tokeny systemowe

Jeśli potrzebujesz indywidualnie dostosować którykolwiek z opisanych powyżej tokenów, możesz zmienić jego wartość za pomocą mixinu theme-overrides:

@use “@angular/materialas mat;
@use “./theme-colorsas theme;

html {
  @include mat.theme(
    (
      color: theme.$primary-palette,
      typography: Poppins,
      density: 0,
    )
  );
}

.dark-container {
  @include mat.theme-overrides((
    primary-container: #001e2c,
    on-primary-container: #dbe3eb
  ));
}

lub bezpośrednio w mixinie theme:

@use “@angular/materialas mat;
@use “./theme-colorsas theme;

html {
  @include mat.theme(
    (
      color: theme.$primary-palette,
      typography: Poppins,
      density: 0,
    ), $overrides: (
      primary-container: #001e2c,
    )
  );
}

Tokeny komponentów

Każdy komponent Angular Material zawiera mixin overrides, który umożliwia modyfikowanie tokenów definiujących m.in. kolory, typografię i gęstość. Szczegółowe informacje na temat API overrides dla każdego komponentu, w tym lista dostępnych tokenów, które można modyfikować, znajdują się na odpowiedniej stronie dokumentacji w zakładce Styling.

@use “@angular/materialas mat;

:root {
  @include mat.dialog-overrides((
    content-padding: 3rem
  ))
}

.uppercase-button {
  @include mat.button-overrides(
    (
      filled-label-text-transform: uppercase,
      outlined-label-text-transform: uppercase,
      protected-label-text-transform: uppercase,
      text-label-text-transform: uppercase,
    )
  )
}

Podsumowanie

To kolejna istotna zmiana w Angular Material, ale uważam, że przejście w kierunku opisywania designu za pomocą tokenów to fantastyczne podejście. Zapewnia ono granularną kontrolę nad wyglądem aplikacji i oferuje niezwykłą elastyczność. Mam nadzieję, że ten artykuł pomoże Ci płynnie dostosować się do tych zmian lub zbudować atrakcyjny wizualnie design od podstaw.

Podziel się artykułem

Zapisz się na nasz newsletter

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