Introduction
Angular is a popular TypeScript-based web application framework developed and maintained by Google. It provides a comprehensive solution for building single-page applications with a component-based architecture. This cheat sheet covers the essential concepts and techniques you need to know when starting with Angular.
Setting Up Angular
Installing Angular CLI
The Angular CLI (Command Line Interface) is the official tool for initializing, developing, and maintaining Angular applications.
# Install Angular CLI globally
npm install -g @angular/cli
# Check installation and version
ng version
Creating a New Project
# Create a new Angular project
ng new my-project-name
# Create with specific options
ng new my-project-name --routing --style=scss
Running Your Application
# Navigate to your project directory
cd my-project-name
# Start the development server
ng serve
# Start and automatically open in browser
ng serve --open
# or
ng serve -o
Angular Project Structure
my-project-name/
├── node_modules/ # Third-party libraries
├── src/ # Source files
│ ├── app/ # Application code
│ │ ├── app.component.ts # Root component
│ │ ├── app.component.html # Root component template
│ │ ├── app.component.css # Root component styles
│ │ ├── app.component.spec.ts # Root component tests
│ │ ├── app.module.ts # Root module
│ ├── assets/ # Static assets (images, etc.)
│ ├── environments/ # Environment configuration
│ ├── index.html # Main HTML file
│ ├── main.ts # Entry point
│ ├── styles.css # Global styles
├── angular.json # Angular workspace configuration
├── package.json # NPM dependencies and scripts
├── tsconfig.json # TypeScript configuration
Angular Building Blocks
Components
Components are the main building blocks of Angular applications. They control a section of the UI.
Creating a Component
# Generate a new component using Angular CLI
ng generate component component-name
# or shorter syntax
ng g c component-name
Component Structure
// my-component.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-my-component',
templateUrl: './my-component.component.html',
styleUrls: ['./my-component.component.css']
})
export class MyComponent {
// Properties
title = 'My Component';
items = ['Item 1', 'Item 2', 'Item 3'];
// Methods
handleClick() {
alert('Button clicked!');
}
}
<!-- my-component.component.html -->
<h1>{{ title }}</h1>
<ul>
<li *ngFor="let item of items">{{ item }}</li>
</ul>
<button (click)="handleClick()">Click me</button>
Modules
Modules in Angular help organize the application into cohesive blocks of functionality.
Basic Module Structure
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { MyComponent } from './my-component/my-component.component';
@NgModule({
declarations: [
AppComponent,
MyComponent
],
imports: [
BrowserModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Services
Services provide functionality that’s not directly related to views. They are used for data fetching, logging, and other operations.
Creating a Service
# Generate a new service using Angular CLI
ng generate service service-name
# or shorter syntax
ng g s service-name
Basic Service Structure
// data.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root' // This makes the service a singleton
})
export class DataService {
private data = ['Item 1', 'Item 2', 'Item 3'];
constructor() { }
getData() {
return this.data;
}
addData(item: string) {
this.data.push(item);
}
}
Using a Service in a Component
// my-component.component.ts
import { Component, OnInit } from '@angular/core';
import { DataService } from '../data.service';
@Component({
selector: 'app-my-component',
templateUrl: './my-component.component.html'
})
export class MyComponent implements OnInit {
items: string[] = [];
// Inject the service in the constructor
constructor(private dataService: DataService) { }
ngOnInit(): void {
// Get data from service when component initializes
this.items = this.dataService.getData();
}
addItem(newItem: string) {
this.dataService.addData(newItem);
this.items = this.dataService.getData();
}
}
Data Binding
Angular offers several ways to connect your component’s data to its template:
Interpolation (One-way: Component to View)
<h1>{{ title }}</h1>
<p>Welcome, {{ user.name }}</p>
<p>{{ 'Hello, ' + user.name }}</p>
<p>{{ 2 + 2 }}</p> <!-- Displays: 4 -->
Property Binding (One-way: Component to View)
<img [src]="imageUrl">
<button [disabled]="isDisabled">Click me</button>
Event Binding (One-way: View to Component)
<button (click)="handleClick()">Click me</button>
<input (keyup)="handleKeyUp($event)">
Two-way Binding (using ngModel)
First, import FormsModule in your module:
// app.module.ts
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [
BrowserModule,
FormsModule // Add this
],
// ...
})
Then use it in your template:
<input [(ngModel)]="name">
<p>Hello, {{ name }}</p>
Directives
Directives are classes that add behavior to elements in your Angular applications.
Built-in Structural Directives
These change the DOM layout by adding and removing elements.
ngIf (Conditional Rendering)
<!-- Simple condition -->
<div *ngIf="isVisible">This element will show if isVisible is true</div>
<!-- With else block -->
<div *ngIf="isVisible; else notVisible">Content when visible</div>
<ng-template #notVisible>Content when not visible</ng-template>
<!-- With then and else -->
<div *ngIf="isVisible; then visibleBlock else notVisibleBlock"></div>
<ng-template #visibleBlock>Content when visible</ng-template>
<ng-template #notVisibleBlock>Content when not visible</ng-template>
ngFor (List Rendering)
<!-- Basic usage -->
<ul>
<li *ngFor="let item of items">{{ item }}</li>
</ul>
<!-- With index -->
<ul>
<li *ngFor="let item of items; let i = index">{{ i }}: {{ item }}</li>
</ul>
<!-- With additional variables -->
<ul>
<li *ngFor="let item of items; let i = index; let isFirst = first; let isLast = last">
{{ i }}: {{ item }}
<span *ngIf="isFirst">(First item)</span>
<span *ngIf="isLast">(Last item)</span>
</li>
</ul>
ngSwitch
<div [ngSwitch]="color">
<p *ngSwitchCase="'red'">The color is red</p>
<p *ngSwitchCase="'blue'">The color is blue</p>
<p *ngSwitchCase="'green'">The color is green</p>
<p *ngSwitchDefault>The color is something else</p>
</div>
Attribute Directives
These change the appearance or behavior of an existing element.
ngClass
<!-- Single class with condition -->
<div [ngClass]="{'active': isActive}">This div is active</div>
<!-- Multiple classes -->
<div [ngClass]="{'active': isActive, 'disabled': isDisabled, 'special': isSpecial}">
This div has multiple conditional classes
</div>
<!-- Using component method -->
<div [ngClass]="getClasses()">Classes from method</div>
getClasses() {
return {
'active': this.isActive,
'disabled': this.isDisabled,
'special': this.isSpecial
};
}
ngStyle
<!-- Single style with condition -->
<div [ngStyle]="{'color': textColor}">This text has dynamic color</div>
<!-- Multiple styles -->
<div [ngStyle]="{'color': textColor, 'font-size': fontSize + 'px', 'font-weight': isBold ? 'bold' : 'normal'}">
This div has multiple dynamic styles
</div>
<!-- Using component method -->
<div [ngStyle]="getStyles()">Styles from method</div>
getStyles() {
return {
'color': this.textColor,
'font-size': this.fontSize + 'px',
'font-weight': this.isBold ? 'bold' : 'normal'
};
}
Pipes
Pipes transform values for display in your templates.
Built-in Pipes
<!-- Date pipe -->
<p>{{ today | date }}</p> <!-- Apr 15, 2023 -->
<p>{{ today | date:'shortDate' }}</p> <!-- 4/15/23 -->
<p>{{ today | date:'fullDate' }}</p> <!-- Saturday, April 15, 2023 -->
<p>{{ today | date:'MM/dd/yyyy' }}</p> <!-- 04/15/2023 -->
<!-- Uppercase/Lowercase -->
<p>{{ name | uppercase }}</p> <!-- JOHN DOE -->
<p>{{ name | lowercase }}</p> <!-- john doe -->
<!-- Number formatting -->
<p>{{ price | number }}</p> <!-- 1,234.56 -->
<p>{{ price | number:'1.2-2' }}</p> <!-- 1,234.56 -->
<!-- Currency -->
<p>{{ price | currency }}</p> <!-- $1,234.56 -->
<p>{{ price | currency:'EUR' }}</p> <!-- €1,234.56 -->
<!-- Percent -->
<p>{{ 0.75 | percent }}</p> <!-- 75% -->
<!-- Slice -->
<p>{{ [1,2,3,4,5] | slice:1:3 }}</p> <!-- [2,3] -->
<!-- JSON -->
<pre>{{ user | json }}</pre> <!-- Pretty-printed JSON -->
<!-- Chaining pipes -->
<p>{{ today | date:'fullDate' | uppercase }}</p>
Creating a Custom Pipe
# Generate a new pipe using Angular CLI
ng generate pipe pipe-name
# or shorter syntax
ng g p pipe-name
// truncate.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'truncate'
})
export class TruncatePipe implements PipeTransform {
transform(value: string, limit: number = 25, trail: string = '...'): string {
if (!value) {
return '';
}
if (value.length <= limit) {
return value;
}
return value.substring(0, limit) + trail;
}
}
In your template:
<p>{{ longText | truncate:10:'...' }}</p>
Component Communication
Parent to Child: @Input
Parent component:
// parent.component.ts
@Component({
selector: 'app-parent',
template: `
<h1>Parent Component</h1>
<app-child [data]="parentData" [message]="'Hello from parent'"></app-child>
`
})
export class ParentComponent {
parentData = [1, 2, 3, 4, 5];
}
Child component:
// child.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<h2>Child Component</h2>
<p>Message: {{ message }}</p>
<ul>
<li *ngFor="let item of data">{{ item }}</li>
</ul>
`
})
export class ChildComponent {
@Input() data: number[] = [];
@Input() message: string = '';
}
Child to Parent: @Output
Child component:
// child.component.ts
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<h2>Child Component</h2>
<button (click)="sendMessage()">Send Message to Parent</button>
<button (click)="sendData()">Send Data to Parent</button>
`
})
export class ChildComponent {
@Output() messageEvent = new EventEmitter<string>();
@Output() dataEvent = new EventEmitter<number>();
sendMessage() {
this.messageEvent.emit('Hello from child component');
}
sendData() {
this.dataEvent.emit(42);
}
}
Parent component:
// parent.component.ts
@Component({
selector: 'app-parent',
template: `
<h1>Parent Component</h1>
<p>Message from child: {{ childMessage }}</p>
<p>Data from child: {{ childData }}</p>
<app-child
(messageEvent)="receiveMessage($event)"
(dataEvent)="receiveData($event)">
</app-child>
`
})
export class ParentComponent {
childMessage = '';
childData = 0;
receiveMessage(message: string) {
this.childMessage = message;
}
receiveData(data: number) {
this.childData = data;
}
}
Forms
Angular provides two approaches to forms: Template-driven forms (simpler) and Reactive forms (more robust).
Template-driven Forms
First, import FormsModule:
// app.module.ts
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [
BrowserModule,
FormsModule
],
// ...
})
export class AppModule { }
Then create your form:
<!-- template-form.component.html -->
<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)">
<div>
<label for="name">Name</label>
<input
type="text"
id="name"
name="name"
[(ngModel)]="model.name"
required
#name="ngModel">
<div *ngIf="name.invalid && (name.dirty || name.touched)">
Name is required.
</div>
</div>
<div>
<label for="email">Email</label>
<input
type="email"
id="email"
name="email"
[(ngModel)]="model.email"
required
email
#email="ngModel">
<div *ngIf="email.invalid && (email.dirty || email.touched)">
<div *ngIf="email.errors?.['required']">Email is required.</div>
<div *ngIf="email.errors?.['email']">Email is invalid.</div>
</div>
</div>
<button type="submit" [disabled]="myForm.invalid">Submit</button>
</form>
<div *ngIf="submitted">
<h2>Submitted Values</h2>
<p>Name: {{ model.name }}</p>
<p>Email: {{ model.email }}</p>
</div>
// template-form.component.ts
import { Component } from '@angular/core';
import { NgForm } from '@angular/forms';
@Component({
selector: 'app-template-form',
templateUrl: './template-form.component.html'
})
export class TemplateFormComponent {
model = {
name: '',
email: ''
};
submitted = false;
onSubmit(form: NgForm) {
if (form.valid) {
console.log('Form submitted', this.model);
this.submitted = true;
}
}
}
Reactive Forms
First, import ReactiveFormsModule:
// app.module.ts
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule
],
// ...
})
export class AppModule { }
Then create your form:
// reactive-form.component.ts
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
@Component({
selector: 'app-reactive-form',
templateUrl: './reactive-form.component.html'
})
export class ReactiveFormComponent implements OnInit {
userForm: FormGroup;
submitted = false;
constructor(private fb: FormBuilder) { }
ngOnInit() {
this.userForm = this.fb.group({
name: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]],
address: this.fb.group({
street: [''],
city: ['', Validators.required],
zip: ['', [Validators.required, Validators.pattern(/^\d{5}$/)]]
})
});
}
onSubmit() {
if (this.userForm.valid) {
console.log('Form submitted', this.userForm.value);
this.submitted = true;
} else {
// Mark all fields as touched to trigger validation
this.markFormGroupTouched(this.userForm);
}
}
// Helper method to mark all controls as touched
markFormGroupTouched(formGroup: FormGroup) {
Object.values(formGroup.controls).forEach(control => {
control.markAsTouched();
if (control instanceof FormGroup) {
this.markFormGroupTouched(control);
}
});
}
}
<!-- reactive-form.component.html -->
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<div>
<label for="name">Name</label>
<input type="text" id="name" formControlName="name">
<div *ngIf="userForm.get('name')?.invalid && userForm.get('name')?.touched">
<div *ngIf="userForm.get('name')?.errors?.['required']">Name is required.</div>
<div *ngIf="userForm.get('name')?.errors?.['minlength']">
Name must be at least 3 characters.
</div>
</div>
</div>
<div>
<label for="email">Email</label>
<input type="email" id="email" formControlName="email">
<div *ngIf="userForm.get('email')?.invalid && userForm.get('email')?.touched">
<div *ngIf="userForm.get('email')?.errors?.['required']">Email is required.</div>
<div *ngIf="userForm.get('email')?.errors?.['email']">Email is invalid.</div>
</div>
</div>
<div formGroupName="address">
<h3>Address</h3>
<div>
<label for="street">Street</label>
<input type="text" id="street" formControlName="street">
</div>
<div>
<label for="city">City</label>
<input type="text" id="city" formControlName="city">
<div *ngIf="userForm.get('address.city')?.invalid && userForm.get('address.city')?.touched">
City is required.
</div>
</div>
<div>
<label for="zip">ZIP Code</label>
<input type="text" id="zip" formControlName="zip">
<div *ngIf="userForm.get('address.zip')?.invalid && userForm.get('address.zip')?.touched">
<div *ngIf="userForm.get('address.zip')?.errors?.['required']">ZIP code is required.</div>
<div *ngIf="userForm.get('address.zip')?.errors?.['pattern']">
ZIP code must be 5 digits.
</div>
</div>
</div>
</div>
<button type="submit">Submit</button>
</form>
<div *ngIf="submitted">
<h2>Submitted Values</h2>
<pre>{{ userForm.value | json }}</pre>
</div>
Basic Routing
First, create a routing module (usually done when creating a project with --routing
flag):
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { ContactComponent } from './contact/contact.component';
import { NotFoundComponent } from './not-found/not-found.component';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: 'contact', component: ContactComponent },
{ path: '**', component: NotFoundComponent } // Wildcard route for 404
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Import the routing module:
// app.module.ts
import { AppRoutingModule } from './app-routing.module';
@NgModule({
imports: [
BrowserModule,
AppRoutingModule
],
// ...
})
export class AppModule { }
Add the router outlet in your app component:
<!-- app.component.html -->
<header>
<nav>
<ul>
<li><a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Home</a></li>
<li><a routerLink="/about" routerLinkActive="active">About</a></li>
<li><a routerLink="/contact" routerLinkActive="active">Contact</a></li>
</ul>
</nav>
</header>
<main>
<!-- This is where route components will be displayed -->
<router-outlet></router-outlet>
</main>
<footer>
<p>© 2023 My Angular App</p>
</footer>
HTTP Client
First, import the HttpClientModule:
// app.module.ts
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [
BrowserModule,
HttpClientModule
],
// ...
})
export class AppModule { }
Create a service to make HTTP requests:
// data.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
interface Post {
id: number;
title: string;
body: string;
userId: number;
}
@Injectable({
providedIn: 'root'
})
export class DataService {
private apiUrl = 'https://jsonplaceholder.typicode.com';
constructor(private http: HttpClient) { }
getPosts(): Observable<Post[]> {
return this.http.get<Post[]>(`${this.apiUrl}/posts`);
}
getPost(id: number): Observable<Post> {
return this.http.get<Post>(`${this.apiUrl}/posts/${id}`);
}
createPost(post: Omit<Post, 'id'>): Observable<Post> {
return this.http.post<Post>(`${this.apiUrl}/posts`, post);
}
updatePost(id: number, post: Partial<Post>): Observable<Post> {
return this.http.put<Post>(`${this.apiUrl}/posts/${id}`, post);
}
deletePost(id: number): Observable<unknown> {
return this.http.delete(`${this.apiUrl}/posts/${id}`);
}
}
Use the service in a component:
// posts.component.ts
import { Component, OnInit } from '@angular/core';
import { DataService } from '../data.service';
interface Post {
id: number;
title: string;
body: string;
userId: number;
}
@Component({
selector: 'app-posts',
templateUrl: './posts.component.html'
})
export class PostsComponent implements OnInit {
posts: Post[] = [];
loading = false;
error = '';
constructor(private dataService: DataService) { }
ngOnInit(): void {
this.loading = true;
this.dataService.getPosts().subscribe({
next: (data) => {
this.posts = data;
this.loading = false;
},
error: (err) => {
this.error = 'Failed to load posts';
this.loading = false;
console.error(err);
}
});
}
createPost() {
const newPost = {
title: 'New Post',
body: 'This is a new post created via HTTP',
userId: 1
};
this.dataService.createPost(newPost).subscribe({
next: (post) => {
this.posts.unshift(post);
},
error: (err) => {
console.error('Error creating post', err);
}
});
}
deletePost(id: number) {
this.dataService.deletePost(id).subscribe({
next: () => {
this.posts = this.posts.filter(post => post.id !== id);
},
error: (err) => {
console.error('Error deleting post', err);
}
});
}
}
<!-- posts.component.html -->
<h1>Posts</h1>
<div *ngIf="loading">Loading posts...</div>
<div *ngIf="error">{{ error }}</div>
<button (click)="createPost()">Create New Post</button>
<div *ngIf="!loading && !error">
<div *ngFor="let post of posts" class="post">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
<button (click)="deletePost(post.id)">Delete</button>
</div>
</div>
Common Angular CLI Commands
# Generate components, services, etc.
ng generate component my-component
ng generate service my-service
ng generate pipe my-pipe
ng generate directive my-directive
ng generate module my-module
ng generate interface my-interface
ng generate enum my-enum
ng generate class my-class
ng generate guard my-guard
# Build the application
ng build # Development build
ng build --configuration production # Production build
# Run unit tests
ng test
# Run end-to-end tests
ng e2e
# Lint your code
ng lint
# Update Angular and dependencies
ng update
# Add a dependency
ng add @angular/material
Best Practices for Beginners
- Organize by Feature: Group related components, services, and other files into feature modules
- Keep Components Small: Each component should have a single responsibility
- Use OnPush Change Detection: For better performance, especially in larger applications
- Unsubscribe from Observables: Prevent memory leaks by unsubscribing from observables in ngOnDestroy
- Use Async Pipe: Let Angular handle subscribing and unsubscribing to observables in templates
- Lazy Load Modules: For larger applications, improve initial load time
- Proper Error Handling: Always handle errors in HTTP requests and other asynchronous operations
- Follow Angular Style Guide: For consistent code structure and naming conventions
Resources for Further Learning
Official Documentation
- Angular.io – The official Angular documentation
- Angular CLI – Documentation for the Angular CLI
- Angular Material – UI component library for Angular
Online Courses and Tutorials
- Angular University (angular-university.io)
- Pluralsight Angular courses
- Udemy – Angular: The Complete Guide
Books
- “Angular: Up and Running” by Shyam Seshadri
- “Angular in Action” by Jeremy Wilken
- “ng-book: The Complete Book on Angular” by Nathan Murray et al.
Tools
- Angular DevTools (Chrome Extension)
- Augury (Chrome Extension for debugging)
- VS Code with Angular Language Service extension