Ultimate Angular Framework Cheat Sheet: Comprehensive Guide for Developers

Introduction to Angular

Angular is a powerful, TypeScript-based front-end framework developed and maintained by Google. It enables developers to build dynamic, single-page web applications (SPAs) through a component-based architecture and powerful features like dependency injection, reactive programming, and comprehensive tooling. Angular’s opinionated structure helps teams maintain consistent code organization across large projects.

Core Angular Concepts

Key Building Blocks

ComponentDescription
ComponentsBasic building blocks that control views and include an HTML template, TypeScript class, and CSS styles
ModulesContainers that organize related components, directives, pipes, and services
ServicesReusable classes for data handling, business logic, and external interactions
DirectivesDOM manipulation tools that extend HTML with custom behaviors
PipesSimple functions to transform display values within templates
TemplatesHTML with Angular-specific syntax for binding data and handling events
Dependency InjectionDesign pattern used to increase efficiency and modularity

Component Lifecycle Hooks

  • ngOnChanges(): Responds when Angular sets/resets data-bound input properties
  • ngOnInit(): Initializes component after first ngOnChanges
  • ngDoCheck(): Custom change detection
  • ngAfterContentInit(): After content projection
  • ngAfterContentChecked(): After checking projected content
  • ngAfterViewInit(): After initializing component’s views
  • ngAfterViewChecked(): After checking component’s views
  • ngOnDestroy(): Before instance destruction

Getting Started with Angular

Setting Up a New Project

# Install Angular CLI
npm install -g @angular/cli

# Create new project
ng new my-app-name

# Run development server
cd my-app-name
ng serve

Project Structure

my-app/
├── node_modules/
├── src/
│   ├── app/
│   │   ├── app.component.ts|html|css|spec.ts
│   │   └── app.module.ts
│   ├── assets/
│   ├── environments/
│   ├── favicon.ico
│   ├── index.html
│   ├── main.ts
│   ├── polyfills.ts
│   ├── styles.css
│   └── test.ts
├── angular.json
├── package.json
├── tsconfig.json
└── other config files...

Common CLI Commands

# Generate components
ng generate component component-name
ng g c component-name

# Generate services, modules, etc.
ng g service service-name
ng g module module-name
ng g directive directive-name
ng g pipe pipe-name
ng g class class-name
ng g interface interface-name
ng g enum enum-name
ng g guard guard-name

# Build for production
ng build --prod

# Run tests
ng test
ng e2e

Data Binding and Templates

Types of Data Binding

TypeSyntaxDescription
Interpolation{{ expression }}One-way binding to display component data in templates
Property Binding[property]="expression"One-way binding from component to HTML element property
Event Binding(event)="handler()"One-way binding from HTML element to component
Two-way Binding[(ngModel)]="property"Two-way binding between component and template

Common Directives

Structural Directives

  • *ngIf="condition" – Conditionally creates/removes elements
  • *ngFor="let item of items" – Creates elements for each item in a collection
  • *ngSwitch – Switches between alternative views
<div *ngIf="isVisible">Shown if true</div>

<ul>
  <li *ngFor="let item of items; let i = index">
    {{i}} - {{item.name}}
  </li>
</ul>

<div [ngSwitch]="condition">
  <div *ngSwitchCase="'case1'">Case 1</div>
  <div *ngSwitchCase="'case2'">Case 2</div>
  <div *ngSwitchDefault>Default case</div>
</div>

Attribute Directives

  • [ngClass] – Adds/removes CSS classes
  • [ngStyle] – Sets inline styles
  • [ngModel] – Two-way data binding (requires FormsModule)
<div [ngClass]="{'active': isActive, 'disabled': isDisabled}">Content</div>
<div [ngStyle]="{'color': textColor, 'font-size': fontSize + 'px'}">Styled text</div>
<input [(ngModel)]="name" placeholder="Name">

Services and Dependency Injection

Creating a Service

// user.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'  // Service available application-wide
})
export class UserService {
  private users = [...];
  
  getUsers() {
    return this.users;
  }
}

Injecting Services

// user-list.component.ts
import { Component } from '@angular/core';
import { UserService } from '../user.service';

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html'
})
export class UserListComponent {
  users = [];
  
  constructor(private userService: UserService) {
    this.users = this.userService.getUsers();
  }
}

Routing in Angular

Basic Setup

// 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';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { path: 'contact', component: ContactComponent },
  { path: '**', redirectTo: '' }  // Wildcard route for 404
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Router Navigation

<!-- Template navigation -->
<a routerLink="/about" routerLinkActive="active">About</a>

<!-- Dynamic navigation -->
<a [routerLink]="['/user', userId]">User Details</a>
// Programmatic navigation
import { Router } from '@angular/router';

constructor(private router: Router) {}

navigateToHome() {
  this.router.navigate(['/home']);
}

