Dyrektywa ngStyle w Angularze – dynamiczne stylowanie

1. Czym jest ngStyle?

Dyrektywa ngStyle to narzędzie w Angularze, które pozwala na dynamiczne ustawianie stylów CSS elementów na podstawie danych z komponentu. Dzięki niej możemy zmieniać wygląd elementów w zależności od różnych warunków.

Przykłady zastosowań:

  • Zmiana koloru tła w zależności od statusu (sukces, błąd, ostrzeżenie)
  • Dynamiczna szerokość paska postępu
  • Zmiana rozmiaru czcionki na podstawie preferencji użytkownika
  • Kolorowanie elementów w zależności od oceny filmu

Podstawowa różnica:

  • Zwykły CSS – style są statyczne, określone w pliku CSS
  • ngStyle – style są dynamiczne, zmieniają się w zależności od danych

2. Podstawowa składnia ngStyle

<div [ngStyle]="{'właściwość-css': 'wartość'}">Treść</div>

Gdzie:

  • [ngStyle] – nazwa dyrektywy w nawiasach kwadratowych
  • {'właściwość-css': 'wartość'} – obiekt JavaScript z parami klucz-wartość
  • Klucz to nazwa właściwości CSS (w cudzysłowach)
  • Wartość może być stringiem lub wyrażeniem z komponentu

Ważne: Musisz zaimportować CommonModule w komponencie!

import { CommonModule } from '@angular/common';

@Component({
  imports: [CommonModule], // Wymagane dla ngStyle
  // ...
})

3. Przykłady – od prostych do bardziej złożonych

Przykład 1: Podstawowe użycie

export class BasicStyleComponent {
  filmColor = 'blue';
  fontSize = 16;
  
  changeColor() {
    this.filmColor = this.filmColor === 'blue' ? 'red' : 'blue';
  }
}
<div class="container">
  <h3>Podstawowe stylowanie</h3>
  
  <!-- Jeden styl -->
  <p [ngStyle]="{'color': filmColor}">
    Ten tekst ma dynamiczny kolor: {{ filmColor }}
  </p>
  
  <!-- Wiele stylów -->
  <div [ngStyle]="{
    'background-color': filmColor,
    'font-size.px': fontSize,
    'padding': '10px',
    'border-radius': '5px'
  }">
    Blok z wieloma stylami
  </div>
  
  <button (click)="changeColor()" class="btn btn-primary">
    Zmień kolor
  </button>
</div>

Przykład 2: Stylowanie na podstawie warunków

export class ConditionalStyleComponent {
  filmRating = 7.5;
  
  getRatingColor(): string {
    if (this.filmRating >= 8.0) return 'green';
    if (this.filmRating >= 6.0) return 'orange';
    return 'red';
  }
  
  setRating(rating: number) {
    this.filmRating = rating;
  }
}
<div class="container">
  <h3>Stylowanie warunkowe</h3>
  
  <!-- Styl na podstawie metody -->
  <div [ngStyle]="{
    'color': getRatingColor(),
    'font-weight': filmRating >= 8.0 ? 'bold' : 'normal',
    'font-size': filmRating >= 8.0 ? '20px' : '16px'
  }">
    Ocena filmu: {{ filmRating }}/10
  </div>
  
  <!-- Przyciski do zmiany oceny -->
  <div class="mt-3">
    <button (click)="setRating(5.0)" class="btn btn-danger btn-sm me-2">5.0</button>
    <button (click)="setRating(7.0)" class="btn btn-warning btn-sm me-2">7.0</button>
    <button (click)="setRating(9.0)" class="btn btn-success btn-sm">9.0</button>
  </div>
</div>

Przykład 3: Pasek postępu

export class ProgressComponent {
  progress = 0;
  
  increaseProgress() {
    if (this.progress < 100) {
      this.progress += 10;
    }
  }
  
  resetProgress() {
    this.progress = 0;
  }
}
<div class="container">
  <h3>Dynamiczny pasek postępu</h3>
  
  <!-- Pasek postępu -->
  <div class="progress mb-3" style="height: 25px;">
    <div [ngStyle]="{
      'width.%': progress,
      'background-color': progress >= 100 ? 'green' : progress >= 50 ? 'blue' : 'orange',
      'transition': 'all 0.3s ease'
    }" class="progress-bar">
      {{ progress }}%
    </div>
  </div>
  
  <!-- Kontrolki -->
  <button (click)="increaseProgress()" class="btn btn-primary me-2">
    +10%
  </button>
  <button (click)="resetProgress()" class="btn btn-secondary">
    Reset
  </button>
