Przejdź do treści
School IT / Przestrzeń do nauki

Data, czas i kolor: date, time, color

Przeglądarka ma wbudowany kalendarz, zegar i próbnik kolorów — wystarczy dobrać typ pola. Dla Angulara wszystkie trzy zwracają tekst w ściśle określonym formacie. Kto zna ten format, ten policzy liczbę dni, godzinę zakończenia albo pokoloruje plakat.

Angular 22 type=”date” type=”time” type=”color” [min] new Date padStart 45 min
CEL LEKCJI

Czego się dziś nauczysz

  • Odczytasz datę, godzinę i kolor z pól date, time i color i rozpoznasz ich format.
  • Sformatujesz datę z pola pipe’em date.
  • Porównasz dwie daty jako teksty i policzysz liczbę dni między nimi.
  • Ograniczysz wybór w kalendarzu atrybutami [min] i [max].
  • Wyliczysz godzinę zakończenia z godziny rozpoczęcia i czasu trwania.
  • Użyjesz wybranego koloru w wiązaniu [style.…].

Przygotowanie: lekcje 15–19 — znasz [style.x], pipe date, [(ngModel)] i pola liczbowe. Przewidywany czas: 45 min z zadaniami.

TEORIA

Wygląda jak data, ale to tekst

Pole type="date" pokazuje kalendarz i datę w polskim formacie, np. 16.10.2026. Ale do sygnału trafia tekst '2026-10-16' — rok, miesiąc, dzień, oddzielone myślnikami. Tak samo time daje '16:30', a color — '#0d6efd'. To nie obiekt Date, tylko tekst.

PoleCo widzi użytkownikCo trafia do sygnału
type="date"kalendarz, data po polsku'2026-10-16' albo '', gdy puste
type="time"godzina i minuty'16:30'
type="color"kolorowy kwadrat i próbnik'#ff8800' — zawsze małe litery, 7 znaków
type="datetime-local"data i godzina razem'2026-10-16T16:30'

Format RRRR-MM-DD ma ważną zaletę: daty porównane jak zwykłe teksty (<, >) dają poprawną kolejność — '2026-10-16' < '2026-11-02' jest prawdą. Do liczenia dni zamieniasz tekst na obiekt: new Date('2026-10-16'), a różnica dwóch dat w milisekundach to b.getTime() - a.getTime().

Dzień w milisekundach

1000 ms · 60 s · 60 min · 24 h = 86 400 000 ms. Wynik dzielenia zaokrąglaj Math.round — przez zmianę czasu letniego na zimowy w wybranym okresie może wyjść 4,958 dnia zamiast 5.

PRZYKŁAD 1

Data z kalendarza i pipe date

Wybierasz datę turnieju. Pokazujemy surowy tekst z sygnału i ten sam tekst sformatowany pipe’em date — rozumie on format RRRR-MM-DD. Polskie nazwy wymagają ustawień z lekcji 16.

src/app/nowa-23-1/demo.ts — pełny plik
import { Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { DatePipe } from '@angular/common';

@Component({
    selector: 'app-demo',
    imports: [FormsModule, DatePipe],
    templateUrl: './demo.html'
})
export class Demo {
    readonly data = signal('2026-10-16');
}
src/app/nowa-23-1/demo.html — pełny plik
<div class="p-3" style="width: 24rem">
  <label class="form-label" for="data">Data turnieju</label>
  <input id="data" name="data" type="date" class="form-control" [(ngModel)]="data">

  @if (data()) {
    <p class="mt-3 mb-1">W sygnale: <code>{{ data() }}</code></p>
    <p class="fs-5">{{ data() | date:'EEEE, d MMMM y' }}</p>
  } @else {
    <p class="mt-3 text-danger">Wybierz datę.</p>
  }
</div>
wynik na starcie
W sygnale: 2026-10-16
piątek, 16 października 2026

Skasuj datę (w Chrome: zaznacz pole i Delete) — sygnał dostaje pusty tekst i pojawia się czerwony komunikat.

Zatrzymaj się

Warunek @if (data()) nie ma żadnego porównania. Dlaczego pusty tekst '' jest w nim traktowany jak fałsz?

PRZYKŁAD 2

Od – do: liczba nocy i [min]

Termin obozu informatycznego: data przyjazdu i wyjazdu. Liczymy liczbę nocy, a pole wyjazdu nie pozwala wybrać daty wcześniejszej niż przyjazd.

src/app/nowa-23-2/demo.ts — pełny plik
import { Component, computed, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
    selector: 'app-demo',
    imports: [FormsModule],
    templateUrl: './demo.html'
})
export class Demo {
    readonly cenaZaNoc = 95;
    readonly przyjazd = signal('2027-07-05');
    readonly wyjazd = signal('2027-07-10');

