Fundamentals
3 min readAngular Fundamentals
Core Angular concepts for building applications with standalone components.
Table of Contents
- Standalone Components and Templates
- Data Binding
- Directives and Pipes
- Dependency Injection and Services
- Routing Basics
- Forms Basics
- RxJS Observables
- Lifecycle Hooks
- Change Detection Basics
- Components vs Directives
- Signals
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: accessViewChildand 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
asyncpipe. ChangeDetectorReftriggers 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 });