Route Parameters

// Route definition
{ path: 'user/:id', component: UserDetailComponent }

// Accessing parameters
import { ActivatedRoute } from '@angular/router';

constructor(private route: ActivatedRoute) {
  // Snapshot approach
  const id = this.route.snapshot.paramMap.get('id');
  
  // Observable approach (for parameter changes within same component)
  this.route.paramMap.subscribe(params => {
    const id = params.get('id');
  });
}

Forms in Angular

Template-Driven Forms

// Import in module
import { FormsModule } from '@angular/forms';

@NgModule({
  imports: [FormsModule]
})
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm.value)">
  <div>
    <label for="name">Name</label>
    <input type="text" id="name" name="name" [(ngModel)]="user.name" required #name="ngModel">
    <div *ngIf="name.invalid && (name.dirty || name.touched)">
      Name is required
    </div>
  </div>
  
  <button type="submit" [disabled]="userForm.invalid">Submit</button>
</form>

Reactive Forms

// Import in module
import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
  imports: [ReactiveFormsModule]
})
// Component
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

export class UserFormComponent {
  userForm: FormGroup;
  
  constructor(private fb: FormBuilder) {
    this.userForm = this.fb.group({
      name: ['', [Validators.required, Validators.minLength(2)]],
      email: ['', [Validators.required, Validators.email]],
      address: this.fb.group({
        street: [''],
        city: [''],
        zip: ['']
      })
    });
  }
  
  onSubmit() {
    console.log(this.userForm.value);
  }
}
<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 2 characters</div>
    </div>
  </div>
  
  <div formGroupName="address">
    <h3>Address</h3>
    <input type="text" formControlName="street" placeholder="Street">
    <input type="text" formControlName="city" placeholder="City">
    <input type="text" formControlName="zip" placeholder="ZIP">
  </div>
  
  <button type="submit" [disabled]="userForm.invalid">Submit</button>
</form>

HTTP and API Interaction

HttpClient Setup

// app.module.ts
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [HttpClientModule]
})

Making HTTP Requests

// data.service.ts
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private apiUrl = 'https://api.example.com/data';
  
  constructor(private http: HttpClient) {}
  
  getData(): Observable<any[]> {
    return this.http.get<any[]>(this.apiUrl)
      .pipe(
        tap(data => console.log('Data fetched', data)),
        catchError(this.handleError)
      );
  }
  
  getItemById(id: number): Observable<any> {
    return this.http.get<any>(`${this.apiUrl}/${id}`);
  }
  
  addItem(item: any): Observable<any> {
    return this.http.post<any>(this.apiUrl, item, {
      headers: new HttpHeaders({'Content-Type': 'application/json'})
    });
  }
  
  updateItem(item: any): Observable<any> {
    return this.http.put<any>(`${this.apiUrl}/${item.id}`, item);
  }
  
  deleteItem(id: number): Observable<any> {
    return this.http.delete<any>(`${this.apiUrl}/${id}`);
  }
  
  searchItems(term: string): Observable<any[]> {
    const params = new HttpParams().set('q', term);
    return this.http.get<any[]>(this.apiUrl, { params });
  }
  
  private handleError(error: any) {
    console.error('API error', error);
    return [];
  }
}

Using Services in Components

import { Component, OnInit } from '@angular/core';
import { DataService } from '../data.service';

@Component({
  selector: 'app-data-list',
  templateUrl: './data-list.component.html'
})
export class DataListComponent implements OnInit {
  items = [];
  loading = false;
  error = null;
  
  constructor(private dataService: DataService) {}
  
  ngOnInit() {
    this.getItems();
  }
  
  getItems() {
    this.loading = true;
    this.dataService.getData()
      .subscribe({
        next: (data) => {
          this.items = data;
          this.loading = false;
        },
        error: (err) => {
          this.error = 'Failed to load data';
          this.loading = false;
        }
      });
  }
}

State Management with RxJS

Basic State Service

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class StateService {
  private itemsSubject = new BehaviorSubject<any[]>([]);
  items$: Observable<any[]> = this.itemsSubject.asObservable();
  
  private loadingSubject = new BehaviorSubject<boolean>(false);
  loading$: Observable<boolean> = this.loadingSubject.asObservable();
  
  updateItems(items: any[]) {
    this.itemsSubject.next(items);
  }
  
  setLoading(isLoading: boolean) {
    this.loadingSubject.next(isLoading);
  }
}

Using in Components

import { Component, OnInit } from '@angular/core';
import { StateService } from '../state.service';
import { DataService } from '../data.service';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-items',
  template: `
    <div *ngIf="loading$ | async">Loading...</div>
    <ul>
      <li *ngFor="let item of items$ | async">{{item.name}}</li>
    </ul>
  `
})
export class ItemsComponent implements OnInit {
  items$: Observable<any[]>;
  loading$: Observable<boolean>;
  
