Dyrektywa ngClass w Angularze

1. Czym jest ngClass?

ngClass to dyrektywa Angulara umożliwiająca dynamiczne dodawanie lub usuwanie klas CSS na elementach HTML w zależności od stanu danych lub logiki aplikacji. Jest to bardzo przydatne narzędzie do kontrolowania prezentacji komponentów bezpośrednio z poziomu kodu TypeScript, co zwiększa elastyczność i możliwości dostosowania interfejsu użytkownika.

Przykłady zastosowań:

  • Zmiana kolorów elementów w zależności od statusu
  • Stylowanie formularzy w zależności od walidacji
  • Podświetlanie aktywnych elementów menu
  • Motywy jasny/ciemny
  • Animacje na podstawie interakcji użytkownika

2. Jak działa ngClass

Dyrektywa ngClass może przyjmować różne formy wejścia:

  • String: Jedna klasa lub wiele klas rozdzielonych spacjami
  • Array: Tablica nazw klas
  • Object: Obiekt, gdzie klucze to nazwy klas, a wartości to wyrażenia logiczne (true/false)

Ważne: Musisz zaimportować CommonModule w komponencie standalone!

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

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

3.Podstawowy przykład – Status użytkownika

Definicja komponentu

// status.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-status',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './status.component.html',
  styleUrl: './status.component.css'
})
export class StatusComponent {
  userStatus = 'active'; // 'active', 'inactive', 'pending'
  
  changeStatus(newStatus: string) {
    this.userStatus = newStatus;
  }
}

Szablon HTML z użyciem ngClass

<!-- status.component.html -->
<div class="container">
  <h2>Status użytkownika</h2>
  
  <!-- Użycie ngClass z obiektem -->
  <div [ngClass]="{
    'status-active': userStatus === 'active',
    'status-inactive': userStatus === 'inactive',
    'status-pending': userStatus === 'pending'
  }" class="status-badge">
    Status: {{ userStatus }}
  </div>
  
  <!-- Przyciski do zmiany statusu -->
  <button (click)="changeStatus('active')" class="btn btn-success">Aktywny</button>
  <button (click)="changeStatus('inactive')" class="btn btn-danger">Nieaktywny</button>
  <button (click)="changeStatus('pending')" class="btn btn-warning">Oczekujący</button>
</div>

Stylowanie komponentu

/* status.component.css */
.status-badge {
  padding: 12px 20px;
  border-radius: 25px;
  font-weight: bold;
  text-align: center;
  margin: 20px 0;
  transition: all 0.3s ease;
}

.status-active {
  background-color: #d4edda;
  color: #155724;
  border: 2px solid #28a745;
}

.status-inactive {
  background-color: #f8d7da;
  color: #721c24;
  border: 2px solid #dc3545;
}

.status-pending {
  background-color: #fff3cd;
  color: #856404;
  border: 2px solid #ffc107;
}

4. Krótkie przykłady zastosowań

Przykład 1: ngClass ze stringiem – motyw

export class ThemeComponent {
  currentTheme = 'dark';
  
  toggleTheme() {
    this.currentTheme = this.currentTheme === 'dark' ? 'light' : 'dark';
  }
}
<div [ngClass]="currentTheme" class="theme-container">
  <h3>Aktualny motyw: {{ currentTheme }}</h3>
  <button (click)="toggleTheme()">Zmień motyw</button>
</div>
.theme-container {
  padding: 20px;
  border-radius: 8px;
  transition: all 0.3s ease;
}

.dark {
  background-color: #343a40;
  color: #ffffff;
}

.light {
  background-color: #f8f9fa;
  color: #212529;
  border: 1px solid #dee2e6;
}

Przykład 2: ngClass z tablicą – oceny filmów

export class FilmRatingComponent {
  film = {
    title: 'Avengers',
    rating: 8.4,
    isNew: true,
    isPopular: true
  };
  
  getFilmClasses(): string[] {
    const classes = ['film-item'];
    if (this.film.rating >= 8.0) classes.push('high-rated');
    if (this.film.isNew) classes.push('new-release');
    if (this.film.isPopular) classes.push('popular');
    return classes;
  }
}
<div [ngClass]="getFilmClasses()">
  <h4>{{ film.title }}</h4>
  <p>Ocena: {{ film.rating }}/10</p>
