#The @Component decorator
A component is an ordinary TypeScript class with @Component({...}) on top. The decorator is how you hand Angular the information a plain class doesn't have: which HTML element it controls (selector), what it renders (template), what that template uses (imports), and how it's styled (styles). The class holds the state and behaviour; the decorator's object — the metadata — tells Angular how to turn that class into something on screen.
@Component is a TypeScript decorator: a function that is applied to the class when the class is defined (classes themselves are covered on the TypeScript site). But Angular's decorators are unusual. You might expect the decorator to run in the browser and register the class somewhere. It doesn't. The Angular compiler reads the decorator at build time, compiles the template, and replaces the decorator with static fields on the class. By the time the code reaches the browser, the decorator is effectively gone.
A mental model: the decorator is a build order form stapled to the class. The compiler reads the form, builds the parts (a factory that creates instances, and compiled template instructions), bolts them onto the class, and throws the form away.
import { Component, input } from '@angular/core';
import { UpperCasePipe } from '@angular/common';
@Component({
selector: 'app-user-card',
imports: [UpperCasePipe],
template: `
<h3>{{ name() | uppercase }}</h3>
<p>{{ role() }}</p>
`,
styles: `h3 { margin: 0; }`,
host: { class: 'card' },
})
export class UserCard {
readonly name = input.required<string>();
readonly role = input('Developer');
}ɵfac and ɵcmpThis is what the compiler produced for that class (taken from the CLI's test build with --dump-virtual-files; ɵ is ɵ, and I've trimmed dev-mode-only padding):
var UserCard = class _UserCard {
name = input.required(/* debugName */);
role = input("Developer", /* debugName */);
static ɵfac = function UserCard_Factory(__ngFactoryType__) {
return new (__ngFactoryType__ || _UserCard)();
};
static ɵcmp = i0.ɵɵdefineComponent({ type: _UserCard, selectors: [["app-user-card"]],
hostAttrs: [1, "card"], inputs: { name: [1, "name"], role: [1, "role"] },
decls: 5, vars: 4,
template: function UserCard_Template(rf, ctx) {
if (rf & 1) {
i0.ɵɵdomElementStart(0, "h3");
i0.ɵɵtext(1);
i0.ɵɵpipe(2, "uppercase");
i0.ɵɵdomElementEnd();
i0.ɵɵdomElementStart(3, "p");
i0.ɵɵtext(4);
i0.ɵɵdomElementEnd();
}
if (rf & 2) {
i0.ɵɵadvance();
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind1(2, 2, ctx.name()));
i0.ɵɵadvance(3);
i0.ɵɵtextInterpolate(ctx.role());
}
},
dependencies: [UpperCasePipe],
styles: ["\nh3[_ngcontent-%COMP%] {\n margin: 0;\n}"] });
};
// …plus, only when ngDevMode is on: ɵsetClassMetadata(UserCard, [{ type: Component, args: [{…}] }])ɵfacis the factory: a function that doesnew UserCard(). Angular always creates components through it, inside an injection context, which is whyinject()works in field initialisers (see Component class vs view).ɵcmpis the component definition. Every metadata field became data:selector→selectors: [["app-user-card"]],host: { class: 'card' }→hostAttrs: [1, "card"], the twoinput()fields →inputs,imports→dependencies: [UpperCasePipe].- The template string is gone. It was compiled into the function
UserCard_Template(rf, ctx).rf & 1is the creation block: create the<h3>, a text node, the pipe instance, the<p>and its text node — 5 slots, hencedecls: 5.rf & 2is the update block: re-evaluatectx.name()through the pipe andctx.role(), and write them into the text nodes.ctxis your component instance. - The styles were rewritten at build time:
h3becameh3[_ngcontent-%COMP%]; at runtime%COMP%is replaced with a per-component id. That's style encapsulation (see View encapsulation and styling). - The original decorator arguments survive only inside an
ngDevModecheck (ɵsetClassMetadata) for tooling and TestBed overrides. In a production build that block is dropped, so there's no runtime cost to the decorator.
reflectComponentTypeAt runtime you can confirm what's on the class:
const mirror = reflectComponentType(UserCard)!;
mirror.selector; // 'app-user-card'
mirror.isStandalone; // true
mirror.inputs; // [{ propName: 'name', templateName: 'name', isSignal: true }, { propName: 'role', … }]
Object.getOwnPropertyNames(UserCard).filter((k) => k.startsWith('ɵ')); // ['ɵfac', 'ɵcmp']@Component metadata fields referenceThe metadata fields you'll actually use, from interface Component extends Directive in @angular/core 22.2:
| Field | What it does |
|---|---|
selector | CSS selector that decides where the component is created (see Selectors) |
template / templateUrl | the view, inline or in a file (see Inline vs external templates and styles) |
styles / styleUrl / styleUrls | component styles, inline or from files |
imports | components, directives, pipes and NgModules the template uses (see The modern import model) |
host | bindings and listeners on the host element (see The host element) |
providers / viewProviders | services scoped to this component and its children (see Component-level providers) |
changeDetection | OnPush or Eager; OnPush is the default in Angular 22 (see OnPush) |
encapsulation | Emulated (default), None, ShadowDom |
inputs / outputs | the old array form of inputs and outputs; today you use input() / output() fields |
exportAs | a name for #ref="name" template references |
hostDirectives | directives applied to the host automatically (composition) |
schemas | e.g. CUSTOM_ELEMENTS_SCHEMA to allow unknown elements such as Web Components |
standalone | defaults to true; set false only for NgModule-declared components |
animations | deprecated since 20.2 in favour of animate.enter / animate.leave |