Application Framework

Angular

Angular is the batteries-included framework: TypeScript first, dependency injection built in, and an opinionated structure that scales from a small app to a large enterprise codebase.

intermediate16 min readUpdated Sep 15, 2026
counter.component.ts
ts
// counter.component.ts
import { Component, signal } from "@angular/core";

@Component({
  selector: "app-counter",
  standalone: true,
  template: `
    <button (click)="increment()">
      Clicked {{ count() }} times
    </button>
  `,
})
export class CounterComponent {
  count = signal(0);

  increment() {
    this.count.update((c) => c + 1);
  }
}
Maintained by
Google
Language
TypeScript first
Current major
Angular 17+
Reactivity
Signals and RxJS
DI
Built into the framework
Scope
Full application platform

Why it matters

Why enterprises choose Angular

Batteries included

Routing, forms, HTTP, testing and tooling ship together, so teams do not assemble a stack from a dozen unrelated packages.

Opinionated structure

Conventions and dependency injection keep large codebases consistent, which matters most when many developers share a repo.

Built for scale

Strong typing, a powerful CLI and a testing setup from day one make Angular a common choice for long-lived enterprise apps.

The big picture

The three ideas behind Angular

A component tree, dependency injection and a reactive layer. Angular ships all three as one coherent, opinionated system.

Components

UI

Classes decorated with @Component that pair a template with logic and styles.

Dependency injection

Wiring

A first-class container that provides and injects services throughout the application.

Reactivity

Change

Signals for local state and RxJS observables for streams, driving change detection.

Angular at a glance

The core of Angular

Components

A TypeScript class, an HTML template and styles, wired together by a decorator.

Templates & binding

Interpolation, property binding and event binding connect the class to the view.

Signals

Fine-grained reactive values that update the view without a full check.

RxJS

Observables and operators for handling asynchronous streams of events.

Services & DI

Share logic and data through injectable services instead of importing globals.

Router

Configure routes, guards and lazy-loaded feature areas declaratively.

A short history

From AngularJS to a modern platform

  1. 2010

    AngularJS

    The original framework popularises two-way binding and single-page applications.

    10
  2. 2016

    Angular 2 rewrite

    A ground-up rewrite around components, TypeScript and a new dependency injection system.

    16
  3. 2020

    Ivy and strict mode

    A new compiler improves bundle size and enables stricter type checking.

    20
  4. 2023

    Signals and standalone

    Signals arrive and standalone components become the recommended default.

    23
  5. Today

    A modern platform

    Deferred loading, zoneless change detection and a unified CLI keep Angular competitive.

    Today

The complete guide

Angular: Everything you need to know

What is Angular?

Angular is a complete application framework maintained by Google. Where React is a library you assemble a stack around, Angular ships the stack: components, routing, forms, HTTP, dependency injection, testing and a powerful CLI, all designed to work together. It is TypeScript-first and opinionated, which is exactly why large teams pick it.

That completeness is the core trade-off. You accept a steeper learning curve and more framework conventions in exchange for consistency, strong tooling and fewer architectural decisions to make from scratch. For a small prototype that can feel heavy; for a five-year enterprise codebase it is often a relief.

Components and templates

A component is a TypeScript class decorated with @Component, paired with an HTML template and optional styles.

// user-card.component.ts
import { Component, Input } from "@angular/core";

@Component({
  selector: "app-user-card",
  standalone: true,
  template: `
    <article class="card">
      <h3>{{ user.name }}</h3>
      <p>{{ user.email }}</p>
    </article>
  `,
})
export class UserCardComponent {
  @Input({ required: true }) user!: { name: string; email: string };
}

The template uses {{ }} for interpolation and directives for logic. Modern Angular uses built-in control flow blocks instead of the older structural directives.

<!-- list.html -->
@if (users.length > 0) {
  <ul>
    @for (user of users; track user.id) {
      <li>{{ user.name }}</li>
    }
  </ul>
} @else {
  <p>No users yet.</p>
}

The track expression in @for plays the same role as a key in React or Vue: it tells Angular how to match items between updates.

Data binding

Angular templates support four kinds of binding.

  • Interpolation {{ value }} renders a value as text.
  • Property binding [src]="url" sets an element property from an expression.
  • Event binding (click)="save()" calls a method when an event fires.
  • Two-way binding [(ngModel)]="name" combines both for form inputs.
<!-- bindings.html -->
<img [src]="user.avatar" [alt]="user.name" />
<button (click)="save()">Save</button>
<input [(ngModel)]="query" />

Two-way binding is convenient for simple forms, but for anything with validation, reactive forms are usually a better fit.

Signals

Signals are Angular’s modern reactivity primitive. A signal holds a value and notifies interested consumers when it changes, which lets the framework update only the parts of the view that depend on it.

// cart.component.ts
import { Component, signal, computed } from "@angular/core";

