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.
Czego się dziś nauczysz
- Odczytasz datę, godzinę i kolor z pól
date,timeicolori 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.
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.
| Pole | Co widzi użytkownik | Co 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.
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.
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');
}
<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>
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?
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.
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));
});
}
<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>
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?
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.
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');
});
}
<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>
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?
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.
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');
}
<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>
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ć?
Zestawienie elementów
| Element | Znaczenie | Uwagi |
|---|---|---|
type="date", [min], [max] | kalendarz z zakresem | Wartość 'RRRR-MM-DD'. |
type="time" | godzina | 'GG:MM'. |
type="color", form-control-color | próbnik kolorów | '#rrggbb'. |
new Date('2026-10-16') | tekst → obiekt daty | Do 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] = tablica | rozpakowanie tablicy | TypeScript. |
String(n).padStart(2, '0') | zero z przodu | 7 → '07'. |
Math.floor, % | część całkowita, reszta | Minuty → godziny. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
signal(new Date()) jako wartość pola date | Pole jest puste. ngModel na polu daty oczekuje tekstu 'RRRR-MM-DD'. |
signal('16.10.2026') dla pola date | Pole 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ów | Ograniczenie nie działa — do atrybutu trafia opis sygnału zamiast daty. |
| Wynik dzielenia milisekund bez zaokrąglenia | Czasem 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
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).
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.
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.
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.
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.
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.
Co trzeba zapamiętać
datedaje 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 zpadStart(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”.