Masukkan Password
78 Metode & Parameter Dekorator
Di lecture ini kita masuk ke dua jenis decorator baru dalam TypeScript:
- Method decorator → decorator yang ditempel pada sebuah method.
- Method parameter decorator → decorator yang ditempel pada parameter sebuah method.
Kalau sebelumnya kita sudah belajar:
@decoratorpada class@decoratorpada property@decoratorpada accessor (get/set)
sekarang kita belajar decorator pada method dan parameter method.
1. Gambaran besar dulu
Misalnya kita punya class seperti ini:
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
calculateAge(dateOfBirth: string) {
// ...
}
}
Di sini ada:
Person
│
├── name → property
│
├── constructor() → constructor
│
└── calculateAge() → method
│
└── dateOfBirth → parameter
Jadi ada dua tempat yang sekarang bisa kita kasih decorator:
Method
@Logger
calculateAge(dateOfBirth: string) {
}
Parameter
calculateAge(
@Param dateOfBirth: string
) {
}
Keduanya sama-sama decorator, tetapi parameter yang diterima oleh decorator function berbeda.
2. Method Parameter Decorator
Kita mulai dari yang ditempel pada parameter.
Misalnya:
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
calculateAge(@Param dateOfBirth: string) {
// ...
}
}
Perhatikan:
@Param dateOfBirth: string
Decorator @Param ditempel bukan pada method, tetapi pada:
dateOfBirth
Jadi Param adalah parameter decorator.
3. Decorator sebenarnya hanyalah function
Kita membuat:
function Param(
target: any,
paramName: string,
index: number
) {
console.log("Param decorator called");
}
Yang menarik adalah:
Kenapa
Param()menerima 3 parameter?
Karena parameter decorator memang mendapatkan 3 informasi dari TypeScript.
Bentuk sederhananya:
Parameter Decorator
│
├── target
├── methodName
└── parameterIndex
Mari kita bahas satu per satu.
4. Parameter pertama: target
function Param(
target: any,
paramName: string,
index: number
) {
}
Parameter pertama:
target
Ini adalah object/function tempat method tersebut berada.
Untuk instance method, target biasanya menunjuk ke:
Person.prototype
Misalnya:
class Person {
calculateAge(@Param dateOfBirth: string) {
}
}
Maka secara konsep:
Person
│
└── prototype
│
└── calculateAge()
│
└── dateOfBirth
target mengarah ke prototype tersebut.
Makanya lecture mengatakan:
target akan menjadi prototype object atau constructor function.
5. Kenapa prototype?
Ini sebenarnya berkaitan dengan bagaimana JavaScript menyimpan method instance.
Misalnya:
const person1 = new Person("Budi");
const person2 = new Person("Andi");
Kedua object tersebut tidak perlu memiliki salinan method calculateAge masing-masing.
Method-nya berada di:
Person.prototype
Secara konsep:
Person.prototype
│
└── calculateAge()
Kemudian:
person1 ──┐
├──> Person.prototype.calculateAge
person2 ──┘
Jadi ketika decorator bekerja pada instance method, target-nya adalah prototype.
6. Parameter kedua: nama method
Ini bagian yang agak mengecoh.
Di lecture awalnya disebut:
paramName
Tetapi kemudian lecturer menyadari bahwa nama tersebut kurang tepat.
Karena parameter kedua bukan nama parameter.
Parameter kedua adalah:
nama method tempat parameter tersebut berada.
Misalnya:
class Person {
calculateAge(@Param dateOfBirth: string) {
}
}
Kita mungkin mengira:
paramName
isinya:
"dateOfBirth"
Tetapi sebenarnya:
"calculateAge"
Jadi:
function Param(
target: any,
methodName: string,
index: number
) {
console.log(methodName);
}
Output:
calculateAge
7. Kenapa yang diberikan adalah nama method?
Karena decorator tersebut sebenarnya memberi informasi:
“Parameter yang diberi decorator ini berada di method apa?”
Misalnya:
class Person {
calculateAge(@Param dateOfBirth: string) {
}
}
Informasinya:
target
↓
Person.prototype
methodName
↓
calculateAge
index
↓
0
Jadi decorator mengetahui:
“Ada parameter yang diberi decorator pada method
calculateAge, dan parameter tersebut berada pada index 0.”
8. Parameter ketiga: index
Parameter ketiga:
index: number
Ini adalah posisi parameter.
Contoh:
calculateAge(
@Param dateOfBirth: string
)
Hanya ada satu parameter.
Posisinya:
dateOfBirth
↓
index 0
Kenapa 0?
Karena JavaScript/TypeScript menggunakan zero-based index.
Artinya:
parameter pertama → 0
parameter kedua → 1
parameter ketiga → 2
parameter keempat → 3
9. Contoh dengan banyak parameter
Misalnya:
calculateSomething(
name: string,
@Param age: number,
city: string
) {
}
Posisinya:
name
↓
index 0
age
↓
index 1
city
↓
index 2
Karena decorator dipasang di:
@Param age
maka:
index === 1
10. Jadi parameter decorator punya 3 informasi
Untuk parameter decorator:
function Param(
target: any,
methodName: string,
index: number
) {
}
Artinya:
| Parameter | Isi | Contoh |
|---|---|---|
target | object tempat method berada | Person.prototype |
methodName | nama method | "calculateAge" |
index | posisi parameter | 0 |
Cara mengingatnya:
Parameter decorator = Target + Method Name + Index
11. Contoh lengkap parameter decorator
Misalnya:
function Param(
target: any,
methodName: string,
index: number
) {
console.log("Param decorator called");
console.log("Target:", target);
console.log("Method name:", methodName);
console.log("Index:", index);
}
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
calculateAge(
@Param dateOfBirth: string
) {
// ...
}
}
Saat TypeScript memproses decorator tersebut, secara konsep kita mendapatkan:
Param decorator called
Target:
Person.prototype
Method name:
calculateAge
Index:
0
12. Sekarang kita masuk ke Method Decorator
Sekarang berbeda.
Tadi:
calculateAge(
@Param dateOfBirth: string
)
@Param dipasang pada parameter.
Sekarang kita ingin:
@Logger
calculateAge(dateOfBirth: string) {
}
@Logger dipasang langsung pada method.
Jadi:
@Logger
↓
calculateAge()
Ini disebut method decorator.
13. Membuat Method Decorator
Kita buat:
function Logger(
target: any,
methodName: string,
descriptor: PropertyDescriptor
) {
}
Method decorator juga menerima 3 parameter.
Tetapi berbeda dari parameter decorator.
Method decorator:
target
methodName
descriptor
Sedangkan parameter decorator:
target
methodName
index
Perhatikan bedanya:
Parameter Decorator
↓
target + methodName + index
Method Decorator
↓
target + methodName + descriptor
Ini sangat penting.
14. Parameter pertama method decorator: target
Sama seperti sebelumnya:
target
Untuk instance method:
target = Person.prototype
Misalnya:
class Person {
@Logger
calculateAge(dateOfBirth: string) {
}
}
Karena calculateAge() adalah instance method, maka:
target
↓
Person.prototype
15. Bagaimana kalau method-nya static?
Ini juga penting.
Misalnya:
class Person {
@Logger
static calculateAge(dateOfBirth: string) {
}
}
Sekarang calculateAge adalah:
static method
Maka target-nya bukan lagi:
Person.prototype
melainkan:
Person
atau secara konsep:
constructor function / class
Jadi:
Instance method
class Person {
@Logger
calculateAge() {}
}
Target:
Person.prototype
Static method
class Person {
@Logger
static calculateAge() {}
}
Target:
Person
16. Cara mudah mengingat target
Gunakan aturan:
Instance → prototype Static → class/constructor
Visualisasinya:
INSTANCE METHOD
Person
│
└── prototype
│
└── calculateAge()
↑
target
Sedangkan:
STATIC METHOD
Person
│
└── calculateAge()
↑
target
17. Parameter kedua: methodName
Parameter kedua:
methodName: string
Ini adalah nama method yang diberi decorator.
Misalnya:
@Logger
calculateAge(dateOfBirth: string) {
}
Maka:
methodName === "calculateAge"
Jadi sangat sederhana:
@Logger
calculateAge()
│
└──────> methodName = "calculateAge"
18. Parameter ketiga: descriptor
Nah, ini bagian paling penting dari method decorator.
Parameter ketiga:
descriptor: PropertyDescriptor
PropertyDescriptor adalah object yang berisi informasi tentang property/method tersebut.
Misalnya secara konsep:
{
configurable: true,
enumerable: false,
value: function calculateAge(...) {
...
},
writable: true
}
Yang paling penting untuk kita pahami sekarang adalah:
descriptor.value
19. Apa itu descriptor.value?
Bayangkan kita punya:
class Person {
@Logger
calculateAge(dateOfBirth: string) {
console.log("Calculating age...");
}
}
Method:
calculateAge() {
console.log("Calculating age...");
}
Definisi function tersebut pada descriptor secara konsep tersimpan di:
descriptor.value
Jadi:
descriptor
│
├── configurable
├── enumerable
├── writable
│
└── value
│
└── function calculateAge() { ... }
Ini sangat penting.
20. Kenapa descriptor.value penting?
Karena kalau kita ingin memodifikasi behavior sebuah method, kita bisa mengganti:
descriptor.value
dengan function baru.
Misalnya method awal:
calculateAge() {
console.log("Original method");
}
Kemudian decorator bisa mengubahnya menjadi sesuatu seperti:
descriptor.value = function () {
console.log("Before method");
// jalankan method asli
console.log("After method");
};
Jadi decorator bisa bertindak seperti pembungkus method.
21. Analogi sederhana: Method sebagai makanan
Bayangkan method adalah makanan:
calculateAge()
Decorator adalah bungkusnya.
Awalnya:
calculateAge()
Setelah diberi decorator:
┌─────────────────────────┐
│ Logger │
│ │
│ calculateAge() │
│ │
└─────────────────────────┘
Decorator bisa menambahkan sesuatu sebelum atau sesudah method dijalankan.
Contohnya:
Logger
↓
"Method akan dijalankan"
↓
calculateAge()
↓
"Method selesai"
Ini salah satu kegunaan paling umum dari method decorator.
22. Isi PropertyDescriptor
Lecture menunjukkan beberapa property:
configurable
enumerable
value
writable
prototype
Untuk level lecture ini, yang paling penting adalah memahami:
value
Berisi function/value dari property tersebut.
Untuk method:
descriptor.value
adalah function method.
writable
Menunjukkan apakah value dapat diubah.
Secara sederhana:
writable: true
berarti value dapat ditulis/diubah.
enumerable
Menentukan apakah property tersebut muncul dalam enumeration tertentu.
Untuk sekarang tidak perlu terlalu dalam.
configurable
Menentukan apakah descriptor/property dapat dikonfigurasi lebih lanjut.
Untuk sekarang cukup pahami bahwa ini adalah metadata mengenai property.
23. Method decorator vs accessor decorator
Ini menarik karena sebelumnya kita sudah belajar accessor decorator.
Accessor decorator:
function AccessLogger(
target: any,
name: string,
descriptor: PropertyDescriptor
) {
}
Method decorator:
function Logger(
target: any,
methodName: string,
descriptor: PropertyDescriptor
) {
}
Perhatikan:
Keduanya memiliki:
target
name/methodName
descriptor
Karena keduanya bekerja dengan descriptor.
Bedanya adalah yang diberi decorator:
Accessor decorator
↓
get/set
Method decorator
↓
method()
24. Bandingkan semua decorator yang sudah kita pelajari
Ini bagus untuk dibuat sebagai “peta”.
| Decorator | Dipasang pada | Parameter decorator |
|---|---|---|
| Class decorator | class | target |
| Property decorator | property | target, propertyKey |
| Accessor decorator | getter/setter | target, propertyKey, descriptor |
| Method decorator | method | target, propertyKey, descriptor |
| Parameter decorator | parameter | target, methodName, parameterIndex |
Kalau mau menghafalnya:
CLASS
↓
target
PROPERTY
↓
target + propertyKey
METHOD
↓
target + propertyKey + descriptor
ACCESSOR
↓
target + propertyKey + descriptor
PARAMETER
↓
target + methodName + index
25. Kenapa method dan accessor sama-sama punya descriptor?
Karena keduanya merupakan bagian dari object/class definition yang punya property descriptor.
Untuk method:
calculateAge() {}
descriptor-nya memiliki:
value
yang menunjuk ke function.
Untuk accessor:
get price() {}
set price(value) {}
descriptor-nya memiliki:
get
set
Jadi:
METHOD DESCRIPTOR
descriptor
│
└── value → function
ACCESSOR DESCRIPTOR
descriptor
│
├── get → function
└── set → function
Ini menghubungkan lecture sebelumnya dengan lecture sekarang.
26. Kenapa method decorator bisa mengubah method?
Karena kita memiliki akses ke:
descriptor.value
Misalnya method asli:
calculateAge(dateOfBirth: string) {
console.log("Calculating age");
}
Secara konsep:
descriptor.value
│
↓
calculateAge function
Decorator bisa mengambil function tersebut:
const originalMethod = descriptor.value;
Lalu menggantinya:
descriptor.value = function () {
console.log("Before");
originalMethod.call(this);
console.log("After");
};
Hasilnya:
sebelum method
↓
"Before"
↓
calculateAge()
↓
"After"
Inilah dasar dari banyak decorator yang lebih advanced.
27. Kenapa menggunakan return descriptor?
Decorator bisa mengubah descriptor lalu mengembalikannya:
return descriptor;
Secara konsep:
Original descriptor
↓
Decorator
↓
modify descriptor
↓
return descriptor
↓
TypeScript menggunakan descriptor baru
Misalnya:
function Logger(
target: any,
methodName: string,
descriptor: PropertyDescriptor
) {
console.log("Method decorator called");
descriptor.value = function () {
console.log("Method called");
};
return descriptor;
}
Jadi decorator bukan hanya bisa membaca method, tetapi juga dapat memodifikasi behavior-nya.
28. Perbedaan penting: decorator tidak sama dengan memanggil method
Ini sering membingungkan.
Ketika kita menulis:
@Logger
calculateAge() {
}
Logger tidak berarti:
calculateAge();
Decorator bekerja ketika class sedang didefinisikan/diproses.
Sedangkan:
person.calculateAge();
adalah saat method benar-benar dipanggil.
Jadi:
CLASS DEFINITION
↓
Decorator diproses
↓
Method disiapkan
↓
------------------
↓
INSTANCE dibuat
↓
person.calculateAge()
↓
Method dijalankan
Ini konsep yang penting.
29. Contoh alur lengkap
Misalnya:
function Logger(
target: any,
methodName: string,
descriptor: PropertyDescriptor
) {
console.log("Logger called");
console.log(target);
console.log(methodName);
console.log(descriptor);
}
class Person {
@Logger
calculateAge(dateOfBirth: string) {
console.log("Calculating age");
}
}
Saat TypeScript memproses class:
Person class
│
↓
@Logger ditemukan
│
↓
Logger(
target,
"calculateAge",
descriptor
)
│
↓
Decorator dijalankan
Decorator menerima:
target
↓
Person.prototype
methodName
↓
"calculateAge"
descriptor
↓
{
value: calculateAge function,
...
}
30. Parameter decorator dan method decorator bisa digunakan bersamaan
Misalnya:
class Person {
@Logger
calculateAge(
@Param dateOfBirth: string
) {
}
}
Di sini ada dua decorator:
@Logger
↓
calculateAge(
@Param dateOfBirth
)
Logger:
method decorator
Param:
parameter decorator
Keduanya punya tugas yang berbeda.
31. Visualisasi keseluruhan
Bayangkan:
class Person
│
└── calculateAge()
│
├── @Logger
│
└── parameter
│
└── @Param dateOfBirth
@Logger mendapatkan:
target
methodName
descriptor
Sedangkan @Param mendapatkan:
target
methodName
index
Jadi:
calculateAge
│
┌────────┴────────┐
│ │
@Logger @Param
│ │
↓ ↓
method decorator parameter decorator
│ │
↓ ↓
target, name, descriptor target, name, index
32. Hal yang paling penting dari lecture ini
Menurut saya ada 4 poin utama yang harus benar-benar kamu pahami.
① Method bisa diberi decorator
@Logger
calculateAge() {}
② Parameter method juga bisa diberi decorator
calculateAge(
@Param dateOfBirth: string
) {}
③ Method decorator mendapatkan 3 parameter
function Logger(
target,
methodName,
descriptor
) {}
Dengan:
target
→ prototype / constructor
methodName
→ nama method
descriptor
→ informasi/descriptor method
④ Parameter decorator juga mendapatkan 3 parameter
function Param(
target,
methodName,
index
) {}
Dengan:
target
→ prototype / constructor
methodName
→ nama method
index
→ posisi parameter
33. Mnemonic supaya gampang diingat
Kamu bisa mengingat seperti ini:
Method
“Method punya descriptor.”
METHOD
↓
target
name
descriptor
Parameter
“Parameter punya index.”
PARAMETER
↓
target
method name
index
Jadi:
METHOD
→ descriptor
PARAMETER
→ index
Ini yang paling penting untuk dibawa ke lecture berikutnya.
34. Satu hal yang perlu diperhatikan
Contoh di lecture ini menggunakan legacy/experimental TypeScript decorators, yang API-nya menggunakan pola seperti:
target
propertyKey
descriptor
Jadi kalau nanti kamu melihat dokumentasi TypeScript versi modern dan bentuk decorator-nya terlihat sangat berbeda, jangan langsung mengira salah. TypeScript sekarang juga memiliki standard decorators dengan API yang berbeda.
Untuk memahami course/lecture yang sedang kamu ikuti, gunakan dulu pola yang diajarkan di sini.
Ringkasan super singkat
DECORATORS
│
┌────────────────┼────────────────┐
│ │ │
Method Parameter Accessor
│ │ │
↓ ↓ ↓
@Logger @Param @Logger
│ │ │
↓ ↓ ↓
target target target
methodName methodName propertyKey
descriptor index descriptor
Dan khusus lecture ini:
Method decorator
function Logger(
target: any,
methodName: string,
descriptor: PropertyDescriptor
) {}
Ingat:
target + method name + descriptor
Method parameter decorator
function Param(
target: any,
methodName: string,
index: number
) {}
Ingat:
target + method name + index
Dan konsep paling penting dari descriptor:
descriptor
│
└── value
│
└── function asli dari method
Karena descriptor.value menyimpan function method, decorator nantinya bisa membungkus atau mengganti behavior method tersebut. Ini yang biasanya menjadi langkah berikutnya setelah lecture ini.