Angular logoAngularINTERMEDIATE

Angular

Angular cheat sheet covering components, services, RxJS, dependency injection, routing, and TypeScript patterns with code examples.

18 min read
angulartypescriptrxjsspaframeworkcomponentsreactive

Sign in to mark items as known and track your progress.

Sign in

Setup & CLI

Angular installation and CLI commands

Installation & Setup

Setting up Angular development environment

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

# Create new project
ng new my-app
# Options: routing, CSS/SCSS/Sass/Less

# Start development server
ng serve
ng serve --open  # Opens browser
ng serve --port 4201

# Build for production
ng build
ng build --configuration production
💡 Use ng new with --strict for stricter TypeScript settings
⚡ ng serve automatically reloads on file changes
📌 Production builds include tree-shaking and minification
🔥 Use ng update to keep Angular versions in sync

Angular CLI Commands

Essential Angular CLI commands for development

typescript
# Generate components
ng generate component user
ng g c user --inline-template --inline-style
ng g c components/user-list

# Generate services
ng generate service user
ng g s services/auth

# Generate modules
ng g module admin --routing
ng g module shared

# Generate other artifacts
ng g directive highlight
ng g pipe currency-format
ng g guard auth
ng g interceptor auth
ng g class models/user --type=model
ng g interface models/user
ng g enum models/status
💡 Use --dry-run flag to preview changes without creating files
⚡ Shortcuts: g (generate), c (component), s (service), m (module)
📌 Use --skip-tests to skip test file generation
🔥 Standalone components don't need module declarations

Components

Angular components and lifecycle

Component Basics

Standalone components are the default in Angular 17+. NgModule is legacy.

