@angular/core
@angular/core is the runtime of the Angular framework: components, dependency injection, signals for reactive state, lifecycle hooks, and change detection. Angular is a full platform rather than a view library; the official package family covers routing, forms, HTTP, SSR, and animations, and the CLI generates, builds, tests, and migrates projects. Modern Angular is standalone-component and signals based, with template control flow (@if, @for) built into the compiler, and v22 continues that line. You almost never install this package alone; it arrives as part of a CLI-generated workspace.
Still the strongest option for large enterprise TypeScript apps that value one official way to do everything. For small or fast-moving projects the framework weight and twice-yearly major cadence cost more than they return.
Use it if
- You are building a large, long-lived app where one official answer for routing, forms, HTTP, DI, and testing beats assembling a stack from npm
- Your team wants strong conventions and TypeScript-first tooling: the CLI scaffolds, builds, and upgrades projects (ng update) with automated migrations
- You need structured dependency injection for testability across hundreds of services and components
- You maintain enterprise apps where a predictable release schedule and long deprecation windows matter more than novelty
- You are building a content site or a small widget: the core runtime is about 135 KB gzipped before your own code, and React, Vue, or Svelte get you shipping with less framework to learn
- Your team is new to Angular and the deadline is close: DI, RxJS, signals, and the CLI workspace layout are a genuine learning curve compared to Vue or Svelte
- You want maximum third-party choice: the component and library scene is noticeably thinner than React's
- You dislike scheduled churn: majors land twice a year, and staying supported means running ng update migrations on that clock
Setup reality
You do not npm install @angular/core by hand; you install the CLI globally (npm install -g @angular/cli) and run ng new, which generates the workspace and pins the whole @angular/* constellation plus peer dependencies (rxjs, zone.js, @angular/compiler) at matching versions. Version alignment is the recurring pain: every @angular/* package must move together, so upgrades go through ng update rather than editing package.json. New projects are standalone-component based; older codebases carry NgModules until someone runs the migration.
Patterns
Define a standalone componentstandalone-component
import { Component } from '@angular/core';
@Component({
selector: 'app-hello',
template: '<h1>Hello {{ name }}</h1>',
})
export class HelloComponent {
name = 'Angular';
}Components are standalone by default in modern Angular; you no longer write standalone: true or declare them in an NgModule.
Reactive state with signal and computedsignal-state
import { Component, computed, signal } from '@angular/core';
@Component({
selector: 'app-counter',
template: '<button (click)="inc()">{{ count() }} / {{ double() }}</button>',
})
export class CounterComponent {
count = signal(0);
double = computed(() => this.count() * 2);
inc() {
this.count.update((c) => c + 1);
}
}Signals are functions: read with count(), not count; forgetting the parentheses binds the function object instead of the value.
Declare a typed signal inputsignal-input
import { Component, input } from '@angular/core';
@Component({
selector: 'app-user',
template: '{{ user().name }}',
})
export class UserComponent {
user = input.required<{ name: string }>();
}input() replaces the @Input() decorator; input.required has no default, and reading it before the parent binds it throws.
Emit events to a parent with outputcomponent-output
import { Component, output } from '@angular/core';
@Component({
selector: 'app-save',
template: '<button (click)="saved.emit(\'ok\')">Save</button>',
})
export class SaveButtonComponent {
saved = output<string>();
}output() replaces @Output() with EventEmitter; parents still listen with (saved)="handler($event)" in the template.
Template control flow with @if and @forcontrol-flow
@if (user(); as u) {
<p>{{ u.name }}</p>
} @else {
<p>Loading</p>
}
@for (item of items(); track item.id) {
<li>{{ item.label }}</li>
} @empty {
<li>No items</li>
}track is mandatory in @for; the compiler rejects the block without it, unlike the optional trackBy of the old *ngFor.
Get dependencies with inject()inject-service
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({ providedIn: 'root' })
export class CartService {
private http = inject(HttpClient);
}inject() only works in an injection context (field initializers, constructors, provider factories); calling it inside a method throws NG0203.
Make HTTP calls with HttpClienthttp-request
// app.config.ts
import { provideHttpClient } from '@angular/common/http';
export const appConfig = {
providers: [provideHttpClient()],
};
// user.service.ts
private http = inject(HttpClient);
users$ = this.http.get<User[]>('/api/users');HttpClient observables are cold: nothing fires until something subscribes (the async pipe or toSignal counts).
Lazy-load a route with loadComponentlazy-route
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: 'admin',
loadComponent: () =>
import('./admin/admin.component').then((m) => m.AdminComponent),
},
];loadComponent lazy-loads a standalone component with no NgModule involved; the bundle split happens automatically at build.
Bootstrap a standalone applicationbootstrap-app
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes)],
});bootstrapApplication replaces platformBrowserDynamic().bootstrapModule; app-wide providers all move into this providers array.
Run side effects when signals changesignal-effect
import { Component, effect, signal } from '@angular/core';
@Component({ selector: 'app-theme', template: '' })
export class ThemeComponent {
theme = signal<'light' | 'dark'>('light');
constructor() {
effect(() => {
document.body.dataset.theme = this.theme();
});
}
}effect() must be created in an injection context such as a constructor, and re-runs whenever any signal it read changes.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| react | npm | You want the biggest ecosystem and hiring pool and prefer picking your own router, state, and form libraries |
| vue | npm | You want a batteries-included framework with a gentler learning curve and single-file components |
| svelte | npm | Small bundles and compile-time reactivity matter more than enterprise structure |