Basics

Angular CLI Commands

Essential commands for Angular development:

// Create a new project
ng new my-app

// Serve the application
ng serve --open

// Generate component
ng generate component components/my-component

// Generate service
ng generate service services/my-service

// Build for production
ng build --configuration production

Component Anatomy

Structure of a basic Angular component:

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-my-component',
  template: `
    <div class="container">
      <h2>{{ title }}</h2>
      <button (click)="handleClick()">Click Me</button>
    </div>
  `,
  styles: [`
    .container { padding: 20px; }
  `]
})
export class MyComponent {
  @Input() title: string = 'Default Title';
  @Output() buttonClicked = new EventEmitter<string>();

  handleClick() {
    this.buttonClicked.emit('Button clicked!');
  }
}

Dependency Injection

How to inject services into components:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  getData() {
    return ['item1', 'item2', 'item3'];
  }
}

// In your component
export class MyComponent {
  constructor(private dataService: DataService) {}

  items = this.dataService.getData();
}

Template Syntax

Common template binding techniques:

<!-- Interpolation -->
<p>{{ message }}</p>

<!-- Property binding -->
<img [src]="imageUrl" [alt]="imageAlt" />

<!-- Event binding -->
<button (click)="onClick($event)">Click</button>

<!-- Two-way binding -->
<input [(ngModel)]="userName" />

<!-- Attribute binding -->
<button [attr.aria-label]="buttonLabel">Submit</button>

Common Directives

Essential structural and attribute directives:

<!-- Conditional rendering (OLD) -->
<div *ngIf="isVisible">Visible content</div>

<!-- Loop through array (OLD) -->
<ul>
  <li *ngFor="let item of items">{{ item }}</li>
</ul>

<!-- Switch statement (OLD) -->
<div [ngSwitch]="status">
  <div *ngSwitchCase="'active'">Active</div>
  <div *ngSwitchDefault>Inactive</div>
</div>

<!-- Apply CSS class conditionally -->
<div [ngClass]="{'active': isActive, 'disabled': isDisabled}"></div>

Standalone Components

What are Standalone Components?

Standalone components are self-contained units that don't require NgModule declarations. They can directly specify their dependencies, making the code more modular and tree-shakeable.