typescript
// Standalone component (default in Angular 17+)
// Generate: ng generate component user-card --standalone
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-user-card',
  standalone: true,
  imports: [CommonModule],     // Import dependencies directly
  template: \`
    <div class="card">
      <h2>{{ name }}</h2>
      <p>{{ email }}</p>
    </div>
  \`,
  styles: [\`.card { padding: 1rem; }\`],
})
export class UserCardComponent {
  name = 'Alice';
  email = 'alice@example.com';
}

// Bootstrap a standalone app
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent);
💡 Standalone is the default in Angular 17+ — no AppModule needed
⚡ Import dependencies directly in the component's `imports` array
📌 Bootstrap with bootstrapApplication() + provideX() helpers
🔥 ng generate @angular/core:standalone migrates legacy NgModule apps

Component Communication

Input, Output, and component interaction

typescript
// Parent to child - @Input
@Component({
  selector: 'app-child',
  template: '<p>{{ message }}</p>'
})
export class ChildComponent {
  @Input() message: string;
  @Input() user: User;
}

// Child to parent - @Output
@Component({
  selector: 'app-child',
  template: '<button (click)="send()">Send</button>'
})
export class ChildComponent {
  @Output() messageEvent = new EventEmitter<string>();

  send() {
    this.messageEvent.emit('Hello Parent');
  }
}
💡 Use @Input for data flow down, @Output for events up
⚡ ViewChild is resolved after ngAfterViewInit
📌 Two-way binding uses pattern: [(prop)]="value"
🔥 Content projection allows flexible component composition

ViewChild & ContentChild

Accessing child components and elements

typescript
// ViewChild - Access child in template
@Component({
  selector: 'app-parent',
  template: \`
    <app-child #childRef></app-child>
    <button (click)="callChildMethod()">Call Child</button>
  \`
})
export class ParentComponent {
  @ViewChild('childRef') child: ChildComponent;
  @ViewChild(ChildComponent) childByType: ChildComponent;

  callChildMethod() {
    this.child.doSomething();
  }
}
💡 Use static: true for ViewChild if you need it in ngOnInit
⚡ ViewChildren returns a QueryList that updates automatically
📌 ContentChild queries projected content from ng-content
🔥 Access child components after ngAfterViewInit lifecycle hook

Lifecycle Hooks

Component lifecycle methods

typescript
export class MyComponent implements OnInit, OnDestroy {
  // 1. Constructor
  constructor() { }

  // 2. OnChanges
  ngOnChanges(changes: SimpleChanges) { }

  // 3. OnInit
  ngOnInit() { }

  // 4. DoCheck
  ngDoCheck() { }

  // 5. AfterContentInit
  ngAfterContentInit() { }

  // 6. AfterContentChecked
  ngAfterContentChecked() { }

  // 7. AfterViewInit
  ngAfterViewInit() { }

  // 8. AfterViewChecked
  ngAfterViewChecked() { }

  // 9. OnDestroy
  ngOnDestroy() { }
}
💡 Use ngOnInit for initialization, not constructor
⚡ Always unsubscribe in ngOnDestroy to prevent memory leaks
📌 ngAfterViewInit is safe for DOM manipulation
🔥 Avoid heavy operations in frequently called hooks

Templates & Directives

Template syntax and built-in directives

Template Syntax

Data binding and template expressions

typescript
<!-- Interpolation -->
<h1>{{ title }}</h1>
<p>{{ 'Hello ' + name }}</p>
<p>{{ getFullName() }}</p>

<!-- Property binding -->
<img [src]="imageUrl">
<button [disabled]="isDisabled">Click</button>

<!-- Event binding -->
<button (click)="onClick()">Click</button>
<input (keyup)="onKeyUp($event)">

<!-- Two-way binding -->
<input [(ngModel)]="name">

<!-- Template reference -->
<input #nameInput>
<button (click)="greet(nameInput.value)">Greet</button>
💡 Use safe navigation (?) to avoid null reference errors
⚡ Prefer property binding [prop] over string interpolation
📌 Template reference variables provide direct access to elements
🔥 ng-container groups elements without adding DOM nodes

Control Flow (@if / @for / @switch)

Built-in template control flow — replaces *ngIf/*ngFor/*ngSwitch in Angular 17+.

typescript
<!-- @if / @else if / @else -->
@if (user) {
  <p>Hello, {{ user.name }}</p>
} @else if (loading) {
  <p>Loading...</p>
} @else {
  <p>Sign in to continue</p>
}

<!-- @for (REQUIRES track) -->
@for (item of items; track item.id) {
  <li>{{ item.name }}</li>
} @empty {
  <li>No items</li>
}

<!-- @switch -->
@switch (status) {
  @case ('loading') { <p>Loading...</p> }
  @case ('error')   { <p>Error!</p> }
  @default          { <p>Ready</p> }
}
💡 @if/@for/@switch are the modern way (Angular 17+) — no imports needed
⚠️ @for REQUIRES a track expression — usually track item.id or track $index
⚡ @for $index/$first/$last/$even/$odd are available as local variables
🔥 ng generate @angular/core:control-flow migrates *ngIf/*ngFor/*ngSwitch

Deferrable Views (@defer)

Lazy-load template chunks with @defer blocks (Angular 17+).

typescript
<!-- Basic @defer with @loading / @error / @placeholder -->
@defer {
  <heavy-chart [data]="data" />
} @loading {
  <p>Loading chart...</p>
} @placeholder {
  <p>Chart will appear here</p>
} @error {
  <p>Failed to load</p>
}

<!-- @defer with triggers -->
@defer (on viewport) {
  <comments-section />
}

@defer (on idle) {
  <analytics-widget />
}

@defer (on interaction) {
  <chat-widget />
}
💡 @defer auto-code-splits its block into a separate chunk
⚡ on viewport + prefetch on idle is the most common pattern for below-the-fold widgets
📌 @placeholder/@loading/@error are all optional but recommended for UX
🔥 minimum X prevents flicker when content loads faster than the placeholder

Services & DI

Services and dependency injection

Services

Creating and using Angular services

typescript
// Service with @Injectable
@Injectable({
  providedIn: 'root'
})
export class UserService {
  constructor(private http: HttpClient) {}

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>('/api/users');
  }

  getUser(id: number): Observable<User> {
    return this.http.get<User>(\`/api/users/\${id}\`);
  }
}

// Using in component
constructor(private userService: UserService) {}

ngOnInit() {
  this.userService.getUsers().subscribe(users => {
    this.users = users;
  });
}
💡 Use providedIn: 'root' for singleton services
⚡ Handle errors gracefully with catchError
📌 BehaviorSubject for state management
🔥 Always unsubscribe to prevent memory leaks

Dependency Injection

DI patterns and providers

typescript
// Injection token
const API_URL = new InjectionToken<string>('api.url');

// Provider configuration
providers: [
  UserService,
  { provide: API_URL, useValue: 'https://api.example.com' },
  { provide: Logger, useClass: ConsoleLogger },
  { provide: Storage, useFactory: storageFactory, deps: [Window] }
]

// Inject in component
constructor(
  private userService: UserService,
  @Inject(API_URL) private apiUrl: string,
  @Optional() private logger?: Logger
) {}
💡 Use InjectionToken for non-class dependencies
⚡ providedIn: 'root' enables tree-shaking
📌 Hierarchical DI allows different instances at different levels
🔥 Use factories for complex initialization logic

Routing

Angular router configuration and navigation

Route Configuration

Setting up routes and navigation

typescript
// Basic routes
const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { path: 'users/:id', component: UserComponent },
  { path: '**', component: NotFoundComponent }
];

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

// Router outlet in template
<router-outlet></router-outlet>

// Router links
<a routerLink="/about">About</a>
<a [routerLink]="['/users', userId]">User Profile</a>
💡 Use lazy loading for large feature modules
⚡ PreloadAllModules strategy improves perceived performance
📌 Order matters - wildcard route must be last
🔥 Use routerLinkActive for navigation highlighting

Guards & Resolvers

Route guards and data resolvers

typescript
// CanActivate guard
@Injectable()
export class AuthGuard implements CanActivate {
  constructor(
    private auth: AuthService,
    private router: Router
  ) {}

  canActivate(): boolean {
    if (this.auth.isAuthenticated()) {
      return true;
    }
    this.router.navigate(['/login']);
    return false;
  }
}

// Resolver
@Injectable()
export class UserResolver implements Resolve<User> {
  constructor(private userService: UserService) {}

  resolve(route: ActivatedRouteSnapshot): Observable<User> {
    return this.userService.getUser(route.params['id']);
  }
}
💡 Use guards to control access to routes
⚡ Resolvers ensure data is loaded before component
📌 Functional guards are simpler for basic checks
🔥 CanDeactivate prevents accidental data loss

Forms

Template-driven and reactive forms

Template-Driven Forms

Forms with ngModel and template validation

typescript
<!-- Basic form -->
<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)">
  <input name="name" [(ngModel)]="user.name" required>
  <input name="email" [(ngModel)]="user.email" email>

  <button type="submit" [disabled]="!myForm.valid">
    Submit
  </button>
</form>

// Component
onSubmit(form: NgForm) {
  if (form.valid) {
    console.log(form.value);
  }
}
💡 Template-driven forms are simpler for basic forms
⚡ Use template reference variables for validation
📌 ngModelGroup creates nested form groups
🔥 Import FormsModule to use template-driven forms

Reactive Forms

Forms with FormControl and validators

typescript
// Form setup
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

export class MyComponent {
  myForm: FormGroup;

  constructor(private fb: FormBuilder) {
    this.myForm = this.fb.group({
      name: ['', [Validators.required, Validators.minLength(3)]],
      email: ['', [Validators.required, Validators.email]],
      age: [null, [Validators.min(18)]]
    });
  }

  onSubmit() {
    if (this.myForm.valid) {
      console.log(this.myForm.value);
    }
  }
}
💡 Reactive forms provide more control and testability
⚡ Use FormBuilder for cleaner syntax
📌 Async validators for server-side validation
🔥 FormArray for dynamic form fields

HTTP & Observables

HTTP client and RxJS observables

HTTP Client

Making HTTP requests with HttpClient

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

// Service with HTTP
@Injectable()
export class DataService {
  constructor(private http: HttpClient) {}

  getData(): Observable<Data[]> {
    return this.http.get<Data[]>('/api/data');
  }

  postData(data: Data): Observable<Data> {
    return this.http.post<Data>('/api/data', data);
  }
}
💡 Always handle errors with catchError operator
⚡ Use interceptors for cross-cutting concerns
📌 Set responseType for non-JSON responses
🔥 Use reportProgress for file upload progress

RxJS Observables

Working with observables and operators

typescript
// Creating observables
const obs$ = new Observable(observer => {
  observer.next('Hello');
  observer.next('World');
  observer.complete();
});

// Subscribe
const subscription = obs$.subscribe({
  next: value => console.log(value),
  error: err => console.error(err),
  complete: () => console.log('Complete')
});

// Unsubscribe
subscription.unsubscribe();
💡 Use appropriate flattening operator (switchMap for searches)
⚡ takeUntil pattern prevents memory leaks
📌 BehaviorSubject for state management
🔥 shareReplay for caching HTTP requests

Advanced Features

Pipes, directives, and performance

Pipes & Directives

Custom pipes and directives

typescript
// Custom pipe
@Pipe({ name: 'truncate' })
export class TruncatePipe implements PipeTransform {
  transform(value: string, limit = 100): string {
    return value.length > limit
      ? value.substring(0, limit) + '...'
      : value;
  }
}

// Usage
{{ longText | truncate:50 }}

// Custom directive
@Directive({ selector: '[appHighlight]' })
export class HighlightDirective {
  @Input() appHighlight = 'yellow';

  @HostListener('mouseenter') onMouseEnter() {
    this.el.nativeElement.style.backgroundColor = this.appHighlight;
  }

  constructor(private el: ElementRef) {}
}
💡 Keep pipes pure for better performance
⚡ Use trackBy in ngFor for large lists
📌 Directives add behavior to existing elements
🔥 OnPush change detection optimizes performance

Signals (Angular 16+)

Reactive primitive for state management

Signal Basics

Creating and using signals for reactive state

typescript
// Creating signals
import { signal, computed, effect } from '@angular/core';

@Component({
  selector: 'app-counter',
  template: \`
    <p>Count: {{ count() }}</p>
    <p>Double: {{ doubleCount() }}</p>
    <button (click)="increment()">+</button>
  \`
})
export class CounterComponent {
  // Writable signal
  count = signal(0);

  // Computed signal (read-only)
  doubleCount = computed(() => this.count() * 2);

  increment() {
    this.count.set(this.count() + 1);
    // or: this.count.update(v => v + 1);
  }
}
💡 Signals provide fine-grained reactivity without Zone.js
⚡ Computed signals are memoized and only recalculate when dependencies change
📌 Effects run automatically when their signal dependencies change
🔥 Use signal inputs for better performance than traditional @Input

Signal Outputs, linkedSignal & Resources

Signal-based outputs and async resources (Angular 17.3+, 19+).

typescript
import { output, linkedSignal, resource } from '@angular/core';

// === Signal-based output (17.3+) ===
@Component({ /* ... */ })
export class ToggleComponent {
  // Replaces @Output() EventEmitter
  toggled = output<boolean>();

  handleClick() {
    this.toggled.emit(true);
  }
}

// === linkedSignal (19+) ===
// A writable derived signal — auto-syncs with source but can be overridden
@Component({ /* ... */ })
export class ShippingForm {
  country = signal('US');
  // Resets when country changes, but user can override
  state = linkedSignal(() => this.defaultStateFor(this.country()));
}

// === resource() (19+) — async signal ===
@Component({ /* ... */ })
export class UserDetail {
  userId = signal(1);

  user = resource({
    request: () => ({ id: this.userId() }),
    loader: async ({ request }) => {
      const res = await fetch(\`/api/users/\${request.id}\`);
      return res.json();
    },
  });
}

// In template:
// @if (user.isLoading()) { <p>Loading...</p> }
// @if (user.error()) { <p>Error: {{ user.error() }}</p> }
// @if (user.value(); as u) { <p>{{ u.name }}</p> }
💡 output() replaces @Output() — type-safe, no EventEmitter import
⚡ linkedSignal is writable but auto-resets when its source changes
📌 resource() turns async data into a signal with isLoading/error/value/reload
🔥 httpResource() (19.2+) is the declarative way to bind HTTP to a signal

Animations

Built-in Angular animation API

Animation Basics

Creating animations with Angular animations API

typescript
// Basic animation
import { trigger, state, style, transition, animate } from '@angular/animations';

@Component({
  selector: 'app-animated',
  template: \`
    <div [@slideIn]="isVisible" class="box">
      Animated Content
    </div>
    <button (click)="toggle()">Toggle</button>
  \`,
  animations: [
    trigger('slideIn', [
      transition(':enter', [
        style({ transform: 'translateX(-100%)' }),
        animate('300ms ease-in', style({ transform: 'translateX(0%)' }))
      ]),
      transition(':leave', [
        animate('300ms ease-out', style({ transform: 'translateX(100%)' }))
      ])
    ])
  ]
})
export class AnimatedComponent {
  isVisible = true;
  toggle() { this.isVisible = !this.isVisible; }
}
💡 Use :enter and :leave for elements being added/removed from DOM
⚡ Stagger animations for lists create smooth sequential effects
📌 Always import BrowserAnimationsModule in your app module
🔥 Use NoopAnimationsModule for testing to disable animations

Testing

Built-in testing with Karma, Jasmine, and TestBed

Component Testing

Testing Angular components with TestBed

typescript
// Basic component test
import { ComponentFixture, TestBed } from '@angular/core/testing';

describe('UserComponent', () => {
  let component: UserComponent;
  let fixture: ComponentFixture<UserComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ UserComponent ]
    }).compileComponents();

    fixture = TestBed.createComponent(UserComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should render title', () => {
    const compiled = fixture.nativeElement;
    expect(compiled.querySelector('h1').textContent).toContain('User Profile');
  });
});
💡 Use TestBed.configureTestingModule to set up test environment
⚡ Mock external dependencies for isolated unit tests
📌 Use fixture.detectChanges() to trigger change detection
🔥 HttpTestingController helps test HTTP requests without actual calls

Performance

Built-in performance optimization features

Performance Optimization

OnPush strategy, trackBy, and lazy loading

typescript
// OnPush Change Detection
@Component({
  selector: 'app-optimized',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: \`
    <div *ngFor="let item of items; trackBy: trackByFn">
      {{ item.name }}
    </div>
  \`
})
export class OptimizedComponent {
  @Input() items: Item[];

  trackByFn(index: number, item: Item): number {
    return item.id; // Return unique identifier
  }
}

// Lazy Loading Routes
const routes: Routes = [
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
  }
];
💡 OnPush strategy skips change detection if inputs haven't changed
⚡ Always use trackBy with ngFor for large lists
📌 Lazy load modules to reduce initial bundle size
🔥 Use virtual scrolling for large lists (CDK required)

Security

Built-in security features and sanitization

Security & Sanitization

XSS protection and content sanitization

typescript
// Angular automatically sanitizes values
@Component({
  template: \`
    <!-- Automatically sanitized -->
    <div [innerHTML]="userContent"></div>

    <!-- Text interpolation is always safe -->
    <p>{{ userInput }}</p>
  \`
})
export class SafeComponent {
  userContent = '<script>alert("XSS")</script>'; // Script tags removed
  userInput = '<img src=x onerror="alert(1)">'; // Displayed as text
}
💡 Angular automatically sanitizes innerHTML to prevent XSS
⚡ Use DomSanitizer.bypassSecurityTrust* only for trusted content
📌 Always validate and sanitize user input on both client and server
🔥 Implement CSP headers to prevent unauthorized script execution

Accessibility

Built-in accessibility features and ARIA support

Accessibility (A11y)

ARIA attributes and CDK A11y utilities

typescript
// Basic ARIA attributes
@Component({
  template: \`
    <button [attr.aria-label]="buttonLabel"
            [attr.aria-pressed]="isPressed"
            [attr.aria-disabled]="isDisabled">
      {{ buttonText }}
    </button>

    <div role="alert" aria-live="polite">
      {{ statusMessage }}
    </div>
  \`
})
export class AccessibleComponent {
  buttonLabel = 'Submit form';
  isPressed = false;
  isDisabled = false;
  statusMessage = '';
}
💡 Always use semantic HTML elements when possible
⚡ CDK A11y provides utilities for focus management and live announcements
📌 Use ARIA attributes to provide context for screen readers
🔥 Test with keyboard navigation and screen readers

i18n

Built-in internationalization support

Internationalization (i18n)

Built-in i18n for multi-language support

typescript
// Mark text for translation
@Component({
  template: \`
    <p i18n="@@welcome.message">Welcome to our app!</p>
    <p i18n="User greeting|Greeting message for user@@user.greeting">
      Hello, {{ userName }}!
    </p>
    <button i18n="@@button.submit">Submit</button>
  \`
})

// Extract messages for translation
ng extract-i18n
ng extract-i18n --output-path src/locale
💡 Use i18n attribute to mark text for translation
⚡ Angular CLI extracts and manages translation files automatically
📌 Build separate bundles for each locale for best performance
🔥 Use ICU message format for complex pluralization and selection