☀️Siang
Web Development

Angular: Framework Web Enterprise

Tutorial lengkap belajar Angular dari nol — components, services, routing, RxJS, dependency injection, dan best practices untuk membangun aplikasi enterprise yang scalable

Artikel: Angular Basics Artikel: Angular Basics


1. Pengenalan Angular

Angular adalah framework web yang dikembangkan oleh Google dan pertama kali dirilis pada tahun 2016 sebagai versi rewrite dari AngularJS. Angular menggunakan TypeScript sebagai bahasa utama dan dirancang khusus untuk membangun aplikasi web enterprise yang kompleks, scalable, dan mudah di-maintain.

Berbeda dengan library seperti React yang hanya menangani view layer, Angular bersifat opinionated full framework yang menyediakan semua tools yang dibutuhkan: routing, form handling, HTTP client, testing, dan banyak lagi — semuanya terintegrasi dalam satu paket.

Mengapa Memilih Angular?

Keunggulan Penjelasan
TypeScript NativeAngular dibangun dengan TypeScript yang menyediakan type safety, autocompletion, dan refactoring yang lebih baik
Full FrameworkSemua tools sudah tersedia dari awal — routing, HTTP, forms, testing, i18n
Dependency InjectionSistem DI bawaan yang powerful untuk mengelola dependencies antar komponen
CLI yang PowerfulAngular CLI memudahkan generate components, services, testing, dan build production
Enterprise ReadyBanyak digunakan di perusahaan besar seperti Google, Microsoft, Deutsche Bank
RxJS IntegrationDukungan reactive programming bawaan untuk menangani async operations

Angular vs Framework Lain

Aspek Angular React Vue.js
TipeFull FrameworkLibraryFramework
BahasaTypeScriptJSX/TSXJavaScript/Template
Ukuran~143 KB (gzip)~42 KB (gzip)~33 KB (gzip)
Learning Curve🔴 Curam🟡 Sedang🟢 Mudah
State ManagementNgRx / Services / SignalsRedux / ZustandVuex / Pinia
DOMIncremental DOMVirtual DOMVirtual DOM
Cocok untukEnterprise, Banking, SPA besarSPA, Mobile AppSPA kecil-sedang
Diagram: Angular Application Architecture

AppModule (Root)

Feature Modules User, Admin

Shared Module (Common)

AppComponent

Router Outlet

Navbar Component shared

Home Component default route

About Component /about route

Footer Component shared

AuthService

DataService

NotificationSvc

💡 Angular vs AngularJS

Jangan bingung antara Angular (v2+ sampai v17+) dan AngularJS (v1.x). Angular adalah rewrite total yang menggunakan TypeScript, component-based architecture, dan performa jauh lebih baik. AngularJS sudah deprecated sejak Desember 2021.

2. Setup dan Instalasi Angular CLI

Untuk memulai dengan Angular, kita perlu menginstal Angular CLI (Command Line Interface) yang merupakan tool resmi untuk membuat, mengembangkan, dan mem-build aplikasi Angular.

Prasyarat

  • Node.js versi 18.13 atau lebih baru
  • npm versi 8 atau lebih baru (sudah terinstal bersama Node.js)
  • Text Editor — VS Code sangat direkomendasikan dengan extension Angular Language Service

Instalasi Angular CLI

Diagram

my-angular-app/

src/

app/

app.component.ts

app.component.html

app.component.css

app.component.spec.ts

app.config.ts

app.routes.ts

assets/

environments/

index.html

main.ts

styles.css

angular.json

package.json

tsconfig.json

tsconfig.app.json

⚠️ Standalone Components (Angular 17+)

Sejak Angular 14+, Angular mendukung standalone components yang tidak memerlukan NgModule. Mulai Angular 15-17, standalone menjadi default. Dalam tutorial ini kita menggunakan pendekatan standalone terbaru.

3. Memahami Components

Components adalah blok bangunan utama dari setiap aplikasi Angular. Setiap component terdiri dari tiga bagian utama: template (HTML), class (TypeScript), dan styles (CSS/SCSS).

Membuat Component

Diagram

No. Generate component menggunakan CLI