    readonly noce = computed(() => {
        if (!this.przyjazd() || !this.wyjazd() || this.wyjazd() <= this.przyjazd()) {
            return 0;
        }
        const ms = new Date(this.wyjazd()).getTime() - new Date(this.przyjazd()).getTime();
        return Math.round(ms / (1000 * 60 * 60 * 24));
    });
}
src/app/nowa-23-2/demo.html — pełny plik
<div class="card m-3 p-3" style="width: 26rem">
  <label class="form-label" for="przyjazd">Przyjazd</label>
  <input id="przyjazd" name="przyjazd" type="date" class="form-control mb-2" [(ngModel)]="przyjazd">

  <label class="form-label" for="wyjazd">Wyjazd</label>
  <input id="wyjazd" name="wyjazd" type="date" class="form-control mb-3" [min]="przyjazd()" [(ngModel)]="wyjazd">

  @if (noce() > 0) {
    <p class="mb-1">Liczba nocy: <strong>{{ noce() }}</strong></p>
    <p class="fs-5 mb-0">Koszt: {{ noce() * cenaZaNoc }} zł</p>
  } @else {
    <p class="text-danger mb-0">Wyjazd musi być później niż przyjazd.</p>
  }
</div>
wynik na starcie
Liczba nocy: 5
Koszt: 475 zł

Otwórz kalendarz przy wyjeździe — dni przed datą przyjazdu są wyszarzone, bo [min] dostaje datę z pierwszego pola. Wpisz jednak wcześniejszą datę z klawiatury: obliczenie i tak się nie wykona, bo computed sprawdza kolejność dat.

Zatrzymaj się

Warunek this.wyjazd() <= this.przyjazd() porównuje dwa teksty. Dlaczego dla formatu RRRR-MM-DD daje to poprawny wynik, a dla DD.MM.RRRR by nie dało?

PRZYKŁAD 3

Godzina rozpoczęcia i zakończenia

Wybierasz godzinę rozpoczęcia warsztatu i czas trwania w minutach. Godzinę zakończenia liczymy, rozbijając tekst '16:30' na godziny i minuty.

src/app/nowa-23-3/demo.ts — pełny plik
import { Component, computed, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
    selector: 'app-demo',
    imports: [FormsModule],
    templateUrl: './demo.html'
})
export class Demo {
    readonly start = signal('16:30');
    readonly minuty = signal<number | null>(90);

    readonly koniec = computed(() => {
        if (!this.start() || this.minuty() === null) {
            return '';
        }
        const [g, m] = this.start().split(':').map(Number);
        const razem = g * 60 + m + this.minuty()!;
        const godz = Math.floor(razem / 60) % 24;
        const min = razem % 60;
        return String(godz).padStart(2, '0') + ':' + String(min).padStart(2, '0');
    });
}
src/app/nowa-23-3/demo.html — pełny plik
<div class="p-3" style="width: 24rem">
  <label class="form-label" for="start">Początek warsztatu</label>
  <input id="start" name="start" type="time" class="form-control mb-2" [(ngModel)]="start">

  <label class="form-label" for="minuty">Czas trwania (min)</label>
  <input id="minuty" name="minuty" type="number" min="15" step="15" class="form-control mb-3"
         [(ngModel)]="minuty">

  @if (koniec()) {
    <p class="fs-5">Warsztat: {{ start() }} – {{ koniec() }}</p>
  }
</div>
wynik
16:30 + 90 min  → Warsztat: 16:30 – 18:00
23:30 + 60 min  → Warsztat: 23:30 – 00:30

Trzy sztuczki z jednej linijki: split(':') tnie tekst na tablicę ['16', '30'], map(Number) zamienia każdy element na liczbę, a const [g, m] = … rozpakowuje tablicę do dwóch zmiennych. padStart(2, '0') dokleja zero z przodu, żeby 7 stało się 07. % 24 przenosi godzinę na następną dobę.

