Masukkan Password
67 Membuat Daftar Pengeluaran
Lecture ini melanjutkan langsung dari lecture sebelumnya. Kalau sebelumnya kita berhasil membaca data form → membuat object Expense, sekarang kita menghadapi masalah baru:
Kalau user membuat banyak expense, kita harus menyimpan semua object tersebut di suatu tempat.
Solusinya adalah menggunakan array/list.
Selain itu, instructor juga mulai memperkenalkan dua konsep penting TypeScript/OOP:
privatereadonly
Jadi lecture ini sebenarnya punya dua fokus utama:
1. Menyimpan semua Expense ke dalam Array
2. Mengatur access modifier/property dengan private & readonly
1. Mengingat Kembali Lecture Sebelumnya
Sebelumnya kita punya class:
class Expense {
id: number;
static currentId: number = 0;
type: "credit" | "debit" = "debit";
description: string = "";
amount: number = 0;
constructor(
type: "credit" | "debit",
description: string,
amount: number
) {
this.id = ++Expense.currentId;
this.type = type;
this.description = description;
this.amount = amount;
}
}
Kemudian ketika user klik tombol:
const expense = new Expense(
type,
expenseDescription.value,
+expenseAmount.value
);
Kita mendapatkan object seperti:
Expense
├── id: 1
├── type: "debit"
├── description: "Groceries"
└── amount: 230
Masalahnya sekarang:
Setelah object dibuat, mau disimpan di mana?
2. Masalah: Kita Bisa Membuat Banyak Expense
Misalnya user melakukan:
Expense pertama
Groceries
$230
Object:
{
id: 1,
type: "debit",
description: "Groceries",
amount: 230
}
Kemudian user menambahkan:
Expense kedua
Movie Ticket
$50
Object:
{
id: 2,
type: "debit",
description: "Movie Ticket",
amount: 50
}
Kemudian:
Expense ketiga
YouTube Earnings
$5500
Object:
{
id: 3,
type: "credit",
description: "YouTube Earnings",
amount: 5500
}
Sekarang kita memiliki:
Expense 1
Expense 2
Expense 3
Kita membutuhkan satu tempat untuk menyimpan semuanya.
3. Solusinya: Array
Kita membuat:
const expenseItems: Expense[] = [];
Ini artinya:
Buat sebuah array yang hanya boleh berisi object
Expense.
Awalnya:
expenseItems
↓
[ ]
Setelah expense pertama:
expenseItems
↓
[ Expense 1 ]
Setelah expense kedua:
expenseItems
↓
[ Expense 1, Expense 2 ]
Setelah expense ketiga:
expenseItems
↓
[ Expense 1, Expense 2, Expense 3 ]
4. Cara Membaca Expense[]
Bagian ini penting:
Expense[]
Sama seperti sebelumnya kita pernah melihat:
string[]
number[]
boolean[]
Contohnya:
const names: string[] = [];
berarti:
Array yang berisi string.
Sedangkan:
const ages: number[] = [];
berarti:
Array yang berisi number.
Maka:
const expenseItems: Expense[] = [];
berarti:
Array yang berisi object
Expense.
5. Kenapa Tidak Menggunakan any[]?
Kita sebenarnya bisa menulis:
const expenseItems: any[] = [];
Tapi ini tidak bagus.
Karena:
any[]
berarti array tersebut boleh berisi apa saja:
expenseItems.push(100);
expenseItems.push("hello");
expenseItems.push(true);
expenseItems.push({});
TypeScript tidak bisa membantu kita dengan baik.
Sedangkan:
Expense[]
berarti:
expenseItems
│
├── Expense ✅
├── Expense ✅
├── Expense ✅
└── string ❌
Jadi jauh lebih aman.
6. Menambahkan Expense ke Array
Setelah object dibuat:
const expense = new Expense(
type,
expenseDescription.value,
+expenseAmount.value
);
kita lakukan:
expenseItems.push(expense);
push() digunakan untuk menambahkan item ke akhir array.
Misalnya awalnya:
const expenseItems: Expense[] = [];
isinya:
[]
Setelah:
expenseItems.push(expense);
menjadi:
[Expense 1]
Kemudian user membuat expense lagi:
expenseItems.push(expense);
menjadi:
[Expense 1, Expense 2]
7. Visualisasi push()
Bayangkan array seperti sebuah kotak penyimpanan:
┌─────────────────────────────────┐
│ expenseItems │
│ │
│ [ ] │
└─────────────────────────────────┘
Masukkan Expense pertama:
┌─────────────────────────────────┐
│ expenseItems │
│ │
│ [ Expense 1 ] │
└─────────────────────────────────┘
Masukkan lagi:
┌─────────────────────────────────┐
│ expenseItems │
│ │
│ [ Expense 1 | Expense 2 ] │
└─────────────────────────────────┘
Masukkan lagi:
┌────────────────────────────────────────┐
│ expenseItems │
│ │
│ [ Expense 1 | Expense 2 | Expense 3 ] │
└────────────────────────────────────────┘
8. Kenapa Array Ini Penting untuk Lecture Berikutnya?
Instructor mengatakan:
“in order to display all the expenses in the UI we are going to loop over this list”
Nah, ini alasan utama kita membuat array.
Kita sekarang punya:
expenseItems
yang berisi:
Expense 1
Expense 2
Expense 3
Expense 4
...
Nanti kita bisa melakukan:
expenseItems.forEach(expense => {
// buat HTML
});
Misalnya:
expenseItems
│
▼
┌────────────┐
│ Expense 1 │ ──→ HTML
│ Expense 2 │ ──→ HTML
│ Expense 3 │ ──→ HTML
└────────────┘
Sehingga array menjadi sumber data utama untuk membuat expense list di UI.
9. Data vs UI
Ini konsep yang sangat bagus untuk mulai dipahami.
Kita sekarang memiliki dua hal:
Data
expenseItems
isinya:
Expense 1
Expense 2
Expense 3
UI
Yang dilihat user:
Groceries -$230
Movie Ticket -$50
YouTube Earnings +$5500
Hubungannya:
DATA
│
▼
expenseItems[]
│
▼
Loop / render
│
▼
DOM
│
▼
UI
Jadi array tersebut akan menjadi dasar untuk menampilkan data ke browser.
10. Bagian Kedua: private
Sekarang instructor melakukan perubahan terhadap:
static currentId
Sebelumnya:
static currentId: number = 0;
Kemudian dibuat:
private static currentId: number = 0;
Kenapa?
Karena currentId hanya digunakan di dalam class Expense.
11. Apa Arti private?
private berarti:
Property atau method hanya boleh digunakan dari dalam class tempat property tersebut didefinisikan.
Contoh:
class Expense {
private secret = 123;
}
Di dalam class:
class Expense {
private secret = 123;
showSecret() {
console.log(this.secret); // ✅
}
}
Boleh.
Tetapi dari luar:
const expense = new Expense();
console.log(expense.secret);
akan error.
Karena:
secret
↓
private
↓
hanya bisa digunakan di dalam Expense
12. Kenapa currentId Harus Private?
Ingat fungsi:
private static currentId = 0;
Property ini digunakan untuk membuat ID:
this.id = ++Expense.currentId;
Jadi tugasnya adalah:
currentId
↓
counter internal
↓
membuat ID unik
Kita tidak ingin kode dari luar class mengubahnya sembarangan.
Bayangkan kalau bisa:
Expense.currentId = 10000;
Kemudian expense berikutnya:
id = 10001
Mungkin aplikasi kita menjadi kacau.
Lebih baik:
currentId
↓
private
↓
hanya Expense class yang mengontrolnya
13. Analogi private
Bayangkan class seperti rumah:
┌───────────────────────────────┐
│ Expense House │
│ │
│ 🔒 currentId │
│ │
│ constructor() │
│ methods() │
└───────────────────────────────┘
private adalah seperti ruangan terkunci.
Orang dari luar:
👤 Outside
↓
❌ tidak boleh masuk
Tetapi orang di dalam class:
Expense
↓
✅ boleh mengakses currentId
14. static + private
Jadi:
private static currentId: number = 0;
memiliki dua konsep:
static
Property milik class, bukan masing-masing instance.
private
Property hanya bisa digunakan di dalam class.
Gabungannya:
currentIdadalah counter milik class dan hanya classExpenseyang boleh mengaksesnya.
15. Bagian Ketiga: readonly
Sekarang instructor juga mengubah:
id: number;
menjadi:
readonly id: number;
Ini konsep yang sangat penting.
readonly berarti:
Property boleh dibaca, tetapi tidak boleh diubah setelah diberikan nilai.
16. Kenapa id Harus readonly?
Perhatikan:
constructor(...) {
this.id = ++Expense.currentId;
}
Ketika object dibuat, ID diberikan:
Expense 1 → id = 1
Expense 2 → id = 2
Expense 3 → id = 3
Setelah itu kita tidak ingin:
expense.id = 999;
Karena ID seharusnya menjadi identitas tetap dari expense tersebut.
Jadi:
readonly id: number;
lebih cocok.
17. readonly Tidak Berarti Tidak Bisa Dibaca
Ini kesalahan umum.
Kalau:
readonly id: number;
kita masih boleh membaca:
console.log(expense.id);
✅ Boleh.
Tetapi:
expense.id = 100;
❌ Tidak boleh.
Jadi:
readonly
│
├── READ ✅
└── WRITE ❌
18. Kenapa ID Bisa Diisi di Constructor?
Ini juga penting.
Kita memiliki:
readonly id: number;
tetapi belum memberikan nilai langsung:
readonly id: number;
Kemudian di constructor:
this.id = ++Expense.currentId;
Ini diperbolehkan.
Karena constructor adalah tempat inisialisasi object.
Konsepnya:
Object dibuat
↓
constructor
↓
id diberikan
↓
id menjadi readonly
Setelah object selesai dibuat:
expense.id = 10;
tidak diperbolehkan.
19. Analogi readonly
Bayangkan nomor KTP atau nomor tiket.
Ketika tiket dibuat:
Ticket
ID = 123
Setelah dibuat, ID tersebut harus tetap:
ID = 123
Tidak boleh berubah menjadi:
ID = 999
Maka readonly cocok untuk data seperti:
ID
serial number
createdAt
invoice number
order ID
yang biasanya tidak boleh diubah setelah dibuat.
20. private vs readonly
Ini wajib dibedakan.
| Keyword | Maksud |
|---|---|
private | Tidak bisa diakses dari luar class |
readonly | Bisa dibaca, tetapi tidak bisa diubah |
private readonly | Tidak bisa diakses dari luar dan tidak bisa diubah setelah diinisialisasi |
Contoh:
private static currentId: number = 0;
Berarti:
dari luar → ❌
dari dalam class → ✅
Sedangkan:
readonly id: number;
berarti:
baca dari luar → ✅
ubah dari luar → ❌
21. Kenapa id Tidak Dibuat Private?
Instructor menjelaskan bahwa ID mungkin dibutuhkan dari luar class.
Misalnya nanti ketika user ingin menghapus expense:
User klik Delete
↓
Kita perlu tahu expense mana
↓
Gunakan ID
Misalnya:
expense.id
Jadi kita ingin:
console.log(expense.id);
tetap boleh.
Tetapi:
expense.id = 100;
tidak boleh.
Maka pilihan yang cocok:
readonly id: number;
bukan:
private id: number;
22. currentId vs id
Sekarang kita bisa melihat perbedaan keduanya dengan jelas:
private static currentId: number = 0;
readonly id: number;
currentId
private
static
Fungsinya:
Counter internal untuk membuat ID.
id
readonly
Fungsinya:
ID yang diberikan kepada setiap object.
Visual:
Expense Class
│
┌─────────▼──────────┐
│ currentId = 3 │
│ private + static │
└─────────┬──────────┘
│
generate next ID
│
┌──────────┼───────────┐
▼ ▼ ▼
Expense 1 Expense 2 Expense 3
id=1 id=2 id=3
readonly readonly readonly
23. Full Class Setelah Perubahan
Sekarang class-nya kira-kira:
class Expense {
readonly id: number;
private static currentId: number = 0;
type: "credit" | "debit" = "debit";
description: string = "";
amount: number = 0;
constructor(
type: "credit" | "debit",
description: string,
amount: number
) {
this.id = ++Expense.currentId;
this.type = type;
this.description = description;
this.amount = amount;
}
}
Perhatikan:
readonly id
dan:
private static currentId
24. Sekarang Kita Buat Array
Di luar class:
const expenseItems: Expense[] = [];
Kita bisa membacanya:
“
expenseItemsadalah array yang hanya boleh menyimpan objectExpense, dan saat ini array-nya kosong.”
Awalnya:
expenseItems = []
25. Ketika User Klik Add Expense
Misalnya user mengisi:
Type: debit
Description: Groceries
Amount: 230
Kemudian:
const expense = new Expense(
type,
expenseDescription.value,
+expenseAmount.value
);
Object dibuat:
Expense {
id: 1,
type: "debit",
description: "Groceries",
amount: 230
}
Kemudian:
expenseItems.push(expense);
Array menjadi:
[
Expense {
id: 1,
type: "debit",
description: "Groceries",
amount: 230
}
]
26. User Menambahkan Expense Kedua
Misalnya:
Type: debit
Description: Movie Ticket
Amount: 50
Object:
Expense {
id: 2,
type: "debit",
description: "Movie Ticket",
amount: 50
}
Kemudian:
expenseItems.push(expense);
Array:
[
Expense #1,
Expense #2
]
27. User Menambahkan Expense Ketiga
Misalnya:
Type: credit
Description: YouTube Earnings
Amount: 5500
Hasil:
[
Expense #1,
Expense #2,
Expense #3
]
Sekarang kita sudah memiliki database kecil di memory.
Tentu ini belum database sungguhan seperti MySQL/PostgreSQL.
Data ini hanya berada di memory browser selama aplikasi berjalan.
28. Kenapa Disebut “List”?
Instructor menyebut:
create a list which will store all the expenses
Dalam JavaScript/TypeScript, yang dimaksud list di sini pada dasarnya adalah:
Expense[]
atau array.
Jadi:
List
≈
Array
Untuk kebutuhan project ini, array adalah struktur data yang cocok.
29. console.log(expenseItems)
Instructor kemudian melakukan:
console.log(expenseItems);
Misalnya baru ada satu expense:
[
Expense {
id: 1,
type: "credit",
description: "test",
amount: 1000
}
]
Kemudian tambah satu lagi:
[
Expense {
id: 1,
...
},
Expense {
id: 2,
...
}
]
Ini membuktikan bahwa object berhasil disimpan.
30. Kenapa Tidak Cukup console.log(expense)?
Kalau kita hanya:
console.log(expense);
kita hanya melihat expense yang baru dibuat.
Misalnya:
Klik 1 → Expense 1
Klik 2 → Expense 2
Klik 3 → Expense 3
Tetapi kita ingin memiliki semua:
Expense 1
Expense 2
Expense 3
di satu tempat.
Maka:
expenseItems.push(expense);
sangat penting.
31. Hubungannya dengan Fitur Delete
Ingat dari lecture pertama, kita ingin bisa:
menghapus expense.
Bagaimana nanti kita tahu expense mana yang harus dihapus?
Kita memiliki:
id
dan array:
expenseItems
Misalnya:
[
Expense #1,
Expense #2,
Expense #3
]
User menghapus:
Expense #2
Kita bisa mencari berdasarkan:
expense.id
Kemudian menghapus object tersebut dari array.
Jadi keputusan instructor untuk memberikan ID unik sejak sekarang sangat penting untuk fitur-fitur berikutnya.
32. Hubungannya dengan Menampilkan UI
Lecture berikutnya kemungkinan besar akan melakukan sesuatu seperti:
expenseItems.forEach((expense) => {
// generate HTML
});
Misalnya array:
[
Groceries,
Movie Ticket,
YouTube Earnings
]
akan diubah menjadi:
┌──────────────────────────────────┐
│ Groceries -$230 │
│ Movie Ticket -$50 │
│ YouTube Earnings +$5500 │
└──────────────────────────────────┘
Jadi:
expenseItems
↓
loop
↓
setiap Expense
↓
generate HTML
↓
DOM
↓
UI
33. Ini Mulai Menjadi Pola Data-Driven UI
Ini konsep yang bagus untuk kamu pahami sejak sekarang.
Kita tidak lagi berpikir:
“Saya mau membuat
<div>Groceries.”
Tetapi:
“Saya punya data Expense. Dari data itu saya generate UI.”
Misalnya:
DATA
{
description: "Groceries",
amount: 230
}
↓
RENDER
↓
<div>
Groceries
$230
</div>
Jadi UI adalah representasi dari data.
34. Type Safety pada Array
Karena kita menulis:
const expenseItems: Expense[] = [];
TypeScript menjaga isi array.
Misalnya:
expenseItems.push(expense);
✅ valid karena expense adalah Expense.
Tetapi:
expenseItems.push("hello");
❌ error.
Atau:
expenseItems.push(100);
❌ error.
Ini contoh lain dari type safety.
35. Kenapa Ini Lebih Baik daripada Array Biasa?
Kita bisa saja menulis:
const expenseItems = [];
TypeScript dapat melakukan inference tertentu, tetapi dengan konteks project ini kita ingin jelas bahwa array tersebut memang ditujukan untuk Expense.
Dengan:
const expenseItems: Expense[] = [];
kita mendokumentasikan dan membatasi struktur data:
“Array ini khusus untuk Expense.”
36. Hubungan dengan Generic
Menariknya, kamu juga sudah pernah belajar generic.
Expense[]
secara konsep berkaitan dengan:
Array<Expense>
Keduanya setara:
Expense[]
dan:
Array<Expense>
Artinya:
Array yang element-nya bertipe
Expense.
Ini menghubungkan lecture ini dengan materi Generic sebelumnya.
37. Perhatikan Alur ID
Sekarang kita bisa melihat alurnya dengan lengkap:
Awal:
Expense.currentId = 0
User membuat Expense pertama:
++currentId
↓
1
↓
expense.id = 1
Array:
[ Expense #1 ]
Expense kedua:
++currentId
↓
2
↓
expense.id = 2
Array:
[ Expense #1, Expense #2 ]
Expense ketiga:
++currentId
↓
3
↓
expense.id = 3
Array:
[ Expense #1, Expense #2, Expense #3 ]
38. Kenapa ID Tidak Di-reset Saat Delete?
Ini pertanyaan yang mungkin muncul.
Misalnya:
Expense 1
Expense 2
Expense 3
Kemudian Expense 2 dihapus.
Array:
[ Expense 1, Expense 3 ]
currentId tetap:
3
Kalau membuat expense baru:
currentId:
3 → 4
ID baru:
Expense 4
Bukan:
Expense 2
Ini justru bagus karena ID sebaiknya menjadi identifier unik dan tidak perlu didaur ulang.
39. private Melindungi Counter
Sekarang bayangkan ada code dari luar:
Expense.currentId = 999;
Karena sudah:
private static currentId
TypeScript akan mencegahnya.
Jadi hanya class Expense yang bertanggung jawab terhadap:
generate ID
Ini merupakan prinsip OOP yang bagus:
Encapsulation
40. Encapsulation
Lecture ini sebenarnya mulai memperkenalkan praktik encapsulation.
Encapsulation berarti:
Menyembunyikan detail internal dan hanya memberikan akses yang memang diperlukan.
Dalam kasus ini:
Yang disembunyikan
private static currentId
User luar tidak perlu tahu bagaimana counter bekerja.
Yang boleh dilihat
readonly id
Karena kita mungkin membutuhkan ID untuk:
delete
update
detail
Jadi:
Internal implementation
↓
currentId
↓
🔒 private
Public information
↓
id
↓
👁 readable
41. Analogi Sederhana Encapsulation
Bayangkan mesin ATM.
Kamu bisa:
masukkan kartu
masukkan PIN
ambil uang
Tapi kamu tidak perlu tahu bagaimana mesin ATM menghitung saldo secara internal.
Bagian internal:
🔒 private
Bagian yang diberikan kepada user:
public interface
Begitu juga class Expense.
Kita tidak perlu membiarkan code luar mengubah:
currentId
Tetapi boleh membaca:
id
42. Perbedaan private dan readonly dengan Analogi
Bayangkan sebuah kotak.
private
Kotaknya berada di ruangan terkunci:
🔒
Tidak bisa diakses dari luar
readonly
Kotaknya boleh dilihat:
👀
tetapi isinya tidak boleh diganti:
❌ modify
Jadi:
private → siapa yang boleh mengakses?
readonly → boleh diubah atau tidak?
Ini cara paling mudah mengingatnya.
43. Ringkasan Kode Utama
Bagian class:
class Expense {
readonly id: number;
private static currentId: number = 0;
type: "credit" | "debit" = "debit";
description: string = "";
amount: number = 0;
constructor(
type: "credit" | "debit",
description: string,
amount: number
) {
this.id = ++Expense.currentId;
this.type = type;
this.description = description;
this.amount = amount;
}
}
Kemudian list:
const expenseItems: Expense[] = [];
Ketika membuat expense:
const expense = new Expense(
type,
expenseDescription.value,
+expenseAmount.value
);
Simpan:
expenseItems.push(expense);
44. Flow Lengkap Lecture Ini
Sekarang seluruh prosesnya:
User mengisi form
↓
Klik Add Expense
↓
Event Listener
↓
Ambil data form
↓
new Expense(...)
↓
Constructor
↓
Generate ID
↓
Expense object
↓
expenseItems.push(expense)
↓
Expense tersimpan dalam array
Misalnya tiga kali:
expenseItems
│
┌────────────┼────────────┐
▼ ▼ ▼
Expense #1 Expense #2 Expense #3
│ │ │
$230 $50 $5500
Dan nanti:
expenseItems
↓
Loop
↓
Generate HTML
↓
DOM
↓
UI
45. Hubungan dengan Lecture Sebelumnya
Kalau kita gabungkan lecture sebelumnya dan sekarang:
Lecture sebelumnya
Kita belajar:
HTML Form
↓
DOM
↓
ambil value
↓
Type Assertion
↓
new Expense()
↓
Expense Object
Lecture sekarang
Kita tambahkan:
Expense Object
↓
expenseItems
↓
Expense[]
↓
push()
↓
semua Expense tersimpan
Jadi:
FORM
↓
read values
↓
new Expense()
↓
Expense Object
↓
expenseItems[]
↓
[E1, E2, E3]
↓
render
↓
UI
46. Konsep Baru yang Harus Kamu Ingat
① Array of Objects
const expenseItems: Expense[] = [];
Artinya:
Array yang hanya berisi object
Expense.
② push()
expenseItems.push(expense);
Artinya:
Tambahkan expense ke dalam array.
③ private
private static currentId
Artinya:
Hanya class
Expenseyang boleh mengaksescurrentId.
④ readonly
readonly id
Artinya:
idboleh dibaca tetapi tidak boleh diubah setelah diinisialisasi.
⑤ static
static currentId
Artinya:
Property tersebut milik class, bukan masing-masing object.
47. Cheat Sheet
Expense[]
↓
Array yang berisi Expense
Array<Expense>
↓
Sama dengan Expense[]
.push()
↓
Menambahkan item ke array
private
↓
Hanya bisa diakses dari dalam class
readonly
↓
Bisa dibaca, tidak bisa diubah
static
↓
Milik class, bukan instance
private static
↓
Milik class + hanya bisa digunakan di dalam class
readonly id
↓
ID bisa dibaca, tapi tidak boleh diganti
48. Inti Lecture
Kalau disederhanakan, instructor sedang menyelesaikan satu masalah besar:
“Kita sudah bisa membuat satu Expense. Sekarang bagaimana menyimpan banyak Expense?”
Jawabannya:
const expenseItems: Expense[] = [];
Kemudian setiap kali user membuat expense:
expenseItems.push(expense);
Sehingga:
Expense 1
Expense 2
Expense 3
Expense 4
...
semuanya tersimpan dalam:
expenseItems[]
Dan di saat yang sama instructor memperbaiki desain class:
private static currentId
agar counter ID menjadi internal, dan:
readonly id
agar ID setiap expense tidak bisa diubah sembarangan.
Jadi pola besarnya sekarang adalah:
USER
│
▼
FORM HTML
│
▼
Read form values
│
▼
new Expense(...)
│
▼
┌─────────────┐
│ Expense │
│ │
│ id 🔒 read │
│ type │
│ description │
│ amount │
└──────┬──────┘
│
▼
expenseItems: Expense[]
│
▼
[E1, E2, E3, ...]
│
▼
Lecture berikutnya:
LOOP + RENDER
│
▼
EXPENSE LIST
DI UI
Jadi setelah lecture ini, aplikasi kita sudah mempunyai “tempat penyimpanan” untuk semua expense. Lecture berikutnya tinggal mengambil isi expenseItems, melakukan loop, lalu mengubah setiap object menjadi tampilan HTML di UI.