user-card.component.ts

user-card.component.html

user-card.component.css

user-card.component.spec.ts

Anatomi Component

Diagram

Injector (Root) DI Container

UserService singleton

AuthService singleton

UserList Cmp inject(UserService)

UserCard Cmp inject(UserService)

LoginPage Cmp inject(AuthService)

Dashboard Cmp inject(AuthService)

Pattern 1 service → many cmps singleton per inj...

Membuat dan Menggunakan Service

bash
Diagram
# Generate service menggunakan CLI

ng generate service services/product

# Atau shorthand:

ng g s services/product

typescript

// product.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject, catchError, throwError } from 'rxjs';

export interface Product {
  id: number;
  name: string;
  price: number;
  category: string;
  stock: number;
}

@Injectable({
  providedIn: 'root'  // Singleton tersedia di seluruh app
})
export class ProductService {
  private apiUrl = 'https://api.example.com/products';

  // BehaviorSubject untuk menyimpan state
  private productsSubject = new BehaviorSubject<Product[]>([]);
  public products$ = this.productsSubject.asObservable();

  constructor(private http: HttpClient) {}

  // GET semua produk
  getProducts(): Observable<Product[]> {
    return this.http.get<Product[]>(this.apiUrl).pipe(
      catchError(this.handleError)
    );
  }

  // GET produk berdasarkan ID
  getProductById(id: number): Observable<Product> {
    return this.http.get<Product>(`${this.apiUrl}/${id}`).pipe(
      catchError(this.handleError)
    );
  }

  // POST produk baru
  createProduct(product: Omit<Product, 'id'>): Observable<Product> {
    return this.http.post<Product>(this.apiUrl, product).pipe(
      catchError(this.handleError)
    );
  }

  // PUT update produk
  updateProduct(id: number, product: Partial<Product>): Observable<Product> {
    return this.http.put<Product>(`${this.apiUrl}/${id}`, product).pipe(
      catchError(this.handleError)
    );
  }