@Component({
  selector: "app-cart",
  standalone: true,
  template: `<p>Total: {{ total() }}</p>`,
})
export class CartComponent {
  items = signal([{ price: 10 }, { price: 20 }]);

  total = computed(() =>
    this.items().reduce((sum, i) => sum + i.price, 0),
  );

  add(price: number) {
    this.items.update((items) => [...items, { price }]);
  }
}

computed derives a value from other signals and caches it until a dependency changes. effect runs a side effect when signals it reads change. Together they cover most of what components need.

RxJS and observables

Angular has deep RxJS integration. An observable represents a stream of values over time, and operators let you transform and combine those streams.

// search.component.ts
import { Component } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
import { debounceTime, distinctUntilChanged, switchMap } from "rxjs";

@Component({ /* ... */ })
export class SearchComponent {
  private query$ = new Subject<string>();

  results = toSignal(
    this.query$.pipe(
      debounceTime(300),
      distinctUntilChanged(),
      switchMap((q) => this.api.search(q)),
    ),
    { initialValue: [] },
  );
}

This is where RxJS earns its place: debouncing, cancelling stale requests and combining streams are hard to express with plain promises. For simple synchronous state, signals are simpler. Modern Angular lets you mix both and convert with toSignal and toObservable.

Services and dependency injection

Shared logic and data belong in services, not components. Dependency injection supplies them wherever they are needed.

// users.service.ts
import { Injectable, inject } from "@angular/core";
import { HttpClient } from "@angular/common/http";

@Injectable({ providedIn: "root" })
export class UsersService {
  private http = inject(HttpClient);

  getAll() {
    return this.http.get<User[]>("/api/users");
  }
}
// users.component.ts
export class UsersComponent {
  private users = inject(UsersService);
  list = toSignal(this.users.getAll(), { initialValue: [] });
}

providedIn: "root" registers a singleton for the whole app. DI makes services easy to swap in tests and keeps components focused on presentation.

Routing

The Angular Router maps URLs to components and supports nested routes, guards, resolvers and lazy loading.

// app.routes.ts
import { Routes } from "@angular/router";

export const routes: Routes = [
  { path: "", component: HomeComponent },
  { path: "users/:id", component: UserProfileComponent },
  {
    path: "admin",
    loadChildren: () => import("./admin/admin.routes").then((m) => m.ADMIN_ROUTES),
  },
];

Lazy loading feature areas keeps the initial bundle small, and guards let you block navigation until a condition such as authentication is met.

Forms and HTTP

Angular has two form approaches. Reactive forms define the model and validators in the class, which scales well and is testable. Template-driven forms use directives like ngModel and are quicker for simple cases.

// signup.component.ts
form = new FormGroup({
  email: new FormControl("", [Validators.required, Validators.email]),
  password: new FormControl("", [Validators.required, Validators.minLength(8)]),
});

submit() {
  if (this.form.invalid) return;
  this.http.post("/api/signup", this.form.getRawValue()).subscribe();
}

HttpClient returns observables, so requests compose with RxJS operators for retries, cancellation and error handling.

Best practices

  • Prefer standalone components and signals for new code.
  • Keep components focused on presentation and move logic into services.
  • Use reactive forms for anything with validation or dynamic fields.
  • Lazy-load feature areas to keep the initial bundle small.
  • Use OnPush change detection or signals to limit unnecessary checks.
  • Track items in @for with a stable unique value.
  • Unsubscribe from long-lived observables, or use the async pipe and toSignal.

Common mistakes

  • Putting business logic in components instead of services.
  • Subscribing manually and forgetting to unsubscribe, causing leaks.
  • Reaching for RxJS when a signal would be simpler.
  • Mutating a signal’s value directly instead of using set or update.
  • Building one enormous NgModule instead of lazy-loaded feature routes.
  • Treating Angular like React and fighting the framework’s structure.

Where to go next

Angular rewards patience with a structure that holds up over years. Deepen your TypeScript, understand how it compares to React and Vue, and learn the Node.js tooling that powers its CLI. Then build a feature with routing, a service and a reactive form to see how the pieces fit.

Local state

Signals are the modern default for synchronous component state. Reach for RxJS when you genuinely have a stream.

Prefer
const count = signal(0);
count.update((c) => c + 1);
// read it in the template
// {{ count() }}
Avoid
count$ = new BehaviorSubject(0);
count$.next(
  this.count$.value + 1,
);
// needs an async pipe

Building forms

Reactive forms keep the model in the class, which scales better for validation and dynamic fields.

Reactive
form = new FormGroup({
  email: new FormControl("", [
    Validators.required,
    Validators.email,
  ]),
});
Template-driven
<form #f="ngForm">
  <input
    name="email"
    ngModel
    required
    email
  />
</form>

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning Angular?

Our interactive tutorial walks you through Angular step by step — with quizzes and real code you can run in the browser.