</div>
.film-item {
  padding: 15px;
  border-radius: 8px;
  margin: 10px 0;
}

.high-rated { border-left: 5px solid #28a745; }
.new-release { box-shadow: 0 0 10px rgba(0, 123, 255, 0.5); }
.popular { background: linear-gradient(135deg, #fff3cd, #f8f9fa); }

Przykład 3: ngClass z obiektem – menu nawigacyjne

export class NavigationComponent {
  activeMenuItem = 'home';
  
  setActive(menuItem: string) {
    this.activeMenuItem = menuItem;
  }
  
  isActive(menuItem: string): boolean {
    return this.activeMenuItem === menuItem;
  }
}
<nav class="navigation">
  <a (click)="setActive('home')" 
     [ngClass]="{'nav-item': true, 'active': isActive('home')}">
    Home
  </a>
  <a (click)="setActive('films')" 
     [ngClass]="{'nav-item': true, 'active': isActive('films')}">
    Filmy
  </a>
  <a (click)="setActive('contact')" 
     [ngClass]="{'nav-item': true, 'active': isActive('contact')}">
    Kontakt
  </a>
</nav>
.nav-item {
  padding: 10px 15px;
  cursor: pointer;
  transition: background-color 0.3s;
}

.nav-item:hover {
  background-color: #e9ecef;
}

.active {
  background-color: #007bff;
  color: white;
}

Przykład 4: Przycisk z różnymi stanami

export class ButtonStatesComponent {
  buttonState = 'normal'; // 'normal', 'loading', 'success', 'error'
  
  simulateAction() {
    this.buttonState = 'loading';
    
    setTimeout(() => {
      this.buttonState = Math.random() > 0.5 ? 'success' : 'error';
      
      setTimeout(() => {
        this.buttonState = 'normal';
      }, 2000);
    }, 2000);
  }
}
<button 
  (click)="simulateAction()"
  [disabled]="buttonState === 'loading'"
  [ngClass]="{
    'btn': true,
    'btn-normal': buttonState === 'normal',
    'btn-loading': buttonState === 'loading',
    'btn-success': buttonState === 'success',
    'btn-error': buttonState === 'error'
  }">
  
  @if (buttonState === 'loading') {
    Ładowanie...
  } @else if (buttonState === 'success') {
    Sukces!
  } @else if (buttonState === 'error') {
    Błąd!
  } @else {
    Kliknij mnie
  }
</button>
.btn {
  padding: 10px 20px;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  transition: all 0.3s ease;
}

.btn-normal { background-color: #007bff; color: white; }
.btn-loading { background-color: #ffc107; color: #212529; }
.btn-success { background-color: #28a745; color: white; }
.btn-error { background-color: #dc3545; color: white; }

Przykład 5: Lista zadań z priorytetami

export class TaskListComponent {
  tasks = [
    { id: 1, name: 'Obejrzeć film', priority: 'low', completed: false },
    { id: 2, name: 'Napisać recenzję', priority: 'high', completed: true },
    { id: 3, name: 'Kupić bilety', priority: 'medium', completed: false }
  ];
  
  toggleTask(task: any) {
    task.completed = !task.completed;
  }
}
<div class="task-list">
  @for (task of tasks; track task.id) {
    <div 
      (click)="toggleTask(task)"
      [ngClass]="{
        'task-item': true,
        'completed': task.completed,
        'priority-high': task.priority === 'high',
        'priority-medium': task.priority === 'medium',
        'priority-low': task.priority === 'low'
      }">
      {{ task.name }}
    </div>
  }
</div>
.task-item {
  padding: 10px;
  margin: 5px 0;
  border-radius: 5px;
  cursor: pointer;
  border-left: 4px solid #ddd;
}

.completed {
  opacity: 0.6;
  text-decoration: line-through;
}

.priority-high { border-left-color: #dc3545; }
.priority-medium { border-left-color: #ffc107; }
.priority-low { border-left-color: #28a745; }

5. Ćwiczenia do samodzielnego wykonania

  1. Karta produktu – różne style w zależności od dostępności i ceny
  2. System powiadomień – różne kolory dla różnych typów alertów
  3. Galeria zdjęć – podświetlanie wybranego zdjęcia
  4. Quiz – kolorowanie odpowiedzi (poprawne/błędne)
  5. Dashboard pogodowy – różne ikony i kolory dla różnej pogody