  // DELETE produk
  deleteProduct(id: number): Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/${id}`).pipe(
      catchError(this.handleError)
    );
  }

  // Error handler
  private handleError(error: any): Observable<never> {
    console.error('API Error:', error);
    return throwError(() => new Error(
      error.message || 'Terjadi kesalahan pada server'
    ));
  }
}

Providing Services — Level yang Berbeda

typescript

// 1. Root level (singleton di seluruh app)
@Injectable({ providedIn: 'root' })
export class GlobalService { }

// 2. Module level (singleton di dalam module)
@NgModule({
  providers: [ModuleService]
})
export class FeatureModule { }

// 3. Component level (instance baru per component)
@Component({
  selector: 'app-demo',
  providers: [LocalService]
})
export class DemoComponent { }

// 4. Standalone component level
@Component({
  selector: 'app-demo',
  standalone: true,
  providers: [LocalService]  // Instance baru setiap kali
})
export class DemoComponent { }

7. Routing dan Navigasi

Angular Router memungkinkan kita membangun Single Page Application (SPA) dengan navigasi antar halaman tanpa reload browser. Router mendukung lazy loading, route guards, nested routes, dan banyak fitur lainnya.

Konfigurasi Routes

typescript

// app.routes.ts
import { Routes } from '@angular/router';
import { authGuard } from './guards/auth.guard';

export const routes: Routes = [
  {
    path: '',
    redirectTo: '/home',
    pathMatch: 'full'
  },
  {
    path: 'home',
    loadComponent: () =>
      import('./pages/home/home.component')
        .then(m => m.HomeComponent),
    title: 'Beranda - MyApp'
  },
  {
    path: 'products',
    loadComponent: () =>
      import('./pages/products/products.component')
        .then(m => m.ProductsComponent),
    title: 'Produk - MyApp'
  },
  {
    path: 'products/:id',
    loadComponent: () =>
      import('./pages/product-detail/product-detail.component')
        .then(m => m.ProductDetailComponent),
    title: 'Detail Produk - MyApp'
  },
  {
    path: 'admin',
    canActivate: [authGuard],
    loadComponent: () =>
      import('./pages/admin/admin.component')
        .then(m => m.AdminComponent),
    title: 'Admin Panel',
    children: [
      {
        path: 'dashboard',
        loadComponent: () =>
          import('./pages/admin/dashboard/dashboard.component')
            .then(m => m.DashboardComponent)
      },
      {
        path: 'users',
        loadComponent: () =>
          import('./pages/admin/users/users.component')
            .then(m => m.UsersComponent)
      },
      {
        path: 'settings',
        loadComponent: () =>
          import('./pages/admin/settings/settings.component')
            .then(m => m.SettingsComponent)
      }
    ]
  },
  {
    path: '**',
    loadComponent: () =>
      import('./pages/not-found/not-found.component')
        .then(m => m.NotFoundComponent),
    title: 'Halaman Tidak Ditemukan'
  }
];

Router Outlet dan Navigasi

html

<!-- app.component.html -->
<app-navbar></app-navbar>

<main>
  <router-outlet></router-outlet>
</main>

<app-footer></app-footer>

<!-- navbar.component.html -->
<nav>
  <a routerLink="/home" routerLinkActive="active">Beranda</a>
  <a routerLink="/products" routerLinkActive="active">Produk</a>
  <a [routerLink]="['/products', 42]">Produk #42</a>
  <a routerLink="/admin" routerLinkActive="active">Admin</a>
</nav>

Route Guard

typescript

// guards/auth.guard.ts
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';

export const authGuard: CanActivateFn = (route, state) => {
  const authService = inject(AuthService);
  const router = inject(Router);

  if (authService.isAuthenticated()) {
    return true;
  }

  // Redirect ke login dengan return URL
  router.navigate(['/login'], {
    queryParams: { returnUrl: state.url }
  });
  return false;
};

// Guard untuk role-based access
export const roleGuard: CanActivateFn = (route) => {
  const authService = inject(AuthService);
  const requiredRole = route.data['role'] as string;

  return authService.hasRole(requiredRole);
};

Navigasi Programmatic

typescript

import { Router, ActivatedRoute } from '@angular/router';

@Component({ /* ... */ })
export class ProductComponent {
  constructor(
    private router: Router,
    private route: ActivatedRoute
  ) {}

  // Navigasi sederhana
  goToHome(): void {
    this.router.navigate(['/home']);
  }

  // Navigasi dengan parameter
  goToProduct(id: number): void {
    this.router.navigate(['/products', id]);
  }

  // Navigasi dengan query params
  searchProducts(query: string): void {
    this.router.navigate(['/products'], {
      queryParams: { q: query, page: 1 }
    });
  }

  // Mengambil route params
  ngOnInit(): void {
    this.route.params.subscribe(params => {
      const id = +params['id'];
      this.loadProduct(id);
    });

    this.route.queryParams.subscribe(params => {
      const query = params['q'] || '';
      this.searchTerm = query;
    });
  }
}

8. RxJS: Reactive Programming

RxJS (Reactive Extensions for JavaScript) adalah library untuk reactive programming menggunakan Observables. Angular terintegrasi erat dengan RxJS untuk menangani operasi asynchronous seperti HTTP requests, event handling, dan state management.

Konsep Dasar RxJS

Konsep Penjelasan Analogi
ObservableStream data yang bisa dipublish seiring waktuKanal YouTube yang mengupload video berkala
ObserverYang menerima data dari ObservableSubscriber YouTube yang menonton video
SubscriptionLangganan aktif ke ObservableTombol Subscribe yang ditekan
OperatorFungsi untuk memanipulasi streamFilter, edit, atau gabungkan video
SubjectObservable dan Observer sekaligus (multicasting)Forum diskusi (bisa bicara dan mendengar)

Observable dan Operators

typescript

import { Observable, of, from, interval, Subject, BehaviorSubject } from 'rxjs';
import {
  map, filter, switchMap, mergeMap, debounceTime,
  distinctUntilChanged, takeUntil, catchError,
  tap, retry, delay, combineLatest
} from 'rxjs/operators';

// === 1. Membuat Observable ===
const numbers$ = new Observable<number>(subscriber => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  subscriber.complete();
});

// Subscribe ke Observable
numbers$.subscribe({
  next: value => console.log('Nilai:', value),
  error: err => console.error('Error:', err),
  complete: () => console.log('Selesai')
});

// === 2. Observable dari array / promise ===
const fruits$ = of('Apel', 'Mangga', 'Jeruk');
const numberArray$ = from([10, 20, 30, 40, 50]);
const timer$ = interval(1000); // Setiap 1 detik

// === 3. Operators: map, filter ===
const processed$ = numberArray$.pipe(
  map(x => x * 2),           // Kalikan 2
  filter(x => x > 40),       // Ambil yang > 40
  tap(x => console.log('Processed:', x)) // Side effect
);
// Output: 60, 80, 100

// === 4. debounceTime untuk search ===
const searchInput$ = new Subject<string>();

searchInput$.pipe(
  debounceTime(300),           // Tunggu 300ms setelah ketikan
  distinctUntilChanged(),      // Hanya jika nilai berubah
  filter(query => query.length >= 3),  // Min 3 karakter
  switchMap(query => this.searchService.search(query))  // Cancel prev
).subscribe(results => {
  this.searchResults = results;
});

// Trigger search dari template:
// onSearch(event: Event) {
//   this.searchInput$.next((event.target as HTMLInputElement).value);
// }

// === 5. combineLatest untuk data gabungan ===
const user$ = this.userService.getCurrentUser();
const settings$ = this.settingsService.getSettings();

combineLatest([user$, settings$]).pipe(
  map(([user, settings]) => ({
    ...user,
    theme: settings.theme,
    language: settings.language
  }))
).subscribe(combinedData => {
  this.appState = combinedData;
});

// === 6. Subject dan BehaviorSubject ===
// Subject — tidak punya nilai awal
const clickSubject$ = new Subject<string>();
clickSubject$.subscribe(val => console.log('Click:', val));
clickSubject$.next('button-1');

// BehaviorSubject — punya nilai awal
const authState$ = new BehaviorSubject<boolean>(false);
authState$.subscribe(state => console.log('Auth:', state));
// Output: Auth: false (nilai awal)
authState$.next(true);
// Output: Auth: true

Penggunaan RxJS di Component

typescript

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject, takeUntil, switchMap, startWith, catchError, of } from 'rxjs';
import { ProductService, Product } from '../services/product.service';

@Component({
  selector: 'app-product-list',
  standalone: true,
  template: `
    <input
      placeholder="Cari produk..."
      (input)="onSearch($event)"
    >

