Property Binding i obrazki – [ ] i folder public
Property Binding to mechanizm wiązania zmiennych TypeScript z atrybutami elementów HTML.
Przy okazji poznasz nowe podejście do obrazków w Angular 18+ –
folder public/ bez konfigurowania pliku angular.json.
Czym jest Property Binding?
Interpolacja {{ }} wstawia wartość jako tekst między
znacznikami HTML. Ale co gdy chcemy ustawić wartość atrybutu
elementu – np. src obrazka, disabled przycisku
albo href linku? Tutaj wkracza Property Binding.
Property Binding to wiązanie właściwości DOM elementu HTML z wyrażeniem TypeScript. Zapisujemy go przez nawiasy kwadratowe wokół nazwy atrybutu.
HTML ma atrybuty (np. disabled,
src, href).
Przeglądarka tworzy z nich obiekt DOM z właściwościami
(properties). Property Binding w Angularze ustawia właściwość DOM –
dlatego [disabled]="true" faktycznie blokuje przycisk,
a disabled="{{ zmienna }}" może nie zadziałać poprawnie
(interpolacja daje string, a DOM oczekuje boolean).
Składnia – nawiasy kwadratowe
Umieszczasz nazwę atrybutu w nawiasach kwadratowych [ ],
a po znaku równości podajesz wyrażenie TypeScript
– zmienną, metodę lub dowolne wyrażenie.
<!-- Bez bindingu – statyczna wartość (zwykły HTML) --> <img src="logo.png"> <button disabled>Wyślij</button> <!-- Z bindingiem – wartość ze zmiennej TypeScript --> <img [src]="sciezkaDoLogo"> <button [disabled]="czyZablokowany">Wyślij</button> <!-- Wyrażenie TypeScript po prawej stronie = --> <button [disabled]="imie.length === 0">Dalej</button> <img [alt]="'Zdjęcie: ' + imie"> <a [href]="'https://school-it.pl/' + sciezka">Link</a>
Po znaku = w Property Binding obowiązują podwójne cudzysłowy
"...". Jeśli chcesz wpisać literał stringowy
(nie zmienną), użyj apostrofów wewnątrz:
[alt]="'Opis zdjęcia'".
Zewnętrzne "..." to składnia Angulara –
wewnętrzne '...' to string TypeScript.
Kiedy [ ] a kiedy {{ }}?
To pytanie pojawia się u każdego początkującego. Zasada jest prosta i zawsze działa.
| Sytuacja | Użyj | Przykład |
|---|---|---|
| Tekst między znacznikami HTML | {{ }} |
<p>{{ imie }}</p> |
| Wartość atrybutu elementu HTML | [ ] |
<img [src]="url"> |
❌ Interpolacja w atrybucie – problem
<!-- Daje string "true" lub "false" – przeglądarka i tak traktuje obecność atrybutu jako disabled! --> <button disabled="{{ czyAktywny }}"> Wyślij </button> <!-- Przycisk ZAWSZE będzie zablokowany! -->
✅ Property Binding – poprawnie
<!-- Przekazuje wartość boolean do DOM – przycisk aktywny gdy czyAktywny = false --> <button [disabled]="!czyAktywny"> Wyślij </button>
Atrybut HTML disabled działa inaczej niż myślisz.
W HTML sama obecność atrybutu blokuje element –
nieważne czy ma wartość "true" czy "false".
Dlatego disabled="{{ czyAktywny }}" zawsze blokuje przycisk
(Angular wstawia string i atrybut jest obecny). Jedynym rozwiązaniem
jest [disabled]="!czyAktywny" – tu Angular ustawia
właściwość DOM bezpośrednio jako true/false.
Praktyczne przykłady
disabled i hidden
export class AppComponent { formularzWypelniony = false; czyAdmin = true; wczytywanie = true; imie = ''; }
<!-- Przycisk aktywny tylko gdy formularz wypełniony --> <button class="btn btn-primary" [disabled]="!formularzWypelniony" > Wyślij </button> <!-- Panel widoczny tylko dla admina --> <div class="alert alert-info" [hidden]="!czyAdmin"> Panel administratora </div> <!-- Przycisk "Wyślij" nieaktywny gdy imię jest puste --> <input #imieInput type="text" class="form-control" (input)="''"> <button class="btn btn-success mt-2" [disabled]="imieInput.value.length === 0" > Zapisz </button>
[hidden]="warunek" ustawia CSS display:none
– element istnieje w DOM, tylko jest niewidoczny.
@if (warunek) całkowicie usuwa element z DOM.
Zasada: jeśli element często się przełącza (np. zakładki),
użyj [hidden] – jest szybszy. Jeśli element rzadko
się pojawia lub zawiera dużo danych, użyj @if.
Dynamiczna klasa CSS i styl
export class AppComponent { czyAktywny = true; kolor = 'red'; rozmiar = '24px'; }
<!-- Warunkowa klasa Bootstrap --> <span [class.text-success]="czyAktywny">Status</span> <!-- Gdy czyAktywny = true → dodaje klasę text-success --> <!-- Gdy czyAktywny = false → usuwa klasę text-success --> <!-- Dynamiczny kolor tekstu --> <p [style.color]="kolor">Ten tekst ma kolor ze zmiennej</p> <!-- Dynamiczny rozmiar czcionki --> <p [style.fontSize]="rozmiar">Ten tekst ma rozmiar ze zmiennej</p> <!-- Tytuł (tooltip) ze zmiennej --> <button class="btn btn-info" [title]="'Kliknij aby: ' + akcja"> Info </button>
Folder public – nowe podejście do plików statycznych
W Angular 18+ projekt tworzony przez ng new ma folder
public/ zamiast starego src/assets/.
Pliki z public/ trafiają wprost do korzenia aplikacji –
bez konfigurowania czegokolwiek w angular.json.
Stara struktura (do Angular 17)
moja-aplikacja/ └── src/ ├── assets/ ← obrazki wrzucane tutaj │ └── logo.png └── app/ <!-- W HTML odwoływało się przez: --> <img src="assets/logo.png"> <!-- Wymagało też wpisu w angular.json: --> "assets": ["src/favicon.ico", "src/assets"]
Nowa struktura (Angular 18+)
moja-aplikacja/ ├── public/ ← TUTAJ wrzucamy obrazki (obok src, nie w src) │ ├── logo.png │ └── images/ │ ├── hero.jpg │ └── uczen.jpg ├── src/ │ └── app/ └── angular.json ← nie trzeba tu nic zmieniać! <!-- W HTML odwołujemy się przez ścieżkę od korzenia: --> <img src="logo.png"> <!-- plik w public/ --> <img src="images/hero.jpg"> <!-- plik w public/images/ -->
Angular podczas budowania (ng serve / ng build)
kopiuje całą zawartość folderu public/ wprost do korzenia
serwowanej aplikacji. Plik public/logo.png jest dostępny
pod adresem http://localhost:4200/logo.png.
To identyczne zachowanie jak folder public/ w innych
nowoczesnych narzędziach (Vite, Next.js, Nuxt).
Możesz tworzyć podfoldery wewnątrz public/ –
tak jak chcesz. Popularna konwencja:
public/ ├── images/ ← zdjęcia, grafiki ├── icons/ ← ikony SVG, PNG ├── fonts/ ← czcionki └── data/ ← pliki JSON z danymi
Plik public/images/uczen.jpg → ścieżka images/uczen.jpg
Obrazki w szablonie
Skoro wiemy już gdzie trzymamy obrazki (folder public/)
i jak działa Property Binding, możemy połączyć obie rzeczy.
Statyczna ścieżka – zwykły HTML
Gdy ścieżka do obrazka jest stała i nie zmienia się, używamy
zwykłego atrybutu src bez bindingu:
<!-- Plik: public/logo.png --> <img src="logo.png" alt="Logo szkoły" class="img-fluid"> <!-- Plik: public/images/hero.jpg --> <img src="images/hero.jpg" alt="Nagłówek strony" class="img-fluid rounded" width="800" height="400" >
Dynamiczna ścieżka – Property Binding [src]
Gdy ścieżka pochodzi ze zmiennej TypeScript – np. zdjęcie ucznia
zmienia się w zależności od tego kto jest zalogowany –
używamy [src]:
export class AppComponent { aktualnyUczen = { imie: 'Anna Kowalska', klasa: '3TI', zdjecie: 'images/anna.jpg' // ścieżka relative do public/ }; produkty = [ { id: 1, nazwa: 'Laptop', zdjecie: 'images/laptop.jpg' }, { id: 2, nazwa: 'Myszka', zdjecie: 'images/myszka.jpg' }, { id: 3, nazwa: 'Klawiatura', zdjecie: 'images/klawiatura.jpg' }, ]; }
<!-- Profil ucznia – zdjęcie i opis ze zmiennych --> <div class="card" style="width: 200px"> <img [src]="aktualnyUczen.zdjecie" [alt]="'Zdjęcie: ' + aktualnyUczen.imie" class="card-img-top" > <div class="card-body"> <h5>{{ aktualnyUczen.imie }}</h5> <p>Klasa: {{ aktualnyUczen.klasa }}</p> </div> </div> <!-- Lista produktów ze zdjęciami przez @for --> <div class="row"> @for (produkt of produkty; track produkt.id) { <div class="col-md-4"> <div class="card mb-3"> <img [src]="produkt.zdjecie" [alt]="produkt.nazwa" class="card-img-top" > <div class="card-body"> <h6>{{ produkt.nazwa }}</h6> </div> </div> </div> } </div>
Jeśli plik nie istnieje, przeglądarka wyświetli złamany obrazek.
Możesz obsłużyć ten przypadek przez zdarzenie (error)
i podmienić ścieżkę na obrazek zastępczy:
<img [src]="uczen.zdjecie" (error)="uczen.zdjecie = 'images/brak-zdjecia.png'" alt="Zdjęcie ucznia" >
NgOptimizedImage – zoptymalizowane obrazki
Angular od wersji 15 dostarcza dyrektywę NgOptimizedImage,
która automatycznie stosuje najlepsze praktyki wydajnościowe dla obrazków.
W Angular 21 jest to zalecane podejście dla wszystkich obrazków.
Zamiast atrybutu src używamy atrybutu ngSrc.
Wymaga to zaimportowania dyrektywy w komponencie.
import { Component } from '@angular/core'; import { NgOptimizedImage } from '@angular/common'; @Component({ selector: 'app-root', standalone: true, imports: [NgOptimizedImage], templateUrl: './app.component.html' }) export class AppComponent { zdjecieUrl = 'images/hero.jpg'; }
<!-- Obrazek statyczny z ngSrc --> <img ngSrc="images/hero.jpg" alt="Baner strony" width="800" height="400" > <!-- Obrazek dynamiczny z [ngSrc] --> <img [ngSrc]="zdjecieUrl" alt="Zdjęcie" width="300" height="200" > <!-- Obrazek priorytetowy (ładowany pierwszy – np. logo) --> <img ngSrc="logo.png" alt="Logo" width="200" height="60" priority >
| Cecha | Zwykły <img src> | NgOptimizedImage |
|---|---|---|
| Lazy loading | Ręczne (loading="lazy") |
Automatyczne dla obrazków poniżej fold |
| Priorytet ładowania | Ręczne (fetchpriority) |
Atrybut priority |
| width i height | Opcjonalne | Wymagane – zapobiega layout shift |
| Ostrzeżenia deweloperskie | Brak | Angular ostrzega o brakujących wymiarach |
| Import wymagany? | Nie | Tak – NgOptimizedImage |
Dla obrazków w treści strony (zdjęcia produktów, galerie, banery)
używaj NgOptimizedImage – poprawia wydajność strony
bez żadnego wysiłku. Dla małych ikonek i logotypów w nawigacji
zwykły <img> jest wystarczający.
Częste błędy
❌ Błąd 1 – Stara ścieżka assets/ w Angular 18+
❌ Stara ścieżka – 404
<!-- Plik jest w public/logo.png ale piszemy po staremu: --> <img src="assets/logo.png"> <!-- Błąd 404 – plik nie istnieje pod tą ścieżką -->
✅ Nowa ścieżka – bez assets/
<!-- Plik: public/logo.png → ścieżka: logo.png --> <img src="logo.png"> <!-- Plik: public/images/logo.png → ścieżka: images/logo.png --> <img src="images/logo.png">
❌ Błąd 2 – Interpolacja zamiast [src]
❌ Interpolacja w src – może dać ostrzeżenie
<!-- Angular może ostrzec o niebezpiecznym URL --> <img src="{{ uczen.zdjecie }}">
✅ Property Binding [src]
<!-- Poprawny sposób dla dynamicznych ścieżek --> <img [src]="uczen.zdjecie">
❌ Błąd 3 – NgOptimizedImage bez width i height
❌ Brak wymiarów – błąd w konsoli
<!-- Angular zgłosi błąd w konsoli przeglądarki --> <img ngSrc="images/hero.jpg" alt="Hero">
✅ Z wymaganymi wymiarami
<img ngSrc="images/hero.jpg" alt="Hero" width="800" height="400" >
Zadanie – galeria kart produktów
Stwórz galerię kart produktów łącząc Property Binding,
folder public/ i @for.
Przygotowanie:
- W folderze
public/images/umieść 3–4 zdjęcia produktów (możesz pobrać dowolne obrazki z internetu lub użyć placeholderów z serwisuhttps://placehold.co/300x200)
W klasie zdefiniuj tablicę produktów z polami:
id,nazwa,cena,zdjecie,dostepny(boolean)
W szablonie wyświetl:
- Karty Bootstrap w układzie siatki (
row+col-md-4) - Zdjęcie produktu przez
[src]i[alt] - Przycisk „Dodaj do koszyka” zablokowany przez
[disabled]gdydostepny = false - Odznakę „Dostępny” / „Brak” przez
@ifw odpowiednim kolorze Bootstrap badge - Tytuł (tooltip) na obrazku przez
[title]wyświetlający nazwę i cenę produktu
W następnym materiale poznamy [ngStyle] i [ngClass] – dyrektywy pozwalające dynamicznie zmieniać style i klasy CSS wielu właściwości naraz w bardziej elastyczny sposób.