  constructor(
    private stateService: StateService,
    private dataService: DataService
  ) {
    this.items$ = this.stateService.items$;
    this.loading$ = this.stateService.loading$;
  }
  
  ngOnInit() {
    this.fetchData();
  }
  
  fetchData() {
    this.stateService.setLoading(true);
    this.dataService.getData().subscribe({
      next: (data) => {
        this.stateService.updateItems(data);
        this.stateService.setLoading(false);
      },
      error: () => this.stateService.setLoading(false)
    });
  }
}

Testing Angular Applications

Component Testing

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserComponent } from './user.component';
import { UserService } from '../user.service';
import { of } from 'rxjs';

describe('UserComponent', () => {
  let component: UserComponent;
  let fixture: ComponentFixture<UserComponent>;
  let userServiceSpy: jasmine.SpyObj<UserService>;
  
  beforeEach(async () => {
    const spy = jasmine.createSpyObj('UserService', ['getUsers']);
    
    await TestBed.configureTestingModule({
      declarations: [UserComponent],
      providers: [{ provide: UserService, useValue: spy }]
    }).compileComponents();
    
    userServiceSpy = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;
  });
  
  beforeEach(() => {
    fixture = TestBed.createComponent(UserComponent);
    component = fixture.componentInstance;
    userServiceSpy.getUsers.and.returnValue(of([{id: 1, name: 'Test User'}]));
    fixture.detectChanges();
  });
  
  it('should create', () => {
    expect(component).toBeTruthy();
  });
  
  it('should load users on init', () => {
    expect(userServiceSpy.getUsers).toHaveBeenCalled();
    expect(component.users.length).toBe(1);
    expect(component.users[0].name).toBe('Test User');
  });
});

Service Testing

import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { DataService } from './data.service';

describe('DataService', () => {
  let service: DataService;
  let httpMock: HttpTestingController;
  
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [DataService]
    });
    
    service = TestBed.inject(DataService);
    httpMock = TestBed.inject(HttpTestingController);
  });
  
  afterEach(() => {
    httpMock.verify(); // Verify no outstanding requests
  });
  
  it('should retrieve data from API', () => {
    const mockData = [{id: 1, name: 'Item 1'}, {id: 2, name: 'Item 2'}];
    
    service.getData().subscribe(data => {
      expect(data).toEqual(mockData);
    });
    
    const req = httpMock.expectOne('https://api.example.com/data');
    expect(req.request.method).toBe('GET');
    req.flush(mockData);
  });
});

Optimization Techniques

Performance Best Practices

TechniqueDescription
OnPush Change DetectionUse changeDetection: ChangeDetectionStrategy.OnPush to improve performance
Lazy LoadingLoad feature modules on demand with route configuration
Pure PipesUse pure pipes for transformations where possible
trackBy in ngForAdd trackBy function to optimize list rendering
Async PipeUse async pipe to automatically manage observable subscriptions
Web WorkersOffload CPU-intensive tasks to web workers
Server-Side RenderingUse Angular Universal for SSR
AOT CompilationAlways use ahead-of-time compilation for production

Code Splitting and Lazy Loading

// app-routing.module.ts
const routes: Routes = [
  { path: '', component: HomeComponent },
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
  },
  {
    path: 'products',
    loadChildren: () => import('./products/products.module').then(m => m.ProductsModule)
  }
];

Common Challenges and Solutions

ChallengeSolution
Circular DependenciesRefactor code to use interface or service for shared functionality
Memory LeaksUnsubscribe from observables in ngOnDestroy lifecycle hook
ExpressionChangedAfterItHasBeenCheckedErrorEnsure changes happen before change detection or use setTimeout/ngZone
Large Bundle SizeImplement lazy loading, code splitting, and tree shaking
Slow RenderingUse OnPush change detection, trackBy, and pure pipes
Complex FormsBreak into smaller components with focused responsibility
AuthenticationUse route guards and HTTP interceptors
State Management ComplexityConsider NgRx or simpler state management solutions

Angular Best Practices

  1. Structure and Organization

    • Follow consistent naming conventions
    • Keep components small and focused
    • Organize by feature modules
    • Separate business logic into services
  2. Performance

    • Use OnPush change detection
    • Implement trackBy for ngFor
    • Minimize DOM manipulation
    • Lazy load feature modules
  3. Security

    • Use Angular’s built-in sanitization
    • Implement proper authentication/authorization
    • Protect against XSS and CSRF
  4. Data Management

    • Use RxJS effectively with proper operators
    • Manage subscriptions to avoid memory leaks
    • Consider state management patterns for complex apps
  5. Testing

    • Write unit tests for services and components
    • Use TestBed for component testing
    • Mock dependencies with jasmine spies

Resources for Further Learning

Scroll to Top