    <div *ngIf="loading" class="spinner">Loading...</div>

    <div *ngIf="error" class="error">{{ error }}</div>

    <div *ngFor="let product of products" class="product-card">
      <h3>{{ product.name }}</h3>
      <p>{{ product.price | currency:'IDR' }}</p>
    </div>
  `
})
export class ProductListComponent implements OnInit, OnDestroy {
  products: Product[] = [];
  loading = false;
  error = '';

  private destroy$ = new Subject<void>();
  private search$ = new Subject<string>();

  constructor(private productService: ProductService) {}

  ngOnInit(): void {
    // Reactive search dengan RxJS
    this.search$.pipe(
      startWith(''),           // Load semua saat pertama
      debounceTime(300),
      distinctUntilChanged(),
      tap(() => {
        this.loading = true;
        this.error = '';
      }),
      switchMap(query =>
        this.productService.getProducts().pipe(
          map(products =>
            query
              ? products.filter(p =>
                  p.name.toLowerCase().includes(query.toLowerCase()))
              : products
          ),
          catchError(err => {
            this.error = 'Gagal memuat data produk';
            return of([]);
          })
        )
      ),
      takeUntil(this.destroy$)
    ).subscribe(products => {
      this.products = products;
      this.loading = false;
    });
  }

  onSearch(event: Event): void {
    const query = (event.target as HTMLInputElement).value;
    this.search$.next(query);
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }
}
⚠️ Selalu Unsubscribe!

Selalu pastikan untuk unsubscribe dari Observable saat component di-destroy untuk menghindari memory leaks. Gunakan takeUntil pattern, async pipe di template, atau DestroyRef (Angular 16+).

9. HTTP Client dan API Communication

Angular menyediakan HttpClientModule yang terintegrasi dengan RxJS untuk melakukan HTTP requests ke backend API. Fitur ini mendukung interceptors, error handling, dan berbagai metode HTTP.

Mengkonfigurasi HttpClient

typescript

// app.config.ts — konfigurasi standalone
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { routes } from './app.routes';
import { authInterceptor } from './interceptors/auth.interceptor';
import { errorInterceptor } from './interceptors/error.interceptor';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(
      withInterceptors([authInterceptor, errorInterceptor])
    )
  ]
};

HTTP Interceptors

typescript

// interceptors/auth.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from '../services/auth.service';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const authService = inject(AuthService);
  const token = authService.getToken();

  if (token) {
    const cloned = req.clone({
      setHeaders: {
        Authorization: 'Bearer ' + token
      }
    });
    return next(cloned);
  }

  return next(req);
};

// interceptors/error.interceptor.ts
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { catchError, throwError } from 'rxjs';

export const errorInterceptor: HttpInterceptorFn = (req, next) => {
  const router = inject(Router);

  return next(req).pipe(
    catchError((error: HttpErrorResponse) => {
      switch (error.status) {
        case 401:
          router.navigate(['/login']);
          break;
        case 403:
          alert('Anda tidak memiliki akses');
          break;
        case 404:
          console.error('Resource tidak ditemukan');
          break;
        case 500:
          alert('Terjadi kesalahan server');
          break;
      }
      return throwError(() => error);
    })
  );
};

10. Form Handling di Angular

Angular menyediakan dua pendekatan untuk menangani forms: Template-Driven Forms dan Reactive Forms. Reactive Forms lebih disarankan untuk form kompleks karena memberikan kontrol lebih besar dan testability yang lebih baik.

Reactive Forms

typescript

import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
  ReactiveFormsModule,
  FormBuilder,
  FormGroup,
  FormArray,
  Validators
} from '@angular/forms';

@Component({
  selector: 'app-registration',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule],
  template: `
    <form [formGroup]="regForm" (ngSubmit)="onSubmit()">
      <div class="form-group">
        <label>Nama Lengkap</label>
        <input formControlName="fullName" placeholder="Nama Anda">
        <div class="error"
             *ngIf="regForm.get('fullName')?.invalid &&
                    regForm.get('fullName')?.touched">
          Nama wajib diisi (min 3 karakter)
        </div>
      </div>

