
Tutorial Angular untuk Pemula - Panduan Lengkap dari Nol
Panduan lengkap belajar Angular dari dasar untuk pemula - Tutorial komprehensif framework JavaScript modern
Tutorial Angular untuk Pemula - Panduan Lengkap dari Nol
Apa itu Angular?
🔍 Pengertian Angular
Angular adalah framework JavaScript yang dikembangkan oleh Google untuk membangun aplikasi web yang dinamis dan modern. Angular menggunakan TypeScript sebagai bahasa utama dan mengikuti arsitektur component-based.
Mengapa Angular?
- Single Page Application (SPA): Membuat aplikasi web yang cepat dan responsif
- Two-way Data Binding: Sinkronisasi otomatis antara model dan view
- Dependency Injection: Manajemen dependensi yang efisien
- TypeScript: Type safety dan development experience yang lebih baik
- Component-based: Kode yang modular dan reusable
Contoh penggunaan Angular:
- Gmail (Google)
- Netflix
- PayPal
- Upwork
- Samsung
Persiapan Environment
📋 Prerequisites
Sebelum memulai, pastikan Anda sudah memiliki:
- Node.js (versi 16 atau lebih baru)
- npm atau yarn package manager
- Code Editor (VS Code direkomendasikan)
- Git (opsional)
🛠️ Instalasi Angular CLI
Angular CLI adalah tool command line untuk membuat dan mengelola proyek Angular.
# Install Angular CLI secara global npm install -g @angular/cli # Cek versi Angular CLI ng version
🚀 Membuat Proyek Angular Baru
# Membuat proyek baru ng new my-angular-app # Pilihan konfigurasi: # - Would you like to add Angular routing? (Y/n) -> Y # - Which stylesheet format would you like to use? -> CSS # Masuk ke direktori proyek cd my-angular-app # Menjalankan development server ng serve
Aplikasi akan berjalan di http://localhost:4200
Struktur Proyek Angular
📁 Penjelasan Struktur Folder
my-angular-app/ ├── src/ │ ├── app/ │ │ ├── app.component.ts # Root component │ │ ├── app.component.html # Template │ │ ├── app.component.css # Styles │ │ ├── app.component.spec.ts # Unit tests │ │ ├── app.module.ts # Root module │ │ └── app-routing.module.ts # Routing configuration │ ├── assets/ # Static assets │ ├── environments/ # Environment configurations │ ├── index.html # Main HTML file │ ├── main.ts # Bootstrap file │ └── styles.css # Global styles ├── angular.json # Angular configuration ├── package.json # Dependencies └── tsconfig.json # TypeScript configuration
Konsep Dasar Angular
🧩 Components
Component adalah building block utama dalam Angular. Setiap component terdiri dari:
- TypeScript Class - Logic component
- HTML Template - UI component
- CSS Styles - Styling component
Membuat component baru:
ng generate component user-profile # atau ng g c user-profile
Contoh component sederhana:
// user-profile.component.ts import { Component } from "@angular/core"; @Component({ selector: "app-user-profile", templateUrl: "./user-profile.component.html", styleUrls: ["./user-profile.component.css"], }) export class UserProfileComponent { userName: string = "John Doe"; userAge: number = 25; greetUser(): string { return `Hello, ${this.userName}!`; } }
<!-- user-profile.component.html --> <div class="user-profile"> <h2>{{ greetUser() }}</h2> <p>Age: {{ userAge }}</p> <button (click)="updateAge()">Update Age</button> </div>
📦 Modules
Module adalah cara Angular mengorganisir aplikasi menjadi blok-blok yang kohesif.
// app.module.ts import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { AppComponent } from "./app.component"; import { UserProfileComponent } from "./user-profile/user-profile.component"; @NgModule({ declarations: [AppComponent, UserProfileComponent], imports: [BrowserModule], providers: [], bootstrap: [AppComponent], }) export class AppModule {}
🎯 Data Binding
Angular menyediakan beberapa jenis data binding:
1. Interpolation - One-way (Component → Template)
<h1>{{ title }}</h1> <p>User: {{ user.name }}</p> <p>Total: {{ price * quantity }}</p>
2. Property Binding - One-way (Component → Template)
<img [src]="imageUrl" [alt]="imageAlt" /> <button [disabled]="isDisabled">Click me</button> <div [ngClass]="cssClasses">Content</div>
3. Event Binding - One-way (Template → Component)
<button (click)="onClick()">Click me</button> <input (keyup)="onKeyUp($event)" /> <form (submit)="onSubmit()">Submit</form>
4. Two-way Binding - (Component ↔ Template)
<input [(ngModel)]="userName" /> <p>Hello {{ userName }}!</p>
Catatan: Untuk ngModel, import FormsModule di module:
import { FormsModule } from '@angular/forms'; @NgModule({ imports: [BrowserModule, FormsModule], // ... })
🎪 Directives
Directives adalah instruksi untuk memanipulasi DOM.
Structural Directives
<!-- *ngIf --> <div *ngIf="isLoggedIn">Welcome back!</div> <!-- *ngFor --> <ul> <li *ngFor="let item of items; let i = index">{{ i + 1 }}. {{ item.name }}</li> </ul> <!-- *ngSwitch --> <div [ngSwitch]="userType"> <p *ngSwitchCase="'admin'">Admin Panel</p> <p *ngSwitchCase="'user'">User Dashboard</p> <p *ngSwitchDefault>Guest Mode</p> </div>
Attribute Directives
<!-- ngClass --> <div [ngClass]="{ 'active': isActive, 'disabled': isDisabled, 'highlight': isHighlighted }" > Dynamic Classes </div> <!-- ngStyle --> <div [ngStyle]="{ 'color': textColor, 'font-size': fontSize + 'px', 'background-color': bgColor }" > Dynamic Styles </div>
Services dan Dependency Injection - "Pelayan Khusus" untuk Aplikasi
🔧 Apa itu Service?
Service adalah class khusus untuk berbagi data dan logic antar components.
Analogi Hotel:
Bayangkan aplikasi Angular seperti hotel besar:
- Component = kamar-kamar hotel (Header, Footer, User Profile)
- Service = pelayan hotel khusus (Room Service, Concierge, Housekeeping)
Tanpa Service (Ribet):
Tamu kamar 101: "Saya mau makan" → masak sendiri di kamar Tamu kamar 102: "Saya mau makan" → masak sendiri di kamar Tamu kamar 103: "Saya mau makan" → masak sendiri di kamar
Dengan Service (Efisien):
Semua tamu: "Saya mau makan" → panggil Room Service Room Service: melayani semua kamar dengan menu yang sama
ng generate service services/user # atau ng g s services/user
// user.service.ts import { Injectable } from "@angular/core"; @Injectable({ providedIn: "root", }) export class UserService { private users = [ { id: 1, name: "John Doe", email: "john@example.com" }, { id: 2, name: "Jane Smith", email: "jane@example.com" }, ]; getUsers() { return this.users; } getUserById(id: number) { return this.users.find((user) => user.id === id); } addUser(user: any) { user.id = this.users.length + 1; this.users.push(user); } }
🏗️ Menggunakan Service dalam Component
// user-list.component.ts import { Component, OnInit } from "@angular/core"; import { UserService } from "../services/user.service"; @Component({ selector: "app-user-list", templateUrl: "./user-list.component.html", }) export class UserListComponent implements OnInit { users: any[] = []; constructor(private userService: UserService) {} ngOnInit() { this.users = this.userService.getUsers(); } addNewUser() { this.userService.addUser({ name: "New User", email: "new@example.com", }); this.users = this.userService.getUsers(); } }
HTTP Client dan API
🌐 Setup HTTP Client
// app.module.ts import { HttpClientModule } from '@angular/common/http'; @NgModule({ imports: [ BrowserModule, HttpClientModule ], // ... })
📡 Membuat HTTP Service
// api.service.ts import { Injectable } from "@angular/core"; import { HttpClient, HttpHeaders } from "@angular/common/http"; import { Observable } from "rxjs"; @Injectable({ providedIn: "root", }) export class ApiService { private apiUrl = "https://jsonplaceholder.typicode.com"; constructor(private http: HttpClient) {} // GET request getPosts(): Observable<any[]> { return this.http.get<any[]>(`${this.apiUrl}/posts`); } // GET by ID getPost(id: number): Observable<any> { return this.http.get<any>(`${this.apiUrl}/posts/${id}`); } // POST request createPost(post: any): Observable<any> { return this.http.post<any>(`${this.apiUrl}/posts`, post, { headers: new HttpHeaders({ "Content-Type": "application/json", }), }); } // PUT request updatePost(id: number, post: any): Observable<any> { return this.http.put<any>(`${this.apiUrl}/posts/${id}`, post); } // DELETE request deletePost(id: number): Observable<any> { return this.http.delete<any>(`${this.apiUrl}/posts/${id}`); } }
📊 Menggunakan HTTP Service
// posts.component.ts import { Component, OnInit } from "@angular/core"; import { ApiService } from "../services/api.service"; @Component({ selector: "app-posts", templateUrl: "./posts.component.html", }) export class PostsComponent implements OnInit { posts: any[] = []; loading = false; error = ""; constructor(private apiService: ApiService) {} ngOnInit() { this.loadPosts(); } loadPosts() { this.loading = true; this.apiService.getPosts().subscribe({ next: (data) => { this.posts = data; this.loading = false; }, error: (error) => { this.error = "Failed to load posts"; this.loading = false; }, }); } createPost() { const newPost = { title: "New Post", body: "Post content", userId: 1, }; this.apiService.createPost(newPost).subscribe({ next: (post) => { this.posts.unshift(post); }, error: (error) => { console.error("Error creating post:", error); }, }); } }
Routing dan Navigation
🛣️ Setup Routing
// app-routing.module.ts import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { HomeComponent } from "./home/home.component"; import { AboutComponent } from "./about/about.component"; import { ContactComponent } from "./contact/contact.component"; import { UserDetailComponent } from "./user-detail/user-detail.component"; const routes: Routes = [ { path: "", component: HomeComponent }, { path: "about", component: AboutComponent }, { path: "contact", component: ContactComponent }, { path: "user/:id", component: UserDetailComponent }, { path: "**", redirectTo: "" }, // Wildcard route ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], }) export class AppRoutingModule {}
🧭 Router Outlet dan Navigation
<!-- app.component.html --> <nav> <ul> <li><a routerLink="/">Home</a></li> <li><a routerLink="/about">About</a></li> <li><a routerLink="/contact">Contact</a></li> </ul> </nav> <router-outlet></router-outlet>
📍 Programmatic Navigation
// navigation.component.ts import { Component } from "@angular/core"; import { Router, ActivatedRoute } from "@angular/router"; @Component({ selector: "app-navigation", }) export class NavigationComponent { constructor( private router: Router, private route: ActivatedRoute ) {} navigateToUser(userId: number) { this.router.navigate(["/user", userId]); } navigateWithQuery() { this.router.navigate(["/search"], { queryParams: { q: "angular", page: 1 }, }); } goBack() { window.history.back(); } }
🔍 Route Parameters
// user-detail.component.ts import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; @Component({ selector: "app-user-detail", }) export class UserDetailComponent implements OnInit { userId: string | null = null; constructor(private route: ActivatedRoute) {} ngOnInit() { // Get route parameter this.userId = this.route.snapshot.paramMap.get("id"); // Or subscribe to parameter changes this.route.paramMap.subscribe((params) => { this.userId = params.get("id"); }); // Get query parameters this.route.queryParams.subscribe((params) => { const search = params["q"]; const page = params["page"]; }); } }
Forms dalam Angular
📝 Template-driven Forms
// app.module.ts import { FormsModule } from '@angular/forms'; @NgModule({ imports: [FormsModule] })
<!-- contact-form.component.html --> <form #contactForm="ngForm" (ngSubmit)="onSubmit(contactForm)"> <div> <label>Name:</label> <input type="text" name="name" [(ngModel)]="contact.name" #name="ngModel" required minlength="2" /> <div *ngIf="name.invalid && name.touched">Name is required (min 2 characters)</div> </div> <div> <label>Email:</label> <input type="email" name="email" [(ngModel)]="contact.email" #email="ngModel" required email /> <div *ngIf="email.invalid && email.touched">Valid email is required</div> </div> <button type="submit" [disabled]="contactForm.invalid">Submit</button> </form>
// contact-form.component.ts export class ContactFormComponent { contact = { name: "", email: "", message: "", }; onSubmit(form: any) { if (form.valid) { console.log("Form submitted:", this.contact); } } }
🏗️ Reactive Forms
// app.module.ts import { ReactiveFormsModule } from '@angular/forms'; @NgModule({ imports: [ReactiveFormsModule] })
// user-form.component.ts import { Component, OnInit } from "@angular/core"; import { FormBuilder, FormGroup, Validators } from "@angular/forms"; @Component({ selector: "app-user-form", }) export class UserFormComponent implements OnInit { userForm: FormGroup; constructor(private fb: FormBuilder) { this.userForm = this.fb.group({ name: ["", [Validators.required, Validators.minLength(2)]], email: ["", [Validators.required, Validators.email]], age: ["", [Validators.required, Validators.min(18)]], address: this.fb.group({ street: [""], city: [""], country: [""], }), }); } ngOnInit() {} onSubmit() { if (this.userForm.valid) { console.log(this.userForm.value); } else { this.markFormGroupTouched(); } } markFormGroupTouched() { Object.keys(this.userForm.controls).forEach((key) => { const control = this.userForm.get(key); control?.markAsTouched(); }); } get name() { return this.userForm.get("name"); } get email() { return this.userForm.get("email"); } get age() { return this.userForm.get("age"); } }
<!-- user-form.component.html --> <form [formGroup]="userForm" (ngSubmit)="onSubmit()"> <div> <label>Name:</label> <input type="text" formControlName="name" /> <div *ngIf="name?.invalid && name?.touched"> <div *ngIf="name?.errors?.['required']">Name is required</div> <div *ngIf="name?.errors?.['minlength']">Name must be at least 2 characters</div> </div> </div> <div> <label>Email:</label> <input type="email" formControlName="email" /> <div *ngIf="email?.invalid && email?.touched"> <div *ngIf="email?.errors?.['required']">Email is required</div> <div *ngIf="email?.errors?.['email']">Invalid email format</div> </div> </div> <div formGroupName="address"> <h3>Address</h3> <input type="text" formControlName="street" placeholder="Street" /> <input type="text" formControlName="city" placeholder="City" /> <input type="text" formControlName="country" placeholder="Country" /> </div> <button type="submit" [disabled]="userForm.invalid">Submit</button> </form>
Lifecycle Hooks
🔄 Component Lifecycle
Angular menyediakan lifecycle hooks untuk menjalankan kode pada tahap tertentu:
import { Component, OnInit, OnDestroy, OnChanges, AfterViewInit, SimpleChanges } from "@angular/core"; @Component({ selector: "app-lifecycle", }) export class LifecycleComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit { constructor() { console.log("1. Constructor called"); } ngOnChanges(changes: SimpleChanges) { console.log("2. ngOnChanges called", changes); } ngOnInit() { console.log("3. ngOnInit called"); // Initialization logic here // API calls, setup subscriptions } ngAfterViewInit() { console.log("4. ngAfterViewInit called"); // DOM manipulation after view init } ngOnDestroy() { console.log("5. ngOnDestroy called"); // Cleanup logic here // Unsubscribe from observables } }
Pipes dan Transformasi Data
🔧 Built-in Pipes
<!-- Built-in Pipes --> <p>{{ message | uppercase }}</p> <p>{{ message | lowercase }}</p> <p>{{ price | currency:'USD':'symbol':'1.2-2' }}</p> <p>{{ today | date:'fullDate' }}</p> <p>{{ data | json }}</p> <!-- Chaining Pipes --> <p>{{ message | uppercase | slice:0:10 }}</p>
🛠️ Custom Pipes
ng generate pipe pipes/truncate
// truncate.pipe.ts import { Pipe, PipeTransform } from "@angular/core"; @Pipe({ name: "truncate", }) export class TruncatePipe implements PipeTransform { transform(value: string, limit: number = 25, trail: string = "..."): string { if (!value) return ""; return value.length > limit ? value.substring(0, limit) + trail : value; } }
<!-- Usage --> <p>{{ longText | truncate:50:'...' }}</p>
🛠️ Project Tutorial: Membuat Todo List Sederhana
Mari kita praktik dengan membuat aplikasi Todo List sederhana yang mencakup semua konsep dasar Angular yang sudah kita pelajari.
🎯 Apa yang Akan Kita Buat?
Fitur Todo List:
- ✅ Menampilkan daftar todo
- ➕ Menambah todo baru
- ✏️ Edit todo yang sudah ada
- ❌ Hapus todo
- ✔️ Tandai todo sebagai selesai/belum selesai
- 📊 Counter jumlah todo aktif
Analogi: Seperti daftar belanja digital yang bisa dicoret-coret dan ditambah-tambah.
Step 1: Persiapan Project
# Membuat project baru ng new todo-app # Pilihan konfigurasi: # - Would you like to add Angular routing? (Y/n) -> n (tidak perlu untuk project sederhana) # - Which stylesheet format would you like to use? -> CSS # Masuk ke direktori project cd todo-app # Install Angular Material (opsional, untuk styling) ng add @angular/material # Jalankan development server ng serve
Step 2: Buat Interface untuk Todo
Buat file src/app/interfaces/todo.interface.ts
:
// todo.interface.ts export interface Todo { id: number; task: string; completed: boolean; createdAt: Date; }
Analogi: Interface seperti blueprint atau template yang menentukan bentuk data todo kita.
Step 3: Buat Todo Service
ng generate service services/todo
// todo.service.ts import { Injectable } from "@angular/core"; import { BehaviorSubject, Observable } from "rxjs"; import { Todo } from "../interfaces/todo.interface"; @Injectable({ providedIn: "root", }) export class TodoService { private todos: Todo[] = [ { id: 1, task: "Belajar Angular", completed: false, createdAt: new Date() }, { id: 2, task: "Buat Todo App", completed: false, createdAt: new Date() }, { id: 3, task: "Deploy ke Production", completed: false, createdAt: new Date() }, ]; private todosSubject = new BehaviorSubject<Todo[]>(this.todos); public todos$ = this.todosSubject.asObservable(); constructor() {} // Ambil semua todos getTodos(): Observable<Todo[]> { return this.todos$; } // Tambah todo baru addTodo(task: string): void { const newTodo: Todo = { id: this.generateId(), task: task.trim(), completed: false, createdAt: new Date(), }; this.todos.push(newTodo); this.todosSubject.next([...this.todos]); } // Toggle status completed toggleTodo(id: number): void { const todo = this.todos.find((t) => t.id === id); if (todo) { todo.completed = !todo.completed; this.todosSubject.next([...this.todos]); } } // Update task updateTodo(id: number, newTask: string): void { const todo = this.todos.find((t) => t.id === id); if (todo) { todo.task = newTask.trim(); this.todosSubject.next([...this.todos]); } } // Hapus todo deleteTodo(id: number): void { this.todos = this.todos.filter((t) => t.id !== id); this.todosSubject.next([...this.todos]); } // Hapus semua todo yang sudah completed clearCompleted(): void { this.todos = this.todos.filter((t) => !t.completed); this.todosSubject.next([...this.todos]); } // Generate ID unik private generateId(): number { return this.todos.length > 0 ? Math.max(...this.todos.map((t) => t.id)) + 1 : 1; } // Hitung todo yang belum selesai getActiveTodosCount(): Observable<number> { return new Observable((observer) => { this.todos$.subscribe((todos) => { const activeCount = todos.filter((t) => !t.completed).length; observer.next(activeCount); }); }); } }
Analogi Service: TodoService seperti asisten pribadi yang:
- Menyimpan semua daftar todo (memory)
- Bisa tambah/hapus/edit todo (actions)
- Memberitahu semua component kalau ada perubahan (notifications)
Step 4: Buat Todo List Component
ng generate component components/todo-list
// todo-list.component.ts import { Component, OnInit } from "@angular/core"; import { TodoService } from "../../services/todo.service"; import { Todo } from "../../interfaces/todo.interface"; import { Observable } from "rxjs"; @Component({ selector: "app-todo-list", templateUrl: "./todo-list.component.html", styleUrls: ["./todo-list.component.css"], }) export class TodoListComponent implements OnInit { todos$: Observable<Todo[]>; activeTodosCount$: Observable<number>; editingTodoId: number | null = null; editingTask: string = ""; constructor(private todoService: TodoService) { this.todos$ = this.todoService.getTodos(); this.activeTodosCount$ = this.todoService.getActiveTodosCount(); } ngOnInit(): void {} toggleTodo(id: number): void { this.todoService.toggleTodo(id); } deleteTodo(id: number): void { if (confirm("Yakin mau hapus todo ini?")) { this.todoService.deleteTodo(id); } } startEditing(todo: Todo): void { this.editingTodoId = todo.id; this.editingTask = todo.task; } saveEdit(): void { if (this.editingTodoId !== null && this.editingTask.trim()) { this.todoService.updateTodo(this.editingTodoId, this.editingTask); this.cancelEditing(); } } cancelEditing(): void { this.editingTodoId = null; this.editingTask = ""; } clearCompleted(): void { if (confirm("Hapus semua todo yang sudah selesai?")) { this.todoService.clearCompleted(); } } }
<!-- todo-list.component.html --> <div class="todo-container"> <h1>📝 My Todo List</h1> <!-- Stats --> <div class="stats"> <p>Todo aktif: <strong>{{ activeTodosCount$ | async }}</strong></p> <button class="btn-clear" (click)="clearCompleted()">🗑️ Hapus yang Selesai</button> </div> <!-- Todo List --> <div class="todo-list" *ngIf="todos$ | async as todos"> <div *ngIf="todos.length === 0" class="empty-state"> <p>🎉 Tidak ada todo! Santai dulu...</p> </div> <div *ngFor="let todo of todos; trackBy: trackByTodoId" class="todo-item" [class.completed]="todo.completed" > <!-- Display Mode --> <div *ngIf="editingTodoId !== todo.id" class="todo-display"> <input type="checkbox" [checked]="todo.completed" (change)="toggleTodo(todo.id)" class="todo-checkbox" /> <span class="todo-text" [class.line-through]="todo.completed"> {{ todo.task }} </span> <div class="todo-actions"> <button class="btn-edit" (click)="startEditing(todo)">✏️</button> <button class="btn-delete" (click)="deleteTodo(todo.id)">❌</button> </div> </div> <!-- Edit Mode --> <div *ngIf="editingTodoId === todo.id" class="todo-edit"> <input type="text" [(ngModel)]="editingTask" (keyup.enter)="saveEdit()" (keyup.escape)="cancelEditing()" class="edit-input" #editInput /> <div class="edit-actions"> <button class="btn-save" (click)="saveEdit()">✅</button> <button class="btn-cancel" (click)="cancelEditing()">❌</button> </div> </div> </div> </div> </div>
/* todo-list.component.css */ .todo-container { max-width: 600px; margin: 0 auto; padding: 20px; font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; } h1 { text-align: center; color: #333; margin-bottom: 30px; } .stats { display: flex; justify-content: space-between; align-items: center; background-color: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px; } .stats p { margin: 0; color: #666; } .btn-clear { background-color: #dc3545; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; } .btn-clear:hover { background-color: #c82333; } .empty-state { text-align: center; color: #666; font-style: italic; padding: 40px; } .todo-item { background-color: white; border: 1px solid #dee2e6; border-radius: 8px; margin-bottom: 10px; padding: 15px; transition: all 0.3s ease; } .todo-item:hover { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .todo-item.completed { background-color: #f8f9fa; opacity: 0.7; } .todo-display { display: flex; align-items: center; gap: 12px; } .todo-checkbox { width: 18px; height: 18px; cursor: pointer; } .todo-text { flex-grow: 1; font-size: 16px; color: #333; } .todo-text.line-through { text-decoration: line-through; color: #666; } .todo-actions { display: flex; gap: 8px; } .btn-edit, .btn-delete, .btn-save, .btn-cancel { background: none; border: none; font-size: 16px; cursor: pointer; padding: 4px; border-radius: 4px; } .btn-edit:hover, .btn-save:hover { background-color: #e7f3ff; } .btn-delete:hover, .btn-cancel:hover { background-color: #ffebee; } .todo-edit { display: flex; align-items: center; gap: 12px; } .edit-input { flex-grow: 1; padding: 8px 12px; border: 2px solid #007bff; border-radius: 4px; font-size: 16px; outline: none; } .edit-actions { display: flex; gap: 8px; }
Step 5: Buat Add Todo Component
ng generate component components/add-todo
// add-todo.component.ts import { Component } from "@angular/core"; import { TodoService } from "../../services/todo.service"; @Component({ selector: "app-add-todo", templateUrl: "./add-todo.component.html", styleUrls: ["./add-todo.component.css"], }) export class AddTodoComponent { newTodoTask: string = ""; constructor(private todoService: TodoService) {} addTodo(): void { if (this.newTodoTask.trim()) { this.todoService.addTodo(this.newTodoTask); this.newTodoTask = ""; // Reset input } } onKeyUp(event: KeyboardEvent): void { if (event.key === "Enter") { this.addTodo(); } } }
<!-- add-todo.component.html --> <div class="add-todo-container"> <h2>➕ Tambah Todo Baru</h2> <div class="add-todo-form"> <input type="text" [(ngModel)]="newTodoTask" (keyup)="onKeyUp($event)" placeholder="Apa yang mau dikerjakan hari ini?" class="todo-input" #todoInput /> <button (click)="addTodo()" [disabled]="!newTodoTask.trim()" class="btn-add">Tambah</button> </div> <p class="hint">💡 Tip: Tekan Enter untuk menambah todo</p> </div>
/* add-todo.component.css */ .add-todo-container { max-width: 600px; margin: 0 auto 30px; padding: 20px; } h2 { color: #333; margin-bottom: 20px; } .add-todo-form { display: flex; gap: 12px; margin-bottom: 10px; } .todo-input { flex-grow: 1; padding: 12px 16px; border: 2px solid #dee2e6; border-radius: 8px; font-size: 16px; outline: none; transition: border-color 0.3s ease; } .todo-input:focus { border-color: #007bff; box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1); } .btn-add { background-color: #28a745; color: white; border: none; padding: 12px 24px; border-radius: 8px; font-size: 16px; cursor: pointer; transition: background-color 0.3s ease; } .btn-add:hover:not(:disabled) { background-color: #218838; } .btn-add:disabled { background-color: #6c757d; cursor: not-allowed; } .hint { color: #666; font-size: 14px; font-style: italic; margin: 0; text-align: center; }
Step 6: Update App Module
// app.module.ts import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { FormsModule } from "@angular/forms"; import { AppComponent } from "./app.component"; import { TodoListComponent } from "./components/todo-list/todo-list.component"; import { AddTodoComponent } from "./components/add-todo/add-todo.component"; @NgModule({ declarations: [AppComponent, TodoListComponent, AddTodoComponent], imports: [ BrowserModule, FormsModule, // Untuk ngModel ], providers: [], bootstrap: [AppComponent], }) export class AppModule {}
Step 7: Update App Component
// app.component.ts import { Component } from "@angular/core"; @Component({ selector: "app-root", templateUrl: "./app.component.html", styleUrls: ["./app.component.css"], }) export class AppComponent { title = "Todo App with Angular"; }
<!-- app.component.html --> <div class="app-container"> <header class="app-header"> <h1>🚀 {{ title }}</h1> <p>Belajar Angular dengan membuat Todo List</p> </header> <main> <app-add-todo></app-add-todo> <app-todo-list></app-todo-list> </main> <footer class="app-footer"> <p>Made with ❤️ using Angular</p> </footer> </div>
/* app.component.css */ .app-container { min-height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; } .app-header { text-align: center; color: white; margin-bottom: 40px; } .app-header h1 { margin: 0 0 10px 0; font-size: 2.5rem; } .app-header p { margin: 0; font-size: 1.2rem; opacity: 0.9; } main { background-color: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); overflow: hidden; } .app-footer { text-align: center; color: white; margin-top: 40px; opacity: 0.8; }
Step 8: Update Global Styles
/* src/styles.css */ * { box-sizing: border-box; } body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } /* Custom scrollbar */ ::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-track { background: #f1f1f1; } ::-webkit-scrollbar-thumb { background: #888; border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: #555; }
Step 9: Jalankan Aplikasi
ng serve
Buka browser dan akses http://localhost:4200
. Anda akan melihat aplikasi Todo List yang lengkap!
🎉 Hasil Akhir
Fitur yang berhasil dibuat:
- ✅ Tampilan daftar todo yang cantik
- ➕ Form untuk menambah todo baru
- ✏️ Edit todo dengan inline editing
- ❌ Hapus todo individual
- ✔️ Toggle status completed
- 🗑️ Hapus semua todo yang completed
- 📊 Counter todo aktif
- 🎨 Responsive design yang menarik
📚 Konsep Angular yang Dipraktikkan
- Components - AddTodo & TodoList components
- Services - TodoService untuk data management
- Interfaces - Todo interface untuk type safety
- Data Binding - Two-way binding, event binding, property binding
- Directives - *ngFor, *ngIf, ngClass
- Dependency Injection - Inject TodoService ke components
- Observables - Reactive programming dengan RxJS
- Lifecycle Hooks - OnInit untuk initialization
Analogi Project: Seperti membangun dapur mini dimana:
- Components = peralatan dapur (kompor, kulkas)
- Service = chef yang tahu semua resep
- Interface = daftar menu standar
- Data Binding = sistem komunikasi antar peralatan
Selamat! Anda telah berhasil membuat aplikasi Todo List lengkap dengan Angular! 🎊
✅ Struktur Proyek
src/ ├── app/ │ ├── core/ │ │ ├── guards/ │ │ ├── interceptors/ │ │ └── services/ │ ├── shared/ │ │ ├── components/ │ │ ├── directives/ │ │ └── pipes/ │ ├── features/ │ │ ├── user/ │ │ ├── product/ │ │ └── order/ │ └── layouts/
🎯 Coding Standards
-
Naming Conventions
- Components:
user-profile.component.ts
- Services:
user.service.ts
- Modules:
user.module.ts
- Components:
-
Component Communication
// Parent to Child - @Input @Input() user: User; // Child to Parent - @Output @Output() userSelected = new EventEmitter<User>(); // Service for complex communication constructor(private sharedService: SharedService) {} -
Memory Management
export class Component implements OnDestroy { private destroy$ = new Subject<void>(); ngOnInit() { this.apiService .getData() .pipe(takeUntil(this.destroy$)) .subscribe((data) => {}); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } }
Deployment
🚀 Build untuk Production
# Build optimized untuk production ng build --prod # Output akan ada di folder dist/
🌐 Deploy ke berbagai platform
Vercel:
npm install -g vercel vercel --prod
Netlify:
npm run build # Upload folder dist/ ke Netlify
Firebase Hosting:
npm install -g firebase-tools ng add @angular/fire ng deploy
Kesimpulan
Angular adalah framework yang powerful untuk membangun aplikasi web modern. Tutorial ini mencakup:
- ✅ Setup environment dan struktur proyek
- ✅ Components, services, dan modules
- ✅ Data binding dan directives
- ✅ HTTP client dan API integration
- ✅ Routing dan navigation
- ✅ Forms (template-driven dan reactive)
- ✅ Lifecycle hooks dan pipes
- ✅ Best practices dan deployment
🎯 Langkah Selanjutnya
- Praktik: Buat proyek kecil seperti todo app atau blog
- Pelajari Angular Material: UI component library
- State Management: NgRx untuk aplikasi complex
- Testing: Unit testing dengan Jasmine & Karma
- Advanced Topics: Guards, interceptors, lazy loading
📚 Resources Tambahan
Selamat belajar Angular! 🚀