Angular Fundamentals: Components, Templates & Data Binding
Learn how Angular applications are built from components and templates, how data flows between them with binding, how directives shape the DOM, and how services and dependency injection keep logic reusable — plus the modern shift from NgModules to standalone components.
4 sections · ~28 min · 5-question quiz (pass ≥ 70%)
1Components and Templates: The Building Blocks
Every Angular UI is a tree of components. A component is a TypeScript class paired with an HTML template and optional styles. Angular renders the template, wires it to the class, and keeps them in sync as data changes.
import { Component } from '@angular/core';
@Component({
selector: 'app-user-card',
standalone: true,
template: `
<article class="card">
<h2>{{ user.name }}</h2>
<p>{{ user.role }}</p>
</article>
`,
styles: [`.card { padding: 1rem; border-radius: 8px; }`],
})
export class UserCardComponent {
user = { name: 'Ada Lovelace', role: 'Engineer' };
}
The selector (app-user-card) is how you embed the component in other templates: <app-user-card />. The class holds state (properties) and behavior (methods). The template declares what to show; the class declares what data and actions exist.
Standalone components (shown above) are the modern default in Angular 17+. They declare their own imports instead of relying on a shared NgModule. Legacy codebases still use @NgModule to bundle declarations, but new projects should prefer standalone — simpler dependency graphs and better tree-shaking.
2Data Binding: Connecting Class and Template
Angular provides four binding syntaxes that cover every direction data can flow:
| Syntax | Direction | Example |
|---|---|---|
{{ expr }} |
Class → Template (display) | {{ title }} |
[prop]="expr" |
Class → Template (property) | [disabled]="isSaving" |
(event)="handler" |
Template → Class (event) | (click)="save()" |
[(ngModel)]="field" |
Two-way | [(ngModel)]="email" |
Interpolation ({{ }}) evaluates an expression and inserts the result as text. Property binding sets DOM or component properties from the class — essential for passing data into child components:
<app-user-card [user]="selectedUser" />
Event binding listens for DOM or custom events and calls a method:
<button (click)="increment()">Add</button>
Two-way binding combines property + event binding. With reactive forms you more often bind [formControl] one-way and react to valueChanges; [(ngModel)] remains handy for simple template-driven inputs when FormsModule is imported.
Understanding which arrow points which way prevents the most common beginner bug: trying to mutate a child from the parent via two-way binding when a simple [input] + (output) event pattern is clearer.
3Directives: Shaping the DOM
Directives are classes that add behavior to elements. Angular ships three categories: components (directives with a template), structural directives (change the DOM layout), and attribute directives (change appearance or behavior).
Structural directives conditionally add or remove elements:
<!-- Classic syntax (still widely used) -->
<p *ngIf="isLoggedIn">Welcome back!</p>
<li *ngFor="let item of items; trackBy: trackById">{{ item.name }}</li>
<!-- Modern control flow (Angular 17+) — no import needed -->
@if (isLoggedIn) {
<p>Welcome back!</p>
}
@for (item of items; track item.id) {
<li>{{ item.name }}</li>
}
The @if / @for block syntax is built into the compiler, reads more like plain JavaScript, and supports @else and @empty branches natively. Legacy *ngIf and *ngFor require CommonModule (or individual directive imports in standalone components).
Attribute directives modify an element in place. [ngClass] toggles CSS classes; [ngStyle] sets inline styles:
<div [ngClass]="{ active: isSelected, error: hasError }">...</div>
Always use trackBy (or track in @for) when rendering lists — it tells Angular how to identify items so the DOM is reused instead of destroyed and recreated on every change detection cycle.
4Services, Dependency Injection & Modules vs Standalone
Services are plain TypeScript classes marked with @Injectable(). They hold shared logic: HTTP calls, authentication, state, logging. Components stay thin; services do the heavy lifting.
@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);
getUsers() {
return this.http.get<User[]>('/api/users');
}
}
@Component({ /* ... */ })
export class UserListComponent {
private userService = inject(UserService);
users = signal<User[]>([]);
ngOnInit() {
this.userService.getUsers().subscribe(data => this.users.set(data));
}
}
Dependency Injection (DI) means Angular creates and injects service instances for you. providedIn: 'root' registers a singleton for the entire app. You can also scope providers to a component (providers: [UserService]) for a fresh instance per component tree.
NgModules vs standalone: Historically, AppModule declared components and imported BrowserModule, FormsModule, etc. Standalone components replace that pattern — each component lists its own imports: [CommonModule, ReactiveFormsModule]. The CLI scaffolds standalone by default. When reading older code, expect declarations and imports arrays inside @NgModule; when writing new code, compose standalone components directly.