Introduction to Angular
Angular is a platform and framework for building single-page client applications using HTML and TypeScript. Developed and maintained by Google, Angular is a complete rewrite of AngularJS (Angular 1.x), with a focus on modern web development practices, improved performance, and better scalability.
Real-world analogy: If web development were construction, Angular would be a comprehensive construction system with standardized, prefabricated components, specialized tools, and detailed blueprints. It provides everything you need to build a complete structure from foundation to roof, with strict guidelines on how components should fit together.
2010: AngularJS (1.x)
Original framework developed by Miško Hevery at Google
2016: Angular 2
Complete rewrite, introducing TypeScript, component-based architecture, and modern tooling
2017-Present: Angular 4+ (Now Angular 17+)
Semantic versioning adopted with more frequent, smaller updates and improved performance
Angular vs Other Frameworks
To understand Angular's approach, let's compare it with other popular JavaScript frameworks:
Angular
- Complete framework with built-in solutions for routing, forms, HTTP, testing, etc.
- TypeScript-first approach with strong typing
- Opinionated with strict architectural guidelines
- RxJS integration for reactive programming
- Enterprise-ready with focus on large-scale applications
Vue
- Progressive framework that can be adopted incrementally
- Optional TypeScript support
- Less opinionated with flexible architecture
- Simple and intuitive with gentle learning curve
- Lightweight core with optional plugins
React
- Library rather than a complete framework
- JSX for templating
- Unidirectional data flow with focus on immutability
- Ecosystem of third-party libraries
- Virtual DOM focus
When to choose Angular: Angular is particularly well-suited for large enterprise applications, teams that prefer strongly typed languages, and projects that benefit from a comprehensive, opinionated framework with built-in solutions for common challenges.
Core Angular Concepts
Angular's architecture is built on a few key concepts:
Modules (NgModules)
NgModules are containers for a cohesive block of code dedicated to an application domain, a workflow, or a closely related set of capabilities. They help organize the application into cohesive functionality blocks.
@NgModule({
declarations: [
AppComponent,
HeroesComponent,
HeroDetailComponent
],
imports: [
BrowserModule,
FormsModule,
HttpClientModule
],
providers: [HeroService],
bootstrap: [AppComponent]
})
export class AppModule { }
Real-world analogy: NgModules are like departments in a company. Each department has its own specialized team members (components, directives), tools (services), relationships with other departments (imports/exports), and specific responsibilities.
Components
Components are the basic building blocks of Angular applications. A component controls a patch of screen called a view, consisting of a template (HTML), styles (CSS), and a class (TypeScript) that handles behavior.
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: ['./hero-detail.component.css']
})
export class HeroDetailComponent {
@Input() hero: Hero;
constructor(private heroService: HeroService) { }
saveHero(): void {
this.heroService.updateHero(this.hero)
.subscribe(() => this.goBack());
}
}
Real-world analogy: Components are like specialized machine operators in a factory. Each operator manages their own station (view), follows a specific blueprint (template), has a unique appearance (styles), and knows exactly what to do and how to do it (class).
Templates
Templates define the component's view with HTML enhanced by Angular directives and binding syntax.
<div class="hero-detail">
<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div>
<label>name:
<input [(ngModel)]="hero.name" placeholder="name"/>
</label>
</div>
<button (click)="saveHero()">Save</button>
<button (click)="goBack()">Back</button>
</div>
Services
Services are classes with a focused purpose. They're used for data fetching, logging, or any functionality that isn't tied to a specific view.
@Injectable({
providedIn: 'root'
})
export class HeroService {
private heroesUrl = 'api/heroes';
constructor(
private http: HttpClient,
private messageService: MessageService
) { }
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
tap(_ => this.log('fetched heroes')),
catchError(this.handleError<Hero[]>('getHeroes', []))
);
}
}
Real-world analogy: Services are like utility companies or specialized contractors in a city. They provide specific functionalities (water, electricity, maintenance) that multiple buildings (components) can use without having to implement these functionalities themselves.
Dependency Injection
Dependency Injection (DI) is a design pattern in which a class requests dependencies from external sources rather than creating them. Angular's DI system provides declared dependencies to a class when it's instantiated.
@Component({
selector: 'app-hero-list',
templateUrl: './hero-list.component.html'
})
export class HeroListComponent implements OnInit {
heroes: Hero[] = [];
// HeroService is injected into this component
constructor(private heroService: HeroService) { }
ngOnInit(): void {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
}
Real-world analogy: Dependency Injection is like a hospitality service at a conference. Instead of each speaker (component) bringing their own projector, microphone, and water (services), the conference organizer (Angular DI) provides these resources as needed.
Angular Architecture Diagram
The following diagram illustrates how different parts of an Angular application interact:
- Users interact with the View (UI)
- The View is defined by the Template, which is bound to the Component
- Components contain the application logic and can use Services for cross-cutting concerns
- Services can communicate with external sources like Backend APIs
- Changes in the Component are reflected in the View through binding
Data Binding
Angular's data binding mechanism synchronizes the application state with the view. There are four forms of data binding:
Interpolation
<h1>{{title}}</h1>
<p>{{hero.name}} details!</p>
Displays component property values in the template.
Property Binding
<img [src]="heroImageUrl">
<button [disabled]="isDisabled">Save</button>
Sets an element property to a component property value.
Event Binding
<button (click)="save()">Save</button>
<input (input)="handleInput($event)">
Responds to user events by calling component methods.
Two-way Binding
<input [(ngModel)]="hero.name">
Combines property and event binding to create a two-way flow of data.
Real-world analogy: Data binding is like a sophisticated automation system in a smart home. Interpolation and property binding are like displays that show the current state (e.g., thermostat showing temperature). Event binding is like motion sensors that trigger actions when detected. Two-way binding is like a smart thermostat that both displays the current temperature and allows you to change it.
Directives
Directives are classes that add behavior to elements in Angular applications. There are three kinds of directives:
Components
Components are directives with a template. They are the most common type of directive.
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
Structural Directives
Structural directives alter the DOM layout by adding and removing elements.
<!-- ngFor iterates over heroes array -->
<li *ngFor="let hero of heroes">
{{hero.name}}
</li>
<!-- ngIf conditionally includes an element -->
<div *ngIf="selectedHero">
<h2>{{selectedHero.name | uppercase}} Details</h2>
</div>
<!-- ngSwitch conditionally displays one of multiple elements -->
<div [ngSwitch]="hero.type">
<app-warrior-hero *ngSwitchCase="'warrior'" [hero]="hero"></app-warrior-hero>
<app-mage-hero *ngSwitchCase="'mage'" [hero]="hero"></app-mage-hero>
<app-default-hero *ngSwitchDefault [hero]="hero"></app-default-hero>
</div>
Attribute Directives
Attribute directives alter the appearance or behavior of an existing element.
<!-- ngStyle dynamically sets CSS properties -->
<div [ngStyle]="{'color': hero.type === 'warrior' ? 'red' : 'blue'}">
{{hero.name}}
</div>
<!-- ngClass dynamically adds/removes CSS classes -->
<div [ngClass]="{'selected': hero === selectedHero,
'warrior': hero.type === 'warrior'}">
{{hero.name}}
</div>
Custom Directives
You can create your own directives to encapsulate reusable behavior.
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
@Input('appHighlight') highlightColor: string;
constructor(private el: ElementRef) { }
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.highlightColor || 'yellow');
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight(null);
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
// Usage in template
<p appHighlight="lightblue">Highlight me!</p>
Real-world analogy: If components are like specialized machine operators, directives are like add-on tools and accessories that enhance their capabilities. Structural directives are like jigs that determine what parts go where. Attribute directives are like settings on a machine that modify how it operates.
Angular Routing
Angular Router enables navigation between views based on user actions and provides features like route parameters, guards, and lazy loading.
Basic Routing Configuration
// app-routing.module.ts
const routes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'heroes', component: HeroesComponent },
{ path: 'detail/:id', component: HeroDetailComponent },
{ path: '**', component: PageNotFoundComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
// app.component.html
<nav>
<a routerLink="/dashboard">Dashboard</a>
<a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
Route Parameters
// In component
export class HeroDetailComponent implements OnInit {
hero: Hero;
constructor(
private route: ActivatedRoute,
private heroService: HeroService
) {}
ngOnInit(): void {
const id = this.route.snapshot.paramMap.get('id');
this.heroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
}
Route Guards
Route guards control access to routes based on conditions like authentication or unsaved changes.
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean {
const url: string = state.url;
return this.checkLogin(url);
}
checkLogin(url: string): boolean {
if (this.authService.isLoggedIn) { return true; }
// Store attempted URL for redirecting after login
this.authService.redirectUrl = url;
// Navigate to login page
this.router.navigate(['/login']);
return false;
}
}
// In routes configuration
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard]
}
Lazy Loading
Lazy loading improves application startup time by loading feature modules only when needed.
const routes: Routes = [
{
path: 'customers',
loadChildren: () => import('./customers/customers.module')
.then(m => m.CustomersModule)
},
{
path: 'orders',
loadChildren: () => import('./orders/orders.module')
.then(m => m.OrdersModule)
}
];
Real-world analogy: Angular Routing is like a GPS navigation system. It directs users to different "locations" (components) based on the URL. Route parameters are like specific addresses. Guards are like security checkpoints that may prevent access to certain areas. Lazy loading is like only downloading map data for an area when you actually decide to go there.
Angular CLI and Tools
Angular provides a powerful Command Line Interface (CLI) that makes it easy to create, develop, test, and maintain Angular applications.
Key CLI Commands
# Create a new application
ng new my-app
# Generate a new component
ng generate component hero-detail
# Build the application
ng build
# Run the application locally
ng serve
# Run unit tests
ng test
# Run end-to-end tests
ng e2e
Project Structure
A typical Angular project has the following structure:
my-app/
├── src/
│ ├── app/
│ │ ├── app.component.ts
│ │ ├── app.component.html
│ │ ├── app.component.css
│ │ ├── app.component.spec.ts
│ │ ├── app.module.ts
│ │ └── ...
│ ├── assets/
│ │ └── ...
│ ├── environments/
│ │ ├── environment.ts
│ │ └── environment.prod.ts
│ ├── index.html
│ ├── main.ts
│ ├── polyfills.ts
│ ├── styles.css
│ └── test.ts
├── node_modules/
├── angular.json
├── package.json
├── tsconfig.json
└── ...
Angular Ecosystem
Angular comes with a rich ecosystem of supporting libraries and tools:
- Angular Material: UI component library implementing Material Design
- Angular CLI: Command-line tools for Angular development
- Angular Universal: Server-side rendering (SSR) for Angular
- Angular PWA: Tools for creating Progressive Web Applications
- NgRx: State management library implementing Redux pattern
- RxJS: Reactive programming library for asynchronous operations
- Angular Forms: Template-driven and reactive forms libraries
- Angular Testing: Testing utilities for unit and integration tests
- Angular DevTools: Browser extensions for debugging Angular applications
Angular Versions and Upgrading
Angular follows semantic versioning and has a predictable release schedule:
- Major releases every 6 months
- At least 12-18 months of active support for each major version
- 6 months of additional long-term support (LTS) for final minor releases of major versions
Angular provides tools and detailed guides for upgrading between versions, including the Angular Update Guide, which provides step-by-step instructions for upgrading between specific versions.
Practical Example: Angular Application Structure
Let's look at a practical example of an Angular application for a simple task management system.
Module Structure
// app.module.ts
@NgModule({
declarations: [
AppComponent,
TaskListComponent,
TaskDetailComponent,
TaskFormComponent,
FilterPipe
],
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
AppRoutingModule
],
providers: [
TaskService,
AuthService,
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
],
bootstrap: [AppComponent]
})
export class AppModule { }
Component Structure
// task-list.component.ts
@Component({
selector: 'app-task-list',
templateUrl: './task-list.component.html',
styleUrls: ['./task-list.component.css']
})
export class TaskListComponent implements OnInit {
tasks: Task[] = [];
filteredTasks: Task[] = [];
filterStatus: string = 'all';
constructor(private taskService: TaskService) { }
ngOnInit(): void {
this.getTasks();
}
getTasks(): void {
this.taskService.getTasks()
.subscribe(tasks => {
this.tasks = tasks;
this.applyFilter();
});
}
applyFilter(): void {
if (this.filterStatus === 'all') {
this.filteredTasks = this.tasks;
} else {
const status = this.filterStatus === 'completed';
this.filteredTasks = this.tasks.filter(task => task.completed === status);
}
}
deleteTask(task: Task): void {
this.tasks = this.tasks.filter(t => t.id !== task.id);
this.taskService.deleteTask(task.id).subscribe();
this.applyFilter();
}
}
Template Structure
<!-- task-list.component.html -->
<div class="task-list-container">
<h2>My Tasks</h2>
<div class="filter-controls">
<label>Show:
<select [(ngModel)]="filterStatus" (change)="applyFilter()">
<option value="all">All</option>
<option value="active">Active</option>
<option value="completed">Completed</option>
</select>
</label>
<button routerLink="/tasks/new" class="btn-add">Add Task</button>
</div>
<div *ngIf="filteredTasks.length === 0" class="empty-state">
No tasks found.
</div>
<ul class="task-list">
<li *ngFor="let task of filteredTasks"
[class.completed]="task.completed">
<div class="task-item">
<label class="checkbox-container">
<input type="checkbox"
[checked]="task.completed"
(change)="toggleComplete(task)">
<span class="checkmark"></span>
</label>
<span class="task-title">{{ task.title }}</span>
<div class="task-actions">
<button [routerLink]="['/tasks', task.id]" class="btn-edit">
Edit
</button>
<button (click)="deleteTask(task)" class="btn-delete">
Delete
</button>
</div>
</div>
</li>
</ul>
</div>
Service Structure
// task.service.ts
@Injectable({
providedIn: 'root'
})
export class TaskService {
private tasksUrl = 'api/tasks';
constructor(
private http: HttpClient,
private messageService: MessageService
) { }
getTasks(): Observable<Task[]> {
return this.http.get<Task[]>(this.tasksUrl)
.pipe(
tap(_ => this.log('fetched tasks')),
catchError(this.handleError<Task[]>('getTasks', []))
);
}
getTask(id: number): Observable<Task> {
const url = `${this.tasksUrl}/${id}`;
return this.http.get<Task>(url)
.pipe(
tap(_ => this.log(`fetched task id=${id}`)),
catchError(this.handleError<Task>(`getTask id=${id}`))
);
}
addTask(task: Task): Observable<Task> {
return this.http.post<Task>(this.tasksUrl, task)
.pipe(
tap((newTask: Task) => this.log(`added task id=${newTask.id}`)),
catchError(this.handleError<Task>('addTask'))
);
}
updateTask(task: Task): Observable<any> {
return this.http.put(this.tasksUrl, task)
.pipe(
tap(_ => this.log(`updated task id=${task.id}`)),
catchError(this.handleError<any>('updateTask'))
);
}
deleteTask(id: number): Observable<Task> {
const url = `${this.tasksUrl}/${id}`;
return this.http.delete<Task>(url)
.pipe(
tap(_ => this.log(`deleted task id=${id}`)),
catchError(this.handleError<Task>('deleteTask'))
);
}
private log(message: string) {
this.messageService.add(`TaskService: ${message}`);
}
private handleError<T>(operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
console.error(error);
this.log(`${operation} failed: ${error.message}`);
return of(result as T);
};
}
}
Routing Structure
// app-routing.module.ts
const routes: Routes = [
{ path: '', redirectTo: '/tasks', pathMatch: 'full' },
{ path: 'tasks', component: TaskListComponent },
{ path: 'tasks/new', component: TaskFormComponent },
{ path: 'tasks/:id', component: TaskDetailComponent },
{ path: '**', component: PageNotFoundComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Activities for Practice
Exercise 1: Angular Concepts Map
Create a concept map or diagram showing the relationships between different Angular concepts (modules, components, services, directives, etc.). For each concept, provide a short definition and a real-world analogy that helps explain its purpose.
Exercise 2: Framework Comparison
Create a detailed comparison table of Angular, Vue, and React. Consider factors like:
- Learning curve
- Project structure
- State management
- Performance
- Community and ecosystem
- Use cases and strengths
For each framework, identify at least one type of project where it would be the best choice, and explain why.
Exercise 3: Angular App Planning
Design the structure of an Angular application for a simple e-commerce store. Your design should include:
- Module structure (feature modules, shared modules)
- Component hierarchy
- Services needed
- Routing architecture
- Data models
Create a visual diagram showing how these parts interact, and explain why you organized them this way.