</div>

Przykład 4: Lista z dynamicznym stylowaniem

export class FilmListComponent {
  filmy = [
    { id: 1, title: 'Avengers', rating: 8.4, isHovered: false },
    { id: 2, title: 'Batman', rating: 6.2, isHovered: false },
    { id: 3, title: 'Superman', rating: 7.8, isHovered: false }
  ];
  
  onMouseEnter(film: any) {
    film.isHovered = true;
  }
  
  onMouseLeave(film: any) {
    film.isHovered = false;
  }
}
<div class="container">
  <h3>Lista filmów z hover effects</h3>
  
  @for (film of filmy; track film.id) {
    <div 
      (mouseenter)="onMouseEnter(film)"
      (mouseleave)="onMouseLeave(film)"
      [ngStyle]="{
        'background-color': film.isHovered ? '#f0f8ff' : 'white',
        'transform': film.isHovered ? 'scale(1.02)' : 'scale(1)',
        'box-shadow': film.isHovered ? '0 4px 8px rgba(0,0,0,0.1)' : 'none',
        'transition': 'all 0.2s ease',
        'padding': '15px',
        'margin-bottom': '10px',
        'border-radius': '8px',
        'cursor': 'pointer'
      }">
      
      <h5>{{ film.title }}</h5>
      <span [ngStyle]="{
        'color': film.rating >= 8.0 ? 'green' : film.rating >= 7.0 ? 'orange' : 'red',
        'font-weight': 'bold'
      }">
        Ocena: {{ film.rating }}/10
      </span>
    </div>
  }
</div>

4. Alternatywne sposoby stylowania

Natywne bindowanie stylów (zalecane dla pojedynczych właściwości)

<!-- Zamiast ngStyle dla jednej właściwości -->
<div [ngStyle]="{'color': filmColor}">Film</div>

<!-- Lepiej użyć natywnego bindowania -->
<div [style.color]="filmColor">Film</div>

<!-- Z jednostkami -->
<div [style.font-size.px]="fontSize">Film</div>
<div [style.width.%]="progressPercentage">Pasek</div>

Porównanie różnych metod

export class StylingComparisonComponent {
  filmRating = 8.5;
  backgroundColor = 'lightblue';
  fontSize = 18;
}
<div class="container">
  <h3>Różne sposoby stylowania</h3>
  
  <!-- 1. ngStyle - dla wielu właściwości -->
  <div [ngStyle]="{
    'background-color': backgroundColor,
    'font-size.px': fontSize,
    'padding': '10px'
  }">
    ngStyle - wiele właściwości
  </div>
  
  <!-- 2. Natywne bindowanie - dla pojedynczych właściwości -->
  <div 
    [style.background-color]="backgroundColor"
    [style.font-size.px]="fontSize"
    [style.padding]="'10px'">
    Natywne bindowanie
  </div>
  
  <!-- 3. Warunkowe z @if -->
  @if (filmRating >= 8.0) {
    <div style="background-color: green; color: white; padding: 10px;">
      Wysokie oceny - statyczny styl
    </div>
  } @else {
    <div style="background-color: orange; color: white; padding: 10px;">
      Niższe oceny - statyczny styl
    </div>
  }
</div>

5.Kiedy używać ngStyle vs alternatywy

SytuacjaZalecane rozwiązaniePrzykład
Jedna właściwość[style.właściwość][style.color]="kolor"
Wiele właściwości[ngStyle][ngStyle]="obiektStyli"
Statyczne styleCSS classesclass="film-card"
Warunkowe klasy[class.nazwa][class.active]="isActive"
Złożona logikaMetody w komponencie[ngStyle]="getStyles()"

6. Ćwiczenia do samodzielnego wykonania

  1. Termometr ocen: Stwórz wizualny termometr pokazujący ocenę filmu
  2. Paleta kolorów: Pozwól użytkownikowi wybierać kolory motywu aplikacji
  3. Animowany licznik: Stwórz licznik który animuje się do docelowej wartości
  4. Interaktywne menu: Menu zmieniające kolory przy najechaniu myszką
  5. Karta profilu: Karta użytkownika z dynamicznymi stylami na podstawie statusu