Zatrzymaj się

Bez padStart godzina 9:05 wyglądałaby jak 9:5. Które z dwóch wywołań padStart to naprawia?

PRZYKŁAD 4

Kolor z próbnika w stylu elementu

Projektujesz plakat: kolor tła i kolor tekstu z dwóch próbników, tytuł z pola tekstowego. Wartość koloru trafia prosto do wiązania [style.…]. form-control-color to wygląd próbnika w Bootstrapie.

src/app/nowa-23-4/demo.ts — pełny plik
import { Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
    selector: 'app-demo',
    imports: [FormsModule],
    templateUrl: './demo.html'
})
export class Demo {
    readonly tytul = signal('NOC ROBOTÓW');
    readonly tlo = signal('#212529');
    readonly tekst = signal('#ffc107');
}
src/app/nowa-23-4/demo.html — pełny plik
<div class="p-3">
  <div class="row g-4" style="max-width: 50rem">
    <div class="col-md-5">
      <label class="form-label" for="tytul">Tytuł</label>
      <input id="tytul" name="tytul" class="form-control mb-3" [(ngModel)]="tytul">

      <div class="d-flex gap-4">
        <div>
          <label class="form-label d-block" for="tlo">Tło</label>
          <input id="tlo" name="tlo" type="color" class="form-control form-control-color" [(ngModel)]="tlo">
        </div>
        <div>
          <label class="form-label d-block" for="tekst">Tekst</label>
          <input id="tekst" name="tekst" type="color" class="form-control form-control-color" [(ngModel)]="tekst">
        </div>
      </div>
      <p class="small text-secondary mt-3">tło: {{ tlo() }}, tekst: {{ tekst() }}</p>
      @if (tlo() === tekst()) {
        <p class="text-danger">Tekst zlewa się z tłem!</p>
      }
    </div>

    <div class="col-md-7">
      <div class="rounded p-5 text-center fw-bold fs-2"
           [style.background-color]="tlo()" [style.color]="tekst()">
        {{ tytul() }}
      </div>
    </div>
  </div>
</div>
wynik na starcie
tło: #212529, tekst: #ffc107
[ciemny plakat z żółtym napisem NOC ROBOTÓW]

Każda zmiana koloru od razu zmienia plakat. Ustaw oba kolory na ten sam — pojawi się czerwone ostrzeżenie.

Zatrzymaj się

#FFC107 i #ffc107 to ten sam kolor. Czy warunek tlo() === tekst() zadziała dla kolorów wpisanych w kodzie różną wielkością liter? Jak to zabezpieczyć?

ELEMENTY WBUDOWANE

Zestawienie elementów

ElementZnaczenieUwagi
type="date", [min], [max]kalendarz z zakresemWartość 'RRRR-MM-DD'.
type="time"godzina'GG:MM'.
type="color", form-control-colorpróbnik kolorów'#rrggbb'.
new Date('2026-10-16')tekst → obiekt datyDo liczenia.
data.getTime()milisekundy od 1970 r.Różnica dwóch dat.
tekst.split(':').map(Number)tekst → tablica liczb'16:30' → [16, 30].
const [a, b] = tablicarozpakowanie tablicyTypeScript.
String(n).padStart(2, '0')zero z przodu7 → '07'.
Math.floor, %część całkowita, resztaMinuty → godziny.
CZĘSTE BŁĘDY

Zanim utkniesz

ZapisProblem
signal(new Date()) jako wartość pola datePole jest puste. ngModel na polu daty oczekuje tekstu 'RRRR-MM-DD'.
signal('16.10.2026') dla pola datePole jest puste — przeglądarka nie rozumie polskiego formatu w wartości.
this.wyjazd() - this.przyjazd()Błąd TypeScriptu — nie odejmuje się tekstów. Najpierw new Date(…), potem getTime().
[min]="przyjazd" bez nawiasówOgraniczenie nie działa — do atrybutu trafia opis sygnału zamiast daty.
Wynik dzielenia milisekund bez zaokrągleniaCzasem 4,958 dnia zamiast 5. Zaokrąglaj Math.round.
new Date().toISOString().slice(0, 10) jako „dziś”To data w strefie UTC — tuż po północy bywa wczorajsza. Na egzaminie wystarczy, ale warto o tym wiedzieć.
ZADANIA

Zadania

