Tutorial Angular untuk Pemula - Panduan Lengkap dari Nol

Tutorial Angular untuk Pemula - Panduan Lengkap dari Nol

author

Teguh Bagas M

Web Developer

Tags

Share this post

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:

  1. Node.js (versi 16 atau lebih baru)
  2. npm atau yarn package manager
  3. Code Editor (VS Code direkomendasikan)
  4. 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:

  1. TypeScript Class - Logic component
  2. HTML Template - UI component
  3. 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

  1. Components - AddTodo & TodoList components
  2. Services - TodoService untuk data management
  3. Interfaces - Todo interface untuk type safety
  4. Data Binding - Two-way binding, event binding, property binding
  5. Directives - *ngFor, *ngIf, ngClass
  6. Dependency Injection - Inject TodoService ke components
  7. Observables - Reactive programming dengan RxJS
  8. 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

  1. Naming Conventions

    • Components: user-profile.component.ts
    • Services: user.service.ts
    • Modules: user.module.ts
  2. 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) {}
  3. 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

  1. Praktik: Buat proyek kecil seperti todo app atau blog
  2. Pelajari Angular Material: UI component library
  3. State Management: NgRx untuk aplikasi complex
  4. Testing: Unit testing dengan Jasmine & Karma
  5. Advanced Topics: Guards, interceptors, lazy loading

📚 Resources Tambahan

Selamat belajar Angular! 🚀