      <div class="form-group">
        <label>Email</label>
        <input formControlName="email" type="email">
        <div class="error"
             *ngIf="regForm.get('email')?.errors?.['required'] &&
                    regForm.get('email')?.touched">
          Email wajib diisi
        </div>
        <div class="error"
             *ngIf="regForm.get('email')?.errors?.['email'] &&
                    regForm.get('email')?.touched">
          Format email tidak valid
        </div>
      </div>

      <div formGroupName="password">
        <div class="form-group">
          <label>Password</label>
          <input formControlName="pass" type="password">
        </div>
        <div class="form-group">
          <label>Konfirmasi Password</label>
          <input formControlName="confirmPass" type="password">
        </div>
      </div>

      <div formArrayName="skills">
        <label>Keahlian</label>
        <div *ngFor="let skill of skills.controls; let i = index">
          <input [formControlName]="i">
          <button type="button" (click)="removeSkill(i)">Hapus</button>
        </div>
        <button type="button" (click)="addSkill()">+ Tambah Skill</button>
      </div>

      <button type="submit" [disabled]="regForm.invalid">
        Daftar
      </button>
    </form>
  `
})
export class RegistrationComponent implements OnInit {
  regForm!: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit(): void {
    this.regForm = this.fb.group({
      fullName: ['', [Validators.required, Validators.minLength(3)]],
      email: ['', [Validators.required, Validators.email]],
      password: this.fb.group({
        pass: ['', [Validators.required, Validators.minLength(8)]],
        confirmPass: ['', Validators.required]
      }),
      skills: this.fb.array([
        this.fb.control('')
      ])
    });
  }

  get skills(): FormArray {
    return this.regForm.get('skills') as FormArray;
  }

  addSkill(): void {
    this.skills.push(this.fb.control(''));
  }

  removeSkill(index: number): void {
    this.skills.removeAt(index);
  }

  onSubmit(): void {
    if (this.regForm.valid) {
      console.log('Form Data:', this.regForm.value);
    } else {
      this.regForm.markAllAsTouched();
    }
  }
}


11. Best Practices dan Tips

Berikut adalah beberapa best practices yang harus diikuti saat mengembangkan aplikasi Angular untuk menghasilkan kode yang bersih, scalable, dan mudah di-maintain.

Daftar Best Practices

Praktik Penjelasan
Gunakan Standalone ComponentsAngular 15+ mendukung standalone — lebih ringan tanpa NgModule boilerplate
Lazy Loading RoutesGunakan loadComponent() untuk memuat halaman hanya saat dibutuhkan
OnPush Change DetectionGunakan ChangeDetectionStrategy.OnPush untuk performa lebih baik
Avoid Logic di TemplateSimpan logika di component class, bukan di template HTML
Unsubscribe ProperlyGunakan takeUntil, async pipe, atau DestroyRef
Folder Structure RapiGroup berdasarkan fitur, bukan berdasarkan tipe file
Environment VariablesGunakan environment.ts untuk konfigurasi berbeda (dev, staging, prod)
Unit TestingTest setiap component dan service menggunakan Jasmine/Karma atau Jest
typescript

// === OnPush Change Detection ===
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';

@Component({
  selector: 'app-user-display',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="user-display">
      <h2>{{ user.name }}</h2>
      <p>{{ user.email }}</p>
    </div>
  `
})
export class UserDisplayComponent {
  @Input({ required: true }) user!: { name: string; email: string };
}

