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.

[ ] binding public/ NgOptimizedImage atrybuty HTML
01

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.

zmienna .ts
aktywny = false
[disabled]
property binding
przycisk
nieaktywny w DOM
Właściwość DOM vs atrybut HTML – krótka różnica

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).

02

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.

html/składnia.html HTML
<!-- 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>
Podwójne cudzysłowy i apostrofy

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.

03

Kiedy [ ] a kiedy {{ }}?

To pytanie pojawia się u każdego początkującego. Zasada jest prosta i zawsze działa.

SytuacjaUżyjPrzykł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>
Pułapka z disabled

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.

04

Praktyczne przykłady

disabled i hidden

ts/app.component.ts TS
export class AppComponent {
  formularzWypelniony = false;
  czyAdmin            = true;
  wczytywanie         = true;
  imie                = '';
}
html/disabled-hidden.html HTML
<!-- 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] vs @if – kiedy które?

[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

ts/app.component.ts TS
export class AppComponent {
  czyAktywny = true;
  kolor      = 'red';
  rozmiar    = '24px';
}
html/class-style.html HTML
<!-- 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>
05

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)

struktura/stara-Angular17 DIR
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+)

struktura/nowa-Angular18plus DIR
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/ -->
Dlaczego tak działa?

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).

Organizacja plików w public/

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

06

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:

html/statyczny-obrazek.html HTML
<!-- 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]:

ts/app.component.ts TS
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' },
  ];
}
html/dynamiczny-obrazek.html HTML
<!-- 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>
Co gdy obrazek nie istnieje?

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"
>
07

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.

ts/app.component.ts TS
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';
}
html/ng-optimized.html HTML
<!-- 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
>
CechaZwykł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
Kiedy używać 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.

08

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 serwisu https://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] gdy dostepny = false
  • Odznakę „Dostępny” / „Brak” przez @if w odpowiednim kolorze Bootstrap badge
  • Tytuł (tooltip) na obrazku przez [title] wyświetlający nazwę i cenę produktu
Następny temat

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.