Masukkan Password
81 Dekorator Validasi
Lecture ini sebenarnya menggabungkan hampir semua konsep decorator yang sudah dipelajari sebelumnya untuk membuat sesuatu yang benar-benar berguna: validasi object berdasarkan decorator yang ditempel pada property.
Kalau disederhanakan, tujuan akhirnya adalah:
Kita menandai property dengan decorator seperti
@required,@minLength(5), atau@positiveNumber, lalu sebuah fungsivalidate()membaca decorator tersebut dan menentukan apakah object tersebut valid atau tidak.
1. Masalah yang ingin diselesaikan
Misalkan kita punya class User:
class User {
username: string;
age: number;
constructor(username: string, age: number) {
this.username = username;
this.age = age;
}
}
Kemudian kita membuat object:
const u1 = new User("John", 28);
Tidak ada masalah.
Tapi kita juga bisa melakukan:
const u2 = new User("", -30);
Padahal:
username = ""→ tidak validage = -30→ tidak valid
Tetapi TypeScript tetap mengizinkannya.
Kenapa?
Karena:
username: string;
age: number;
hanya memberitahu tipe data.
Artinya:
username harus string
age harus number
Bukan:
username tidak boleh kosong
age harus lebih dari 0
Jadi:
new User("", -30);
secara tipe masih valid:
"" → string ✅
-30 → number ✅
Tetapi secara aturan bisnis, tidak valid.
2. Kita ingin membuat aturan validasi
Kita ingin bisa menulis seperti ini:
class User {
@required
username: string;
@positiveNumber
age: number;
}
Artinya decorator menjadi semacam label aturan validasi.
Misalnya:
username
↓
@required
↓
"harus diisi"
Dan:
age
↓
@positiveNumber
↓
"harus angka positif"
Bahkan kita bisa punya:
class User {
@required
@minLength(5)
username: string;
@positiveNumber
age: number;
}
Artinya:
username
├── required
└── minLength(5)
age
└── positiveNumber
Nah, pertanyaannya:
Bagaimana
validate()tahu decorator apa saja yang dipasang pada setiap property?
Jawabannya:
kita simpan informasi decorator tersebut sebagai metadata sederhana.
3. Gambaran besar sistemnya
Sebelum masuk kode, pahami dulu arsitekturnya.
Kita punya:
CLASS User
│
┌─────────┴─────────┐
↓ ↓
username age
│ │
@required @positiveNumber
│ │
└─────────┬─────────┘
↓
validateObject
│
↓
validate(user)
│
↓
cek semua property
│
↓
cek validators
│
↓
true / false
Jadi decorator tidak langsung melakukan seluruh validasi.
Decorator bertugas mencatat:
“Property ini menggunakan validator apa?”
Kemudian validate() membaca catatan tersebut dan melakukan validasinya.
Ini konsep yang sangat penting.
4. Membuat User
Lecture dimulai dengan:
class User {
username: string;
age: number;
constructor(username: string, age: number) {
this.username = username;
this.age = age;
}
}
Kemudian:
const u1 = new User("John", 28);
Hasilnya:
u1
├── username → "John"
└── age → 28
Sedangkan:
const u2 = new User("", -30);
Hasilnya:
u2
├── username → ""
└── age → -30
Object berhasil dibuat, meskipun datanya tidak valid.
5. Membuat decorator required
Kita ingin bisa menulis:
@required
username: string;
Maknanya:
usernamewajib memiliki value.
Decorator ini adalah property decorator.
Seperti yang sudah dipelajari sebelumnya, property decorator menerima:
function required(
target: any,
propertyName: string
) {
}
Ada dua parameter:
target
Object prototype dari class.
propertyName
Nama property yang diberi decorator.
Misalnya:
@required
username: string;
maka:
propertyName
berisi:
"username"
6. minLength adalah decorator factory
Berbeda dengan required, kita ingin minLength menerima parameter.
Misalnya:
@minLength(5)
username: string;
Karena ada:
(5)
maka kita membutuhkan decorator factory.
Strukturnya:
function minLength(length: number) {
return function(
target: any,
propertyName: string
) {
};
}
Ada dua tahap.
Tahap 1
minLength(5)
memanggil factory.
Tahap 2
Factory menghasilkan decorator:
function(target, propertyName) {
}
Gambarnya:
@minLength(5)
│
↓
minLength(5)
│
↓
decorator function
│
↓
target + propertyName
7. positiveNumber
Kita juga punya:
function positiveNumber(
target: any,
propertyName: string
) {
}
Decorator ini digunakan seperti:
@positiveNumber
age: number;
Tujuannya nanti:
age > 0
harus terpenuhi.
8. Masalah berikutnya: bagaimana decorator menyimpan informasinya?
Ini bagian terpenting dari lecture.
Misalnya kita punya:
class User {
@required
username: string;
@positiveNumber
age: number;
}
Ketika validate(u1) dipanggil, kita ingin sistem mengetahui:
User
│
├── username
│ └── required
│
└── age
└── positiveNumber
Karena itu lecture membuat interface:
interface IValidator {
[prop: string]: {
[propKey: string]: string[];
};
}
Kelihatannya rumit, tapi sebenarnya bentuk datanya sederhana.
9. Memahami IValidator pelan-pelan
Kita mulai dari bentuk akhirnya.
Bayangkan:
validateObject
akan berisi seperti ini:
{
User: {
username: ["required"],
age: ["positiveNumber"]
}
}
Nah, sekarang kita bedah.
Level 1 — nama class
User
Ini adalah nama class yang sedang divalidasi.
Misalnya nanti ada:
class Product {}
maka bisa menjadi:
{
Product: {
...
}
}
Jadi struktur pertama:
Class name
↓
User
10. Level 2 — nama property
Di dalam User terdapat:
username
age
Jadi:
{
User: {
username: ...,
age: ...
}
}
11. Level 3 — validators
Untuk setiap property, kita simpan array.
Contoh:
{
User: {
username: ["required"],
age: ["positiveNumber"]
}
}
Kenapa array?
Karena satu property bisa memiliki lebih dari satu decorator.
Contoh:
@required
@minLength(5)
username: string;
Maka:
username: [
"required",
"minLength"
]
Jadi array memungkinkan kita menyimpan banyak validator.
12. Bentuk lengkap metadata
Misalnya:
class User {
@required
@minLength(5)
username: string;
@positiveNumber
age: number;
}
Metadata yang ingin kita hasilkan:
{
User: {
username: ["required", "minLength"],
age: ["positiveNumber"]
}
}
Ini sebenarnya inti seluruh sistem.
13. Memahami interface dengan diagram
Interface:
interface IValidator {
[prop: string]: {
[propKey: string]: string[];
};
}
Bacanya:
IValidator
│
├── [prop: string]
│ │
│ │ nama class
│ │
│ └── User
│
└── object
│
├── [propKey: string]
│ │
│ ├── username
│ │ └── ["required", "minLength"]
│ │
│ └── age
│ └── ["positiveNumber"]
│
└── ...
Jadi ada 3 level:
Level 1 → Class
Level 2 → Property
Level 3 → Validators
Atau:
User
↓
username
↓
["required", "minLength"]
14. Membuat validateObject
Kemudian lecture membuat:
let validateObject: IValidator = {};
Ini adalah tempat kita menyimpan metadata.
Awalnya:
{}
Setelah decorator dijalankan, misalnya:
{
User: {
username: ["required"],
age: ["positiveNumber"]
}
}
Jadi validateObject bisa dianggap sebagai buku catatan validator.
15. Analogi sederhana: buku catatan
Bayangkan decorator seperti guru yang memberikan label kepada siswa.
Misalnya ada siswa:
John
Dan guru mencatat:
John:
- wajib punya nama
- nama minimal 5 karakter
- umur harus positif
validateObject adalah buku catatan tersebut.
Bentuknya:
User
├── username
│ ├── required
│ └── minLength
│
└── age
└── positiveNumber
Nanti validate() membaca buku itu.
16. Implementasi required
Sekarang bagian menarik.
Kita punya:
function required(
target: any,
propName: string
) {
...
}
Kita ingin memasukkan:
User → username → required
Ke dalam:
validateObject
Lecture menggunakan:
target.constructor.name
untuk mendapatkan nama class.
17. Kenapa target.constructor.name?
Misalnya:
class User {
@required
username: string;
}
Pada property decorator, target untuk instance property adalah prototype dari User.
Secara konsep:
target
↓
User.prototype
Prototype tersebut mempunyai constructor:
target.constructor
yang menunjuk ke:
User
Kemudian:
target.constructor.name
menghasilkan:
"User"
Jadi:
target.constructor.name
= nama class.
18. Memasukkan data ke validateObject
Secara konsep kita melakukan:
validateObject[target.constructor.name] = {
[propName]: ["required"]
};
Kalau:
target.constructor.name
adalah:
User
dan:
propName
adalah:
username
maka hasilnya:
validateObject["User"] = {
username: ["required"]
};
Sehingga:
{
User: {
username: ["required"]
}
}
19. Kenapa menggunakan [propName]?
Perhatikan:
{
[propName]: ["required"]
}
Ini adalah computed property name.
Kalau:
propName = "username";
maka:
{
[propName]: ["required"]
}
menjadi:
{
username: ["required"]
}
Kalau:
propName = "age";
menjadi:
{
age: ["required"]
}
Jadi nama property ditentukan secara dinamis.
20. Implementasi minLength
Konsepnya sama.
function minLength(length: number) {
return function(
target: any,
propName: string
) {
// simpan minLength
};
}
Kita tahu:
length
berisi nilai:
5
Tetapi lecture pada tahap ini hanya menyimpan string:
"minLength"
Misalnya:
@minLength(5)
username: string;
akan dicatat sebagai:
username: ["minLength"]
Catatan penting: kalau benar-benar ingin melakukan validasi minLength(5), kita nantinya perlu menyimpan juga angka 5, bukan hanya string "minLength". Lecture ini baru membangun fondasi metadata; implementasi validate() akan dilanjutkan di lecture berikutnya.
21. positiveNumber juga sama
Misalnya:
@positiveNumber
age: number;
Decorator akan mencatat:
age: ["positiveNumber"]
Jadi sekarang:
{
User: {
username: ["required", "minLength"],
age: ["positiveNumber"]
}
}
22. Kenapa decorator hanya mencatat validator?
Ini desain yang bagus.
Kita bisa memisahkan dua pekerjaan:
Decorator
Bertugas:
“Property ini menggunakan validator apa?”
validate()
Bertugas:
“Apakah value property tersebut memenuhi validator?”
Jadi:
DECORATOR
↓
MENCATAT ATURAN
↓
validateObject
↓
VALIDATE()
↓
MEMERIKSA VALUE
Ini disebut pemisahan tanggung jawab.
23. Fungsi validate()
Lecture sebelumnya membuat:
function validate(obj: object): boolean {
return true;
}
Nantinya fungsi ini akan menjadi otak validasi.
Contoh:
if (!validate(u1)) {
alert("Invalid input");
} else {
console.log("User created successfully");
}
Artinya:
validate(u1)
│
├── true
│ ↓
│ User created successfully
│
└── false
↓
Invalid input
24. Apa yang harus dilakukan validate()?
Lecture menjelaskan bahwa validate() harus:
Langkah 1
Menerima object:
validate(u1)
Langkah 2
Mengetahui class object tersebut.
Misalnya:
u1 → User
Langkah 3
Mencari metadata:
validateObject["User"]
Langkah 4
Mendapatkan property:
username
age
Langkah 5
Untuk setiap property, cari validatornya.
Misalnya:
username → required
username → minLength
age → positiveNumber
Langkah 6
Ambil value sebenarnya dari object.
Misalnya:
u1.username
u1.age
Langkah 7
Jalankan validasi.
Misalnya:
username = "John"
required → PASS
minLength → PASS
age = 28
positiveNumber → PASS
Langkah 8
Kalau semuanya berhasil:
return true;
Kalau satu saja gagal:
return false;
25. Flow lengkapnya
Sekarang kita bisa melihat keseluruhan sistem:
CLASS User
│
┌───────┴────────┐
↓ ↓
username age
│ │
@required @positiveNumber
@minLength
│ │
└───────┬────────┘
↓
decorator berjalan
↓
validateObject
│
↓
┌───────────────────────────┐
│ User: │
│ username: [required, │
│ minLength] │
│ age: [positiveNumber] │
└───────────────────────────┘
│
↓
validate(u1)
│
↓
baca metadata User
│
↓
cek username & age
│
┌────┴────┐
↓ ↓
valid invalid
│ │
↓ ↓
true false
│ │
↓ ↓
"User created" "Invalid input"
26. Contoh object valid
Misalnya:
const u1 = new User("John", 28);
Metadata:
username:
required
minLength
age:
positiveNumber
Value:
username = "John"
age = 28
Pemeriksaan:
"John" tidak kosong → PASS
"John" minimal 5 karakter → PASS
28 > 0 → PASS
Maka:
validate(u1)
menghasilkan:
true
Kemudian:
console.log("User created successfully");
27. Contoh object invalid
Sekarang:
const u2 = new User("", -30);
Pemeriksaan:
username = ""
↓
required
↓
FAIL ❌
Bahkan sebelum mengecek age, kita sudah tahu object tidak valid.
Maka:
validate(u2)
bisa menghasilkan:
false
Kemudian:
alert("Invalid input");
28. Kenapa pendekatan ini menarik?
Tanpa decorator, kita mungkin menulis:
class User {
username: string;
age: number;
constructor(username: string, age: number) {
if (!username) {
throw new Error("Username required");
}
if (username.length < 5) {
throw new Error("Username too short");
}
if (age <= 0) {
throw new Error("Age must be positive");
}
this.username = username;
this.age = age;
}
}
Kalau class semakin besar, constructor bisa menjadi sangat panjang.
Dengan decorator:
class User {
@required
@minLength(5)
username: string;
@positiveNumber
age: number;
constructor(username: string, age: number) {
this.username = username;
this.age = age;
}
}
Aturan validasi terlihat langsung di property.
29. Decorator menjadi “label aturan”
Ini cara paling mudah mengingatnya.
@required
username: string;
Baca:
username punya aturan required.
@minLength(5)
username: string;
Baca:
username punya aturan minimum length 5.
@positiveNumber
age: number;
Baca:
age punya aturan positive number.
Jadi decorator bisa dianggap sebagai label/annotation untuk memberi informasi tambahan pada class.
30. Satu property bisa punya banyak validator
Misalnya:
class User {
@required
@minLength(5)
username: string;
}
Metadata:
{
User: {
username: [
"required",
"minLength"
]
}
}
Kemudian validate() harus menjalankan:
username
│
├── required
│
└── minLength
Semua harus lolos.
Ini konsep AND:
required = true
minLength = true
↓
VALID
Kalau:
required = true
minLength = false
↓
INVALID
31. Kenapa validator disimpan sebagai array?
Karena jumlah decorator tidak selalu satu.
Tanpa array:
username: "required"
hanya bisa menyimpan satu validator.
Dengan array:
username: [
"required",
"minLength",
"someOtherValidator"
]
kita bisa menambahkan banyak aturan.
Jadi:
Property
↓
Array of validators
adalah desain yang fleksibel.
32. Hubungan dengan decorator yang sudah dipelajari
Lecture ini sebenarnya menggabungkan materi-materi sebelumnya.
Kita sudah belajar:
Property decorator
@required
username: string;
Decorator menerima:
target
propertyName
Decorator factory
@minLength(5)
Factory:
function minLength(length: number) {
return function(target, propertyName) {
...
};
}
target.constructor
Kita gunakan untuk mengetahui class:
target.constructor.name
Multiple decorators
Satu property bisa punya:
@required
@minLength(5)
Metadata
Kita menyimpan informasi decorator:
validateObject
Validation
Kemudian:
validate(object)
membaca metadata tersebut.
Jadi lecture ini adalah semacam final project kecil untuk konsep decorators.
33. Hal yang sangat penting: decorator tidak otomatis memvalidasi value
Ini sering menjadi salah paham.
Kalau kita menulis:
@required
username: string;
bukan berarti TypeScript otomatis melakukan:
if (!username) ...
Decorator yang dibuat di lecture ini hanya mencatat:
username → required
Kemudian fungsi:
validate()
yang akan membaca informasi tersebut dan melakukan pemeriksaan.
Jadi:
@required
↓
"catat bahwa username wajib"
↓
validate()
↓
"oke, sekarang saya periksa username"
34. Bedakan TypeScript type checking dan validation
Ini juga penting.
TypeScript:
username: string
memeriksa:
Apakah username berupa string?
Sedangkan validator:
@required
memeriksa:
Apakah username diisi?
Dan:
@minLength(5)
memeriksa:
Apakah username minimal 5 karakter?
Sedangkan:
@positiveNumber
memeriksa:
Apakah age lebih besar dari 0?
Jadi:
| Sistem | Memeriksa |
|---|---|
| TypeScript | tipe data |
@required | wajib diisi |
@minLength(5) | panjang minimal |
@positiveNumber | angka positif |
validate() | menjalankan semua aturan |
35. Ada satu detail penting tentang minLength(5)
Lecture mencatat:
["minLength"]
Tetapi sebenarnya validator membutuhkan informasi 5.
Kalau hanya menyimpan:
"minLength"
fungsi validate() belum tahu:
Minimum length-nya berapa?
Misalnya:
@minLength(5)
vs:
@minLength(10)
keduanya akan terlihat sama kalau metadata hanya menyimpan:
"minLength"
Jadi dalam implementasi yang lebih lengkap, metadata biasanya perlu menyimpan konfigurasi juga, misalnya secara konsep:
{
User: {
username: [
{
validator: "minLength",
value: 5
}
]
}
}
Tetapi untuk mengikuti lecture, saat ini kita cukup memahami bahwa ia sedang membangun struktur metadata terlebih dahulu. Implementasi validasi sebenarnya akan dibahas di lecture berikutnya.
36. Kenapa ini berguna di aplikasi nyata?
Bayangkan aplikasi besar.
Kita punya:
User
Product
Order
Address
Payment
...
Masing-masing punya aturan validasi.
Tanpa sistem terstruktur, kita bisa memiliki banyak:
if (...)
if (...)
if (...)
if (...)
tersebar di berbagai tempat.
Dengan decorator:
class User {
@required
@minLength(5)
username: string;
@positiveNumber
age: number;
}
Aturan menjadi dekat dengan property yang bersangkutan.
Kemudian validator umum:
validate(user);
validate(product);
validate(order);
bisa digunakan untuk berbagai object.
Itulah alasan lecture mengatakan:
konsep decorator dapat digunakan untuk validating properties of a class.
37. Analogi formulir
Bayangkan sebuah formulir pendaftaran:
Username: [____________]
Age: [____________]
Kita memberikan label aturan:
Username
├── REQUIRED
└── MIN 5 CHARACTERS
Age
└── POSITIVE NUMBER
Decorator adalah label tersebut.
@required
@minLength(5)
username: string;
@positiveNumber
age: number;
Kemudian validate() bertindak sebagai petugas pemeriksa formulir.
Formulir
↓
Petugas lihat aturan
↓
Periksa setiap field
↓
Semua benar?
├── YES → diterima
└── NO → ditolak
38. Inti kode lecture
Secara konsep, struktur yang sedang dibangun adalah:
interface IValidator {
[prop: string]: {
[propKey: string]: string[];
};
}
const validateObject: IValidator = {};
Kemudian decorator:
function required(
target: any,
propName: string
) {
// simpan:
// Class → Property → "required"
}
Factory:
function minLength(length: number) {
return function(
target: any,
propName: string
) {
// simpan:
// Class → Property → "minLength"
};
}
Decorator lain:
function positiveNumber(
target: any,
propName: string
) {
// simpan:
// Class → Property → "positiveNumber"
}
Kemudian:
function validate(obj: object): boolean {
// baca metadata
// cek property
// jalankan validator
// return true / false
}
39. Cara berpikir validate() nanti
Kalau kamu ingin memahami lecture berikutnya dengan mudah, bayangkan validate() melakukan algoritma seperti ini:
validate(object)
│
↓
Cari nama class
│
↓
Cari metadata class
│
↓
Ambil semua property
│
↓
Untuk setiap property
│
├── ambil value object
│
├── ambil validator
│
└── jalankan validator
│
↓
Ada validator gagal?
│
┌─┴─┐
YES NO
│ │
false true
Ini adalah inti lecture berikutnya.
40. Ringkasan seluruh lecture
Kalau dibuat super sederhana:
Masalah
new User("", -30)
bisa dibuat walaupun datanya invalid.
Solusi
Gunakan decorator:
class User {
@required
@minLength(5)
username: string;
@positiveNumber
age: number;
}
Decorator mencatat aturan
Misalnya menghasilkan metadata:
{
User: {
username: ["required", "minLength"],
age: ["positiveNumber"]
}
}
validate() membaca metadata
validate(u1)
kemudian:
User
↓
username
↓
required + minLength
↓
cek value
age
↓
positiveNumber
↓
cek value
Hasil
Semua valid:
true
Ada satu saja yang invalid:
false
🧠 Cheat sheet
Yang paling penting untuk diingat dari lecture ini:
DECORATOR
↓
menandai property
↓
"property ini punya aturan X"
↓
simpan ke metadata
↓
validate()
↓
baca metadata
↓
periksa value
↓
true / false
Dan struktur metadata:
Class
↓
Property
↓
Validators
Contoh:
User
├── username
│ ├── required
│ └── minLength
│
└── age
└── positiveNumber
Mnemonic:
Decorator = mencatat aturan.
validate()= menjalankan aturan.
Dan satu lagi:
TypeScript type → “tipe datanya apa?” Validator → “nilainya memenuhi aturan bisnis atau tidak?”
Jadi inti besar dari seluruh section decorator ini sebenarnya adalah: decorator bukan hanya untuk logging atau mengubah behavior; decorator juga bisa digunakan untuk menambahkan metadata dan membangun sistem seperti validation, authorization, routing, dependency injection, dan lain-lain.