Creating a Standalone Component

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-hello',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="greeting">
      <h1>Hello {{ name }}!</h1>
      <p>This is a standalone component</p>
    </div>
  `,
  styles: [`
    .greeting { padding: 20px; }
  `]
})
export class HelloComponent {
  name = 'World';
}

Advantages of Standalone Components

  • No NgModule required: Simplified architecture and reduced boilerplate
  • Direct imports: Specify dependencies directly in the component
  • Better tree-shaking: Only bundle what's used
  • Easier to test: Self-contained units with all dependencies visible
  • Improved composability: Easier to compose and reuse components

Lazy Loading Standalone Components

import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: 'dashboard',
    loadComponent: () => import('./components/dashboard.component')
      .then(m => m.DashboardComponent)
  },
  {
    path: 'admin',
    loadComponent: () => import('./components/admin.component')
      .then(m => m.AdminComponent),
    canActivate: [AuthGuard]
  }
];

Signals

What are Signals?

Signals are a new primitive in Angular for tracking state changes with fine-grained reactivity. Unlike observables, signals are simpler to use and provide better performance through fine-grained change detection.

Creating Signals

import { Component, signal, computed, effect } from '@angular/core';

@Component({
  selector: 'app-counter',
  template: `
    <div>
      <p>Count: {{ count() }}</p>
      <p>Doubled: {{ doubledCount() }}</p>
      <button (click)="increment()">Increment</button>
    </div>
  `
})
export class CounterComponent {
  // Create a signal
  count = signal(0);

  // Create a computed signal
  doubledCount = computed(() => this.count() * 2);

  // Create an effect
  constructor() {
    effect(() => {
      console.log('Count changed to:', this.count());
    });
  }

  increment() {
    this.count.update(value => value + 1);
  }
}

Signal Methods

// Reading a signal value
const value = mySignal();

// Setting a new value
mySignal.set(newValue);

// Updating based on previous value
mySignal.update(currentValue => currentValue + 1);

// Creating a readonly signal
import { signal } from '@angular/core';
const readOnlySignal = signal(42).asReadonly();

// Using computed for derived state
const isPositive = computed(() => mySignal() > 0);

Signal-based State Management Example

import { Injectable, signal, computed } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class TodoService {
  // State
  todos = signal([
    { id: 1, title: 'Learn Signals', completed: false }
  ]);

  // Derived state
  completedCount = computed(() => 
    this.todos().filter(t => t.completed).length
  );

  // Actions
  addTodo(title: string) {
    const newTodo = {
      id: Date.now(),
      title,
      completed: false
    };
    this.todos.update(todos => [...todos, newTodo]);
  }

  toggleTodo(id: number) {
    this.todos.update(todos =>
      todos.map(t => t.id === id ? {...t, completed: !t.completed} : t)
    );
  }
}

Signals vs RxJS Observables

Feature Signals Observables
Learning Curve Simple, synchronous Steep, requires RxJS knowledge
State Access Direct: signal() Via subscribe()
Change Detection Fine-grained, automatic Zone-based, requires AsyncPipe
Async Operations Works with Promises Native support

New Control Flow Syntax

@if Control Flow

Replaces *ngIf with cleaner, more readable syntax:

<!-- NEW: @if syntax -->
@if (isLoggedIn) {
  <div>Welcome {{ username }}!</div>
} @else if (isPending) {
  <div>Loading...</div>
} @else {
  <div>Please log in</div>
}

<!-- OLD: *ngIf (still works) -->
<div *ngIf="isLoggedIn">Welcome {{ username }}!</div>
<div *ngIf="!isLoggedIn">Please log in</div>

@for Loop

Replaces *ngFor with improved performance and track function:

<!-- NEW: @for syntax -->
<ul>
  @for (item of items; track item.id) {
    <li>
      {{ item.name }}
      @if (item.important) {
        <span class="badge">Important</span>
      }
    </li>
  } @empty {
    <li>No items available</li>
  }
</ul>

<!-- OLD: *ngFor (still works) -->
<ul>
  <li *ngFor="let item of items; trackBy: trackById">
    {{ item.name }}
  </li>
  <li *ngIf="items.length === 0">No items available</li>
</ul>

@switch Statement

Replaces *ngSwitch with cleaner control flow:

<!-- NEW: @switch syntax -->
@switch (status) {
  @case ('loading') {
    <div>Loading...</div>
  }
  @case ('success') {
    <div>Success! Data loaded.</div>
  }
  @case ('error') {
    <div class="alert">An error occurred</div>
  }
  @default {
    <div>Unknown status</div>
  }
}

<!-- OLD: *ngSwitch (still works) -->
<div [ngSwitch]="status">
  <div *ngSwitchCase="'loading'">Loading...</div>
  <div *ngSwitchDefault>Unknown status</div>
</div>

Track Function for Performance

The track function is crucial for @for performance optimization:

// Component class
export class ListComponent {
  items = [
    { id: 1, name: 'Item 1' },
    { id: 2, name: 'Item 2' }
  ];
}

// Template with track by id
@for (item of items; track item.id) {
  <div>{{ item.name }}</div>
}

// Or with a method
@for (item of items; track getItemId(item)) {
  <div>{{ item.name }}</div>
}

// Method definition
getItemId(item: Item): number {
  return item.id;
}

Benefits of New Control Flow

  • Cleaner Syntax: More readable and intuitive
  • Better Performance: Fewer directives to process
  • Compile-time Safety: Type checking happens at compile time
  • Smaller Bundle: Less directive overhead
  • Native @empty: Built-in empty state handling with @empty

Change Detection Strategies

OnPush Change Detection Strategy

The OnPush strategy only runs change detection when inputs change or events occur, making it more performant:

import { Component, Input, ChangeDetectionStrategy } from '@angular/core';

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

Default Change Detection vs OnPush

Change Detection with Signals

Signals automatically use fine-grained change detection without manual configuration:

import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-smart-list',
  template: `
    <div>
      <h2>Count: {{ count() }}</h2>
      <button (click)="increment()">Increment</button>
      
      @for (item of items(); track item) {
        <div>{{ item }}</div>
      }
    </div>
  `
})
export class SmartListComponent {
  count = signal(0);
  items = signal(['a', 'b', 'c']);

  increment() {
    this.count.update(c => c + 1);
    // Change detection runs only for affected signals
  }
}

State Management Patterns

Signal-Based Service Pattern

import { Injectable, signal, computed } from '@angular/core';
import { HttpClient } from '@angular/common/http';

export interface User {
  id: number;
  name: string;
  email: string;
}

@Injectable({ providedIn: 'root' })
export class UserStateService {
  // State
  users = signal<User[]>([]);
  loading = signal(false);
  error = signal<string | null>(null);

  // Derived state
  userCount = computed(() => this.users().length);
  hasUsers = computed(() => this.userCount() > 0);

  constructor(private http: HttpClient) {}

  // Actions
  loadUsers() {
    this.loading.set(true);
    this.http.get<User[]>('/api/users')
      .subscribe({
        next: (data) => {
          this.users.set(data);
          this.error.set(null);
        },
        error: (err) => {
          this.error.set('Failed to load users');
        },
        complete: () => this.loading.set(false)
      });
  }

  addUser(user: User) {
    this.users.update(users => [...users, user]);
  }
}

Using State Service in Components

import {
    Component,
    OnInit,
    inject
} from '@angular/core';
import {
    CommonModule
} from '@angular/common';
import {
    UserStateService
} from './user-state.service';
@Component({
    selector: 'app-user-list',
    standalone: true,
    imports: [CommonModule],
    template: ` @if (userState.loading()) { <div>Loading...</div> } @else if (userState.error()) { <div class="alert">{{ userState.error() }}</div> } @else { <div> <h2>Users ({{ userState.userCount() }})</h2> @for (user of userState.users(); track user.id) { <div>{{ user.name }} - {{ user.email }}</div> } </div> } `
}) export class UserListComponent implements OnInit {
    userState = inject(UserStateService);
    ngOnInit() {
        this.userState.loadUsers();
    }
}

Best Practices for State Management

  • Single Source of Truth: Keep state in one place (service)
  • Immutable Updates: Always create new objects/arrays when updating state
  • Computed Values: Use computed() for derived state
  • Async Operations: Handle loading and error states explicitly
  • Component Isolation: Components only receive what they need via signals
  • Service Reusability: Keep services focused and reusable

Best Practices

20+ Essential Tips for Angular Development

Use Standalone Components

For new projects, embrace standalone components. They're simpler, more maintainable, and follow modern Angular patterns.
 

Interview Questions & Answers

Interview Questions

1. What are the main differences between Angular 20 and previous versions?

Key Differences:

  • Signals: New primitive for fine-grained reactivity and state management

  • New Control Flow: @if, @for, @switch replacing directive-based syntax

  • Standalone by Default: New projects prefer standalone components

  • Improved Performance: Reduced bundle size and faster change detection

  • Better TypeScript Support: Enhanced type safety and IntelliSense

Example:

// OLD: NgModule based
@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule]
})
export class AppModule { }

// NEW: Standalone
bootstrap([AppComponent]);