ZAD 1Data urodzenia★☆☆

Pole daty urodzenia i napis „Urodziłeś się w …” z nazwą dnia tygodnia (pipe date). Dodaj [max], żeby nie dało się wybrać daty z przyszłości.

Kryterium sukcesu: nazwa dnia jest po polsku; w kalendarzu przyszłe dni są wyszarzone.

Podpowiedź

Dzisiejszą datę zbuduj w klasie: new Date().toISOString().slice(0, 10).

ZAD 2Kolor drużyny★☆☆

Pole tekstowe z nazwą drużyny i próbnik koloru. Pod spodem odznaka z nazwą drużyny w wybranym kolorze tła i zapis koloru szesnastkowo wielkimi literami.

Kryterium sukcesu: odznaka zmienia kolor przy każdym wyborze, a zapis jest wielkimi literami, np. #FF8800.

Podpowiedź

[style.background-color]="kolor()" i pipe uppercase.

ZAD 3Ile do wydarzenia★★☆

Pole daty wydarzenia. Pokaż: datę słownie, liczbę dni do wydarzenia i komunikat „Dziś!”, „Jutro!”, „Za X dni” albo „To już było”.

Kryterium sukcesu: dla dzisiejszej daty widać „Dziś!”, dla jutrzejszej „Jutro!”, dla przeszłej „To już było”.

Podpowiedź

Liczba dni jak w przykładzie 2, ale od dzisiejszej daty: new Date(dzisTekst), gdzie dzisTekst to dziś w formacie RRRR-MM-DD — wtedy obie daty mają północ.

ZAD 4Plan lekcji★★☆

Godzina rozpoczęcia pierwszej lekcji (time) i liczba lekcji (suwak 1–9). Wypisz pętlą listę lekcji z godzinami, przyjmując 45 minut lekcji i 10 minut przerwy, np. „3. 9:50–10:35”.

Kryterium sukcesu: lista ma tyle pozycji, ile wskazuje suwak, a godziny zgadzają się z zasadą 45 + 10.

Podpowiedź

Zamianę minut na tekst GG:MM z przykładu 3 wydziel do osobnej metody i wywołuj w computed, który buduje tablicę lekcji.

ZAD 5Rezerwacja sali★★★

Formularz rezerwacji sali komputerowej: data (nie wcześniejsza niż dziś), godzina od i godzina do (time). Pokaż czas rezerwacji w minutach i komunikaty błędów: „godzina do musi być późniejsza”, „rezerwacja najwyżej na 3 godziny”, „sala czynna 8:00–18:00”.

Kryterium sukcesu: dla poprawnych danych widać czas rezerwacji, dla błędnych — właściwy komunikat; data z przeszłości nie da się wybrać w kalendarzu.

Podpowiedź

Godziny w formacie GG:MM porównasz jak teksty ('09:00' < '10:30'); czas w minutach — zamiana jak w przykładzie 3.

ZAD 6Kartka okolicznościowa★★★

Generator kartki: imię adresata, data wydarzenia, kolor tła, kolor tekstu i rozmiar napisu (suwak). Kartka pokazuje „Zaproszenie dla …” oraz datę słownie i liczbę dni, które do niej zostały. Gdy kolory są takie same, kartka dostaje czerwoną ramkę i ostrzeżenie.

Kryterium sukcesu: każde pole od razu zmienia kartkę; przy dacie z przeszłości zamiast liczby dni pojawia się „to już było”.

Podpowiedź

Połączenie przykładów 1, 2 i 4 oraz suwaka z lekcji 19.

PODSUMOWANIE

Co trzeba zapamiętać

  • date daje tekst 'RRRR-MM-DD', time — 'GG:MM', color — '#rrggbb'; puste pole to ''.
  • Daty w tym formacie porównasz jak teksty; liczbę dni policzysz przez new Date(…).getTime().
  • [min] i [max] ograniczają kalendarz, ale kod i tak powinien sprawdzić dane.
  • Godzinę rozbijesz split(':').map(Number), a złożysz z padStart(2, '0').
  • Wartość z próbnika kolorów wpinasz wprost w [style.color] albo [style.background-color].

Sprawdź się: uczeń ustawił signal('16.10.2026') i pole daty jest puste. Co mu powiesz?

Dokumentacja: MDN — pole input type=”date”.

Postęp zapisuje się w tej przeglądarce.