// === Signal-based Components (Angular 16+) ===
import { Component, signal, computed, effect } from '@angular/core';

@Component({
  selector: 'app-counter',
  template: `
    <h2>Count: {{ count() }}</h2>
    <p>Doubled: {{ doubled() }}</p>
    <button (click)="increment()">+</button>
    <button (click)="decrement()">-</button>
  `
})
export class CounterComponent {
  count = signal(0);
  doubled = computed(() => this.count() * 2);

  constructor() {
    // Effect — runs when signals change
    effect(() => {
      console.log('Count changed to:', this.count());
    });
  }

  increment(): void {
    this.count.update(n => n + 1);
  }

  decrement(): void {
    this.count.update(n => n - 1);
  }
}
💡 Angular Signals (v16+)

Angular 16+ memperkenalkan Signals sebagai alternatif dari RxJS untuk state management sederhana. Signals menyediakan reactive state dengan API yang lebih intuitif: signal(), computed(), dan effect(). Ini membantu mengurangi boilerplate dan mempercepat development.



12. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Angular:

Pertanyaan 1: Apa bahasa pemrograman utama yang digunakan oleh Angular?

a) JavaScript
b) TypeScript
c) Dart
d) CoffeeScript

Pertanyaan 2: Apa fungsi dari Dependency Injection di Angular?

a) Untuk meng-upload file ke server
b) Untuk mengelola CSS styling
c) Untuk menyediakan instance services ke komponen yang membutuhkan
d) Untuk membuat animasi

Pertanyaan 3: Operator RxJS apa yang digunakan untuk membatalkan HTTP request sebelumnya saat ada request baru?

a) mergeMap
b) concatMap
c) switchMap
d) exhaustMap

Pertanyaan 4: Apa perbedaan utama antara Template-Driven Forms dan Reactive Forms?

a) Tidak ada perbedaan
b) Reactive Forms didefinisikan di component class dengan kontrol lebih besar, Template-Driven didefinisikan di template
c) Template-Driven lebih cepat performanya
d) Reactive Forms tidak bisa digunakan dengan validasi

Pertanyaan 5: Apa fungsi dari router-outlet di Angular?

a) Menampilkan loading indicator
b) Menjadi tempat di-render-nya component berdasarkan route aktif
c) Mengatur tema aplikasi
d) Meng-handle error routing
🔍 Zoom
100%
🎨 Tema