Angular is a complete, opinionated platform for building single-page applications — not a library you assemble piecemeal, but a full framework with a prescribed architecture. Understanding how Angular structures an application — the roles of modules, components, templates, services, and the CLI — before writing a single line of code is the difference between building an Angular application and fighting Angular to build an application. This lesson gives you the mental model and practical foundation that every subsequent chapter builds on.
Core Angular Concepts
| Concept | What It Is | Analogy |
|---|---|---|
| Component | A TypeScript class + HTML template + CSS styles that controls a piece of the UI | A reusable UI widget — a task card, a nav bar, a modal |
| Template | HTML with Angular-specific syntax — bindings, directives, pipes | A smart HTML file that knows how to display component data |
| Service | A class that provides shared logic — HTTP calls, state, utilities — injected into components | A business logic layer decoupled from the UI |
| Directive | A class that modifies the behaviour or appearance of a DOM element | *ngIf, *ngFor, or custom attribute directives |
| Pipe | A pure function that transforms display values in templates | date | 'mediumDate', currency | 'USD' |
| Module (NgModule) | A container that groups components, directives, and pipes (legacy pre-v14 approach) | A feature package — AppModule, TasksModule |
| Standalone Component | A component that declares its own imports — no NgModule needed (Angular 14+) | Self-contained feature unit — the modern Angular approach |
| Router | Maps URL paths to components — handles navigation without full page reloads | URL-to-component mapping table |
| Dependency Injection | Angular’s mechanism for providing services to components — configured via providers | Automatic wiring of dependencies — no manual new Service() |
Angular CLI Commands Reference
| Command | What It Does |
|---|---|
ng new app-name --routing --style=scss --standalone |
Scaffold a new Angular project |
ng serve |
Start the development server on port 4200 with hot reload |
ng build --configuration production |
Build optimised production bundle in dist/ |
ng generate component feature/name |
Generate component files (also: ng g c) |
ng generate service core/services/name |
Generate service file (also: ng g s) |
ng generate interface shared/models/name |
Generate TypeScript interface |
ng generate guard core/guards/name |
Generate route guard |
ng generate pipe shared/pipes/name |
Generate pipe |
ng test |
Run unit tests with Karma/Jest |
ng lint |
Run ESLint on the project |
ng add @angular/material |
Add Angular Material component library |
ng version |
Display Angular CLI and framework versions |
standalone: true property in the @Component decorator marks a component as self-contained. New MEAN Stack projects should use standalone components from the start — the code is simpler, lazy loading is easier, and it aligns with Angular’s direction. Legacy NgModule-based code will continue to work but standalone is the modern path.angular.json and routes when appropriate. A single ng generate component features/tasks/task-list creates task-list.component.ts, task-list.component.html, task-list.component.scss, and task-list.component.spec.ts with all the boilerplate correctly wired.new MyService() inside a component. Angular’s dependency injection system manages service instances — singleton services are created once and shared. Manually instantiating a service creates a separate instance that does not share state with the rest of the application, bypasses the injection hierarchy, and makes testing impossible. Always receive services through the constructor or inject() function.Project Structure
task-manager-frontend/
src/
app/
app.config.ts ← Bootstrap providers: router, http, etc.
app.routes.ts ← Top-level route definitions
app.component.ts ← Root component — contains <router-outlet>
app.component.html
app.component.scss
core/ ← Singleton services, interceptors, guards
services/
auth.service.ts
api.service.ts
interceptors/
auth.interceptor.ts
error.interceptor.ts
guards/
auth.guard.ts
features/ ← Feature components — one folder per page/feature
auth/
login/
login.component.ts
login.component.html
login.component.scss
register/
register.component.ts
tasks/
task-list/
task-list.component.ts
task-list.component.html
task-form/
task-form.component.ts
task-detail/
task-detail.component.ts
shared/ ← Reusable components, pipes, models
components/
spinner/
button/
modal/
models/
task.model.ts ← TypeScript interfaces
user.model.ts
pipes/
relative-date.pipe.ts
environments/
environment.ts ← Dev: apiUrl = http://localhost:3000
environment.prod.ts ← Prod: apiUrl = https://api.yourapp.com
main.ts ← Bootstrap the Angular app
index.html ← Single HTML file — Angular injects into <app-root>
Creating and Understanding a Component
// Generate: ng generate component features/tasks/task-list
// Creates 4 files — this is task-list.component.ts:
import { Component, OnInit, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { TaskService } from '../../../core/services/task.service';
import { Task } from '../../../shared/models/task.model';
@Component({
// ── Selector: how this component is used in other templates ────────────
// <app-task-list></app-task-list>
selector: 'app-task-list',
// ── Standalone: declares its own imports (no NgModule) ─────────────────
standalone: true,
// ── Imports: other components, directives, pipes this template needs ───
// Only needed for standalone components
imports: [CommonModule, RouterModule],
// ── Template: HTML that this component renders ─────────────────────────
// Either inline or a separate .html file (recommended for larger templates)
templateUrl: './task-list.component.html',
// ── Styles: scoped to this component — do not leak to other components ─
styleUrl: './task-list.component.scss',
})
export class TaskListComponent implements OnInit {
// ── Signals for reactive state (Angular 16+) ──────────────────────────
tasks = signal<Task[]>([]);
loading = signal(true);
error = signal<string | null>(null);
// ── Constructor injection: Angular provides the service ────────────────
constructor(private taskService: TaskService) {}
// ── Lifecycle hook: runs once after component initialises ──────────────
ngOnInit(): void {
this.loadTasks();
}
loadTasks(): void {
this.loading.set(true);
this.taskService.getAll().subscribe({
next: tasks => { this.tasks.set(tasks); this.loading.set(false); },
error: err => { this.error.set(err.message); this.loading.set(false); },
});
}
trackById(_: number, task: Task): string {
return task._id;
}
}
<!-- task-list.component.html -->
<div class="task-list">
<!-- Loading state -->
<div *ngIf="loading()" class="spinner">Loading tasks...</div>
<!-- Error state -->
<div *ngIf="error()" class="error">{{ error() }}</div>
<!-- Task list -->
<ul *ngIf="!loading() && !error()">
<li *ngFor="let task of tasks(); trackBy: trackById">
<a [routerLink]="['/tasks', task._id]">{{ task.title }}</a>
<span [class]="'badge badge--' + task.priority">{{ task.priority }}</span>
</li>
</ul>
<!-- Empty state -->
<p *ngIf="!loading() && tasks().length === 0">No tasks yet.</p>
</div>
Bootstrap: app.config.ts and main.ts
// src/app/app.config.ts — application-wide providers
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient,
withInterceptors } from '@angular/common/http';
import { routes } from './app.routes';
import { authInterceptor } from './core/interceptors/auth.interceptor';
import { errorInterceptor } from './core/interceptors/error.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
// Performance: use zone.js for change detection (or provideExperimentalZonelessChangeDetection)
provideZoneChangeDetection({ eventCoalescing: true }),
// Router with route definitions
provideRouter(routes),
// HttpClient with functional interceptors
provideHttpClient(
withInterceptors([authInterceptor, errorInterceptor])
),
],
};
// src/main.ts — bootstrap the application
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
bootstrapApplication(AppComponent, appConfig)
.catch(err => console.error(err));
// src/app/app.routes.ts — top-level routes
import { Routes } from '@angular/router';
import { authGuard } from './core/guards/auth.guard';
export const routes: Routes = [
{ path: '', redirectTo: '/tasks', pathMatch: 'full' },
// Auth routes — eagerly loaded (small, needed immediately)
{
path: 'auth',
children: [
{
path: 'login',
loadComponent: () => import('./features/auth/login/login.component')
.then(m => m.LoginComponent),
},
{
path: 'register',
loadComponent: () => import('./features/auth/register/register.component')
.then(m => m.RegisterComponent),
},
],
},
// Protected routes — lazy loaded + guarded
{
path: 'tasks',
canActivate: [authGuard],
children: [
{
path: '',
loadComponent: () => import('./features/tasks/task-list/task-list.component')
.then(m => m.TaskListComponent),
},
{
path: ':id',
loadComponent: () => import('./features/tasks/task-detail/task-detail.component')
.then(m => m.TaskDetailComponent),
},
],
},
// 404 catch-all
{
path: '**',
loadComponent: () => import('./features/not-found/not-found.component')
.then(m => m.NotFoundComponent),
},
];
How It Works
Step 1 — Angular Bootstraps at the Root Component
When the browser loads the Angular application, main.ts calls bootstrapApplication(AppComponent, appConfig). Angular reads appConfig.providers to set up the dependency injection container (router, HTTP client, interceptors). It then renders AppComponent into the <app-root> element in index.html. The root component typically just contains a <router-outlet> — a placeholder where the active route’s component is rendered.
Step 2 — Components Control Pieces of the UI
Each component is responsible for one piece of the user interface. The @Component decorator tells Angular the component’s HTML selector, template, and style files. Angular compiles the template at build time, replacing Angular-specific syntax ({{ }}, *ngIf, [property]) with efficient DOM manipulation code. The component class holds the data the template displays and handles user interactions.
Step 3 — Standalone Components Declare Their Own Dependencies
A standalone component’s imports array lists everything its template needs: CommonModule (for *ngIf, *ngFor), RouterModule (for routerLink), FormsModule (for ngModel), or other standalone components used in the template. This is the component’s explicit dependency declaration — Angular uses it to tree-shake unused code from the bundle at build time.
Step 4 — Services Are Provided and Injected
A service decorated with @Injectable({ providedIn: 'root' }) is a singleton — one instance shared across the entire application. Angular’s DI system creates it the first time it is requested and provides the same instance to every component and service that injects it. Services declared with providedIn: 'root' are automatically available everywhere without any additional configuration.
Step 5 — Lazy Loading Loads Feature Code on Demand
Route definitions with loadComponent: () => import('./features/...') create code-split bundles. The tasks feature’s JavaScript is only downloaded when the user navigates to /tasks — not on initial page load. This dramatically reduces the initial bundle size and time-to-interactive, especially important for large applications with many features.
Real-World Example: app.component.ts (Root)
// src/app/app.component.ts — root component
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { NavbarComponent } from './shared/components/navbar/navbar.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, NavbarComponent],
template: `
<app-navbar></app-navbar>
<main class="main-content">
<router-outlet></router-outlet>
</main>
`,
styles: [`
.main-content {
max-width: 1200px;
margin: 0 auto;
padding: 2rem 1rem;
}
`]
})
export class AppComponent {
title = 'Task Manager';
}
Common Mistakes
Mistake 1 — Manually instantiating services with new
❌ Wrong — creates a separate instance outside Angular’s DI system:
export class TaskListComponent {
private taskService = new TaskService(); // bypasses DI entirely!
// No HttpClient is available — TaskService's constructor will fail
}
✅ Correct — inject through the constructor or inject() function:
export class TaskListComponent {
constructor(private taskService: TaskService) {}
// OR modern style:
private taskService = inject(TaskService);
}
Mistake 2 — Forgetting to add components to imports in standalone components
❌ Wrong — child component selector not recognised in template:
@Component({
standalone: true,
imports: [CommonModule], // missing TaskCardComponent!
template: `<app-task-card [task]="task"></app-task-card>`
})
// Error: 'app-task-card' is not a known element
✅ Correct — include every component, directive, and pipe used in the template:
@Component({
standalone: true,
imports: [CommonModule, TaskCardComponent],
template: `<app-task-card [task]="task"></app-task-card>`
})
Mistake 3 — Putting all routes in one eager bundle
❌ Wrong — all feature code downloaded on initial load:
import { TaskListComponent } from './features/tasks/task-list.component';
import { AdminDashboard } from './features/admin/admin.component';
// All imported — all bundled together — slow initial load
const routes = [{ path: 'tasks', component: TaskListComponent }];
✅ Correct — lazy load feature components:
const routes = [{
path: 'tasks',
loadComponent: () => import('./features/tasks/task-list.component')
.then(m => m.TaskListComponent)
}];
Quick Reference
| Task | CLI Command |
|---|---|
| New project | ng new app --routing --style=scss --standalone |
| Generate component | ng g c features/tasks/task-list |
| Generate service | ng g s core/services/task |
| Generate interface | ng g interface shared/models/task |
| Generate guard | ng g guard core/guards/auth |
| Generate pipe | ng g pipe shared/pipes/relative-date |
| Serve dev | ng serve |
| Build prod | ng build --configuration production |
| Run tests | ng test |