Fundamentals

3 min read
Rapid overview

Angular Fundamentals

Core Angular concepts for building applications with standalone components.

Table of Contents

Standalone Components and Templates

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

@Component({
  selector: 'app-greeting',
  standalone: true,
  template: `
    <h1>Hello {{ name }}</h1>
    <button (click)="toggleActive()">Toggle</button>
  `
})
export class GreetingComponent {
  name = 'Frontend';
  isActive = true;

  toggleActive() {
    this.isActive = !this.isActive;
  }
}

Standalone components remove the need for NgModule declarations and keep dependencies local.

Data Binding

<input [value]="query" (input)="query = $any($event.target).value" />
<p [class.active]="isActive">Status: {{ isActive ? 'Active' : 'Idle' }}</p>
  • Property binding: [value]="query"
  • Event binding: (input)="onInput($event)"
  • Interpolation: {{ value }}

Directives and Pipes

<ul>
  <li *ngFor="let item of items; trackBy: trackById">
    {{ item.name | titlecase }}
  </li>
</ul>

<div *ngIf="items.length === 0">No items yet</div>

Structural directives (ngIf, ngFor) shape the DOM, pipes transform output.

Dependency Injection and Services

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

@Injectable({ providedIn: 'root' })
export class UserService {
  constructor(private http: HttpClient) {}

  fetchUsers() {
    return this.http.get('/api/users');
  }
}

Inject services into components via constructors to keep logic testable.

Routing Basics

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

export const routes: Routes = [
  {
    path: '',
    loadComponent: () => import('./home.component').then((m) => m.HomeComponent)
  },
  {
    path: 'settings',
    loadComponent: () => import('./settings.component').then((m) => m.SettingsComponent)
  }
];

Use lazy loaded standalone routes for smaller initial bundles.

Forms Basics

import { FormControl, FormGroup, Validators } from '@angular/forms';

form = new FormGroup({
  email: new FormControl('', [Validators.required, Validators.email]),
  role: new FormControl('viewer')
});

Reactive forms provide explicit control over validation and value changes.

RxJS Observables

import { of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';

this.users$ = this.userService.fetchUsers().pipe(
  map((users) => users.slice(0, 5)),
  catchError(() => of([]))
);

Use async pipe in templates to subscribe/unsubscribe automatically.

Lifecycle Hooks

Common lifecycle hooks and when to use them:

  • ngOnChanges: react to @Input() changes.
  • ngOnInit: initialize data after the first change detection.
  • ngAfterViewInit: access ViewChild and DOM elements.
  • ngOnDestroy: cleanup subscriptions, timers, and listeners.

Change Detection Basics

ChangeDetectionStrategy.OnPush checks a component when:

  • An input reference changes.
  • An event originates from the component or children.
  • An observable emits via the async pipe.
  • ChangeDetectorRef triggers detection.
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';

@Component({
  selector: 'app-summary',
  standalone: true,
  template: `{{ total }}`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SummaryComponent {
  total = 0;

  constructor(private cdr: ChangeDetectorRef) {}

  updateTotal(value: number) {
    this.total = value;
    this.cdr.markForCheck();
  }
}

Components vs Directives

Use components when you need a template and UI. Use directives when you need behavior on existing elements.

import { Directive, ElementRef, HostListener } from '@angular/core';

@Directive({
  selector: '[appHighlight]',
  standalone: true
})
export class HighlightDirective {
  constructor(private element: ElementRef) {}

  @HostListener('mouseenter')
  onEnter() {
    this.element.nativeElement.style.background = 'lavender';
  }

  @HostListener('mouseleave')
  onLeave() {
    this.element.nativeElement.style.background = '';
  }
}

Signals

Signals are synchronous state holders that work well for local UI state.

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

const count = signal(0);
const double = computed(() => count() * 2);

count.set(3);
count.update((value) => value + 1);

Signals vs Observables (when to use which)

Use signals for:

  • Local component/UI state (isOpen, selectedId, derived display values).
  • Derived state via computed (memoized recomputation based on dependencies).

Use observables for:

  • Async boundaries and streams (HTTP, WebSockets, user events, timers).
  • Multi-value flows over time and cancellation (switchMap, retries, backpressure patterns).

Best-practice interview answer:

Use Observables for async boundaries and external events; use Signals for in-memory UI state and derived values.

Bridging example (observable → signal):

import { toSignal } from '@angular/core/rxjs-interop';

price = toSignal(this.price$, { initialValue: 0 });