1. Czym są elementy szablonowe?
W Angularze istnieją specjalne elementy, które pomagają w zarządzaniu strukturą szablonów HTML. Nie są to zwykłe elementy DOM – to narzędzia Angulara do kontrolowania tego, jak i gdzie treść jest wyświetlana.
Trzy główne elementy szablonowe:
<ng-template>– szablon, który nie jest wyświetlany domyślnie<ng-container>– grupuje elementy bez dodawania dodatkowego elementu DOM<ng-content>– pozwala na wstawianie treści do komponentu z zewnątrz
Dlaczego są potrzebne?
- Lepsze zarządzanie strukturą HTML
- Unikanie niepotrzebnych elementów DOM
- Tworzenie elastycznych komponentów
- Warunkowe wyświetlanie treści
2. ng-template – szablony do wielokrotnego użytku
<ng-template> to element, który definiuje szablon HTML, ale nie wyświetla go automatycznie. To jak „przepis” na treść, którą możemy użyć w różnych miejscach.
Podstawowe użycie ng-template
// film-template.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-film-template',
standalone: true,
imports: [CommonModule],
templateUrl: './film-template.component.html',
styleUrl: './film-template.component.css'
})
export class FilmTemplateComponent {
showFilm = false;
film = {
title: 'Avengers: Endgame',
year: 2019,
rating: 8.4
};
toggleFilm() {
this.showFilm = !this.showFilm;
}
}<!-- film-template.component.html -->
<div class="container">
<h2>Przykład ng-template</h2>
<button (click)="toggleFilm()" class="btn btn-primary">
@if (showFilm) {
Ukryj film
} @else {
Pokaż film
}
</button>
<!-- Używanie ng-template z @if (nowoczesny sposób) -->
@if (showFilm) {
<ng-container [ngTemplateOutlet]="filmTemplate"></ng-container>
} @else {
<ng-container [ngTemplateOutlet]="noFilmTemplate"></ng-container>
}
<!-- Definicja szablonów -->
<ng-template #filmTemplate>
<div class="alert alert-success">
<h4>{{ film.title }}</h4>
<p>Rok: {{ film.year }}</p>
<p>Ocena: {{ film.rating }}/10</p>
</div>
</ng-template>
<ng-template #noFilmTemplate>
<div class="alert alert-info">
<p>Kliknij przycisk, aby zobaczyć film!</p>
</div>
</ng-template>
</div>Przykład z listą filmów i szablonami
// film-list-templates.component.ts
export class FilmListTemplatesComponent {
filmy = [
{ id: 1, title: 'Avengers', rating: 8.4, type: 'akcja' },
{ id: 2, title: 'Titanic', rating: 7.8, type: 'romans' },
{ id: 3, title: 'Joker', rating: 8.5, type: 'dramat' },
{ id: 4, title: 'Scary Movie', rating: 6.2, type: 'komedia' }
];
selectedTemplate = 'card';
changeTemplate(templateName: string) {
this.selectedTemplate = templateName;
}
}<!-- film-list-templates.component.html -->
<div class="container">
<h2>Lista filmów z różnymi szablonami</h2>
<!-- Przełączanie szablonów -->
<div class="mb-3">
<button (click)="changeTemplate('card')"
[class.active]="selectedTemplate === 'card'"
class="btn btn-outline-primary me-2">
Widok karty
</button>
<button (click)="changeTemplate('list')"
[class.active]="selectedTemplate === 'list'"
class="btn btn-outline-primary me-2">
Widok listy
</button>
<button (click)="changeTemplate('table')"
[class.active]="selectedTemplate === 'table'"
class="btn btn-outline-primary">
Widok tabeli
</button>
</div>
<!-- Wyświetlanie filmów z wybranym szablonem -->
<div class="films-container">
@for (film of filmy; track film.id) {
@if (selectedTemplate === 'card') {
<ng-container [ngTemplateOutlet]="cardTemplate"
[ngTemplateOutletContext]="{film: film}">
</ng-container>
} @else if (selectedTemplate === 'list') {
<ng-container [ngTemplateOutlet]="listTemplate"
[ngTemplateOutletContext]="{film: film}">
</ng-container>
} @else if (selectedTemplate === 'table') {
<ng-container [ngTemplateOutlet]="tableRowTemplate"
[ngTemplateOutletContext]="{film: film}">
</ng-container>
}
}
</div>
<!-- Szablony -->
<ng-template #cardTemplate let-film="film">
<div class="card mb-3" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">{{ film.title }}</h5>
<p class="card-text">
<span class="badge bg-secondary">{{ film.type }}</span>
</p>
<p class="card-text">
<strong>Ocena:</strong> {{ film.rating }}/10
</p>
</div>
</div>
</ng-template>
<ng-template #listTemplate let-film="film">
<div class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1">{{ film.title }}</h6>
<small>{{ film.rating }}/10</small>
</div>
<small class="text-muted">{{ film.type }}</small>
</div>
</ng-template>
<ng-template #tableRowTemplate let-film="film">
<tr>
<td>{{ film.title }}</td>
<td>{{ film.type }}</td>
<td>{{ film.rating }}/10</td>
</tr>
</ng-template>
</div>3. ng-container – grupowanie bez dodatkowego DOM
<ng-container> to element, który pozwala grupować inne elementy bez dodawania dodatkowego elementu DOM. Jest niewidoczny w końcowym HTML.
Problem: niepotrzebne elementy DOM
<!-- PROBLEM: dodatkowy div tylko dla @if -->
<div *ngIf="showContent">
<h3>Tytuł</h3>
<p>Treść</p>
</div>Rozwiązanie: ng-container
<!-- ROZWIĄZANIE: brak dodatkowego elementu DOM -->
<ng-container *ngIf="showContent">
<h3>Tytuł</h3>
<p>Treść</p>
</ng-container>Praktyczne przykłady ng-container
// film-container.component.ts
export class FilmContainerComponent {
films = [
{ id: 1, title: 'Avengers', category: 'akcja', rating: 8.4, isNew: true },
{ id: 2, title: 'Titanic', category: 'romans', rating: 7.8, isNew: false },
{ id: 3, title: 'Joker', category: 'dramat', rating: 8.5, isNew: true }
];
showCategories = true;
selectedCategory = '';
toggleCategories() {
this.showCategories = !this.showCategories;
}
setCategory(category: string) {
this.selectedCategory = category;
}
}<!-- film-container.component.html -->
<div class="container">
<h2>Przykłady ng-container</h2>
<!-- Przykład 1: Grupowanie elementów bez dodatkowego DOM -->
<div class="mb-4">
<h3>Nagłówek sekcji</h3>
@if (showCategories) {
<ng-container>
<p>Wybierz kategorię filmu:</p>
<button (click)="setCategory('akcja')" class="btn btn-sm btn-outline-danger me-2">
Akcja
</button>
<button (click)="setCategory('romans')" class="btn btn-sm btn-outline-pink me-2">
Romans
</button>
<button (click)="setCategory('dramat')" class="btn btn-sm btn-outline-secondary">
Dramat
</button>
</ng-container>
}
<button (click)="toggleCategories()" class="btn btn-primary mt-2">
{{ showCategories ? 'Ukryj' : 'Pokaż' }} kategorie
</button>
</div>
<!-- Przykład 2: ng-container z @for -->
<div class="mb-4">
<h3>Lista filmów</h3>
@for (film of films; track film.id) {
<ng-container>
<!-- Sprawdzamy czy film pasuje do wybranej kategorii -->
@if (!selectedCategory || film.category === selectedCategory) {
<div class="card mb-2">
<div class="card-body">
<h5 class="card-title">
{{ film.title }}
@if (film.isNew) {
<span class="badge bg-success ms-2">NOWOŚĆ!</span>
}
</h5>
<p class="card-text">
<span class="badge bg-info">{{ film.category }}</span>
<span class="ms-2">Ocena: {{ film.rating }}/10</span>
</p>
</div>
</div>
}
</ng-container>
}
</div>
<!-- Przykład 3: Złożone warunki -->
<div class="mb-4">
<h3>Filmy z wysoką oceną</h3>
@for (film of films; track film.id) {
@if (film.rating >= 8.0) {
<ng-container>
<div class="alert alert-warning">
<strong>{{ film.title }}</strong> - Wysoko oceniony film!
@if (film.isNew) {
<br><small>Dodatkowo: to nowość w naszej kolekcji!</small>
}
</div>
</ng-container>
}
}
</div>
</div>4. ng-content – projekcja treści (content projection)
<ng-content> pozwala na wstawianie treści do komponentu z zewnątrz. To znaczy, że komponent może mieć „dziury”, które wypełniamy treścią podczas używania komponentu.
Podstawowy przykład ng-content
// film-card.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-film-card',
standalone: true,
template: `
<div class="card">
<div class="card-header">
<ng-content select="[slot=header]"></ng-content>
</div>
<div class="card-body">
<ng-content></ng-content>
</div>
<div class="card-footer">
<ng-content select="[slot=footer]"></ng-content>
</div>
</div>
`,
styles: [`
.card {
border: 1px solid #ddd;
border-radius: 8px;
margin: 16px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.card-header {
background: #f8f9fa;
padding: 16px;
border-bottom: 1px solid #ddd;
}
.card-body {
padding: 16px;
}
.card-footer {
background: #f8f9fa;
padding: 12px 16px;
border-top: 1px solid #ddd;
}
`]
})
export class FilmCardComponent { }Używanie komponentu z ng-content
// parent.component.ts
import { Component } from '@angular/core';
import { FilmCardComponent } from './film-card/film-card.component';
@Component({
selector: 'app-parent',
standalone: true,
imports: [FilmCardComponent],
templateUrl: './parent.component.html'
})
export class ParentComponent {
films = [
{ id: 1, title: 'Avengers: Endgame', year: 2019, rating: 8.4 },
{ id: 2, title: 'The Godfather', year: 1972, rating: 9.2 }
];
}<!-- parent.component.html -->
<div class="container">
<h2>Przykłady ng-content</h2>
<!-- Użycie 1: Podstawowa karta filmu -->
<app-film-card>
<h3 slot="header">🎬 Avengers: Endgame</h3>
<p>Finalna walka z Thanosem. Bohaterowie próbują odwrócić skutki Blip.</p>
<p><strong>Rok:</strong> 2019</p>
<p><strong>Ocena:</strong> 8.4/10</p>
<button slot="footer" class="btn btn-primary">Zobacz szczegóły</button>
</app-film-card>
<!-- Użycie 2: Inna zawartość -->
<app-film-card>
<h3 slot="header">🏆 The Godfather</h3>
<div>
<p>Klasyka kina gangsterskiego. Historia rodziny Corleone.</p>
<div class="badge bg-success">Klasyka</div>
<div class="badge bg-warning ms-2">Dramat</div>
</div>
<div slot="footer">
<button class="btn btn-success me-2">⭐ Dodaj do ulubionych</button>
<button class="btn btn-outline-secondary">Więcej info</button>
</div>
</app-film-card>
<!-- Użycie 3: Minimalna zawartość -->
<app-film-card>
<h4 slot="header">Film bez szczegółów</h4>
<p>Tylko podstawowe informacje.</p>
</app-film-card>
</div>5. Porównanie i kiedy używać
ng-template
Kiedy używać:
- Chcesz zdefiniować szablon do wielokrotnego użytku
- Potrzebujesz warunkowego wyświetlania z różnymi szablonami
- Tworzysz dynamiczne komponenty
Przykład zastosowania:
- Różne widoki dla różnych stanów (loading, error, success)
- Szablony dla różnych typów użytkowników
- Części formularza pokazywane warunkowo
ng-container
Kiedy używać:
- Potrzebujesz grupować elementy bez dodawania dodatkowego DOM
- Używasz dyrektyw strukturalnych (@if, @for) ale nie chcesz owijać w dodatkowy element
- Chcesz zastosować wiele dyrektyw na tej samej grupie elementów
Przykład zastosowania:
- Warunkowe wyświetlanie grupy elementów
- Optymalizacja DOM (mniej zbędnych elementów)
- Złożone warunki logiczne w szablonach
ng-content
Kiedy używać:
- Tworzysz komponenty wielokrotnego użytku
- Chcesz, żeby komponent mógł przyjmować różną treść
- Budujesz komponenty UI (karty, modale, panele)
Przykład zastosowania:
- Komponenty layout (header, sidebar, footer)
- Komponenty UI (przyciski, karty, modale)
- Komponenty owijające (kontenery, panele)
6. Najlepsze praktyki
✅ Co robić:
- Używaj ng-container zamiast dodatkowych div-ów dla grup elementów
- Nazywaj ng-template opisowo –
#loadingTemplatenie#temp1 - Używaj slotów w ng-content dla lepszej kontroli (
select="[slot=header]") - Twórz komponenty z ng-content dla lepszej reużywalności
- Dokumentuj sloty w komponentach dla innych deweloperów
❌ Czego unikać:
- Nadużywania ng-template dla prostych przypadków
- Tworzenia zbyt zagnieżdżonych struktur szablonowych
- Zapominania o dostępności w komponentach z ng-content
- Używania ng-container gdy potrzebujesz rzeczywisty element DOM
7. Ćwiczenia do samodzielnego wykonania
- Komponent karty produktu – z ng-content dla obrazka, tytułu i opisów
- System powiadomień – z ng-template dla różnych typów alertów
- Komponent tabs – z ng-content dla zawartości każdej zakładki
- Layout strony – z ng-content dla header, main, sidebar, footer
- Komponent formularza – z ng-template dla różnych typów pól