Angular Component Anatomy: @Component, Template, Selector, and Standalone Imports [2026]
Almost every screen of every Angular app is built from one shape: a TypeScript class with an @Component decorator on top. Open any Angular codebase and the same five-or-six fields appear over and over — selector, template, styleUrl, imports, plus the class body underneath. This lesson dismantles that shape into its parts.
This is lesson 2.1 of the Angular Tutorial, opening Module 2. Module 1 covered everything around components — CLI, bootstrap, DI, TypeScript, the build. From here on we are writing application code, and every lesson in Module 2 builds on the anatomy below.
The minimum viable component #
The smallest legal Angular component is three lines plus a class:
import { Component } from '@angular/core';
@Component({
selector: 'app-hello',
template: '<h1>Hello!</h1>',
})
export class HelloComponent {}
Three pieces are at work:
| Piece | What it does |
|---|---|
@Component({...}) |
A decorator that registers metadata on the class — Angular reads this at compile time |
selector: 'app-hello' |
The custom-element name Angular wires this component to. Templates that say <app-hello> get this rendered |
template: '<h1>Hello!</h1>' |
The HTML Angular replaces the selector with at runtime |
That is sufficient for Angular to render <app-hello></app-hello> anywhere. Every other field you will see in real code is optional — they exist to add styling, imports, lifecycle, change detection, animations, or providers.
The full @Component metadata table #
Reading any production component, you will encounter these fields:
| Field | Type | What it does |
|---|---|---|
selector |
string | CSS-style selector used to recognize the component in templates |
template / templateUrl |
string | Inline template or path to an external .html file |
styles / styleUrl / styleUrls |
string / string / string[] | Inline CSS or path(s) to external stylesheet(s) |
imports |
array | Other standalone components, directives, pipes used in this template |
providers |
array | DI providers scoped to this component's element injector |
viewProviders |
array | Like providers, but invisible to content projected via <ng-content> |
changeDetection |
enum | Default (legacy) or OnPush (modern default in v22+) |
encapsulation |
enum | Emulated (default), ShadowDom, or None — controlled in lesson 2.11 |
animations |
array | Angular animations triggers (Module 13) |
host |
object | Bindings applied to the host element itself (lesson 2.13) |
standalone |
boolean | Default true since v19. Almost never set explicitly. |
Most components use 4-6 of these. The CLI's ng g component scaffolds with selector, imports, templateUrl, and styleUrl. Everything else is opt-in.
Selector flavors #
The selector field is a CSS-style selector — not arbitrary text. Three shapes work:
@Component({ selector: 'app-header' }) // matches <app-header>
@Component({ selector: '[appTooltip]' }) // matches any element with appTooltip attribute
@Component({ selector: '.app-glow' }) // matches any element with class app-glow
The element selector (app-header) is the common case. The attribute and class selectors are for directives, not components — they let you decorate existing elements without wrapping them. Lesson 2.9 covers directives.
Conventionally, every component selector is prefixed (Angular Material uses mat-, the CLI defaults to app- for new projects). This prevents collisions with native HTML elements and with components from other libraries.
The imports array — the standalone era #
Since standalone became the default (v19), every component declares its own dependencies. If your template uses <router-outlet>, you import RouterOutlet. If it uses the async pipe, you import AsyncPipe.
import { Component } from '@angular/core';
import { RouterOutlet, RouterLink } from '@angular/router';
import { UserCardComponent } from './user-card.component';
import { CurrencyPipe } from '@angular/common';
@Component({
selector: 'app-shell',
imports: [RouterOutlet, RouterLink, UserCardComponent, CurrencyPipe],
template: `
<a routerLink="/users">Users</a>
<router-outlet></router-outlet>
<app-user-card [user]="current()" />
<p>{{ price | currency:'USD' }}</p>
`,
})
export class AppShell { /* ... */ }
The imports array is the only way to make external symbols available inside this component's template. If you forget to add an import, the template compiler refuses the build with a useful error pointing at the offending symbol.
New-in-v22: the @Component decorator's selectorless syntax (lesson 2.3) drops the imports requirement when components are imported directly as classes in the template — but for now, treat imports as required for anything non-trivial.
Templates: inline vs external #
Two flavors, both legal:
// Inline (good for tiny templates)
@Component({
template: `<h1>{{ title }}</h1>`,
})
// External file (good for everything else)
@Component({
templateUrl: './user-list.html',
})
The choice is purely ergonomic. Inline templates work well for single-line components. Past ~10 lines, external files give you better editor support — most IDEs treat .html files as a full HTML language service with Angular template extensions enabled. The CLI's ng g component defaults to external.
You cannot have both. Setting template AND templateUrl on the same component fails at compile time.
Styles: inline vs external #
Mirror story:
@Component({
styles: `
h1 { color: var(--accent); }
.glass { backdrop-filter: blur(20px); }
`,
})
// OR
@Component({
styleUrl: './user-list.css',
})
The one detail that catches people: styles are scoped by default. The h1 { color: red } rule above applies only to <h1> elements inside this component's template — not to every <h1> on the page. Angular implements this via a CSS attribute selector added at compile time (lesson 2.11 walks ViewEncapsulation in depth).
If you want a global rule, write it in src/styles.css (the global stylesheet) instead, or set encapsulation: ViewEncapsulation.None on the component.
The class body — properties, methods, signals #
The class is where your state and behavior live. A typical 2026 component:
import { Component, signal, computed, inject } from '@angular/core';
import { AuthService } from '../auth.service';
@Component({
selector: 'app-greeting',
template: `
@if (user(); as u) {
<p>Hi, {{ u.name }}! You've logged in {{ visits() }} times.</p>
} @else {
<p>Please sign in.</p>
}
`,
})
export class GreetingComponent {
private auth = inject(AuthService);
user = this.auth.user; // exposed signal
visits = signal(0);
isReturning = computed(() => this.visits() > 1); // derived signal
increment() {
this.visits.update(n => n + 1);
}
}
The class members fall into four buckets:
| Bucket | Examples | Purpose |
|---|---|---|
| Injected dependencies | auth = inject(AuthService) |
Services this component uses |
| Signals | visits = signal(0) |
Reactive state the template reads |
| Computed signals | isReturning = computed(...) |
Derived reactive state |
| Methods | increment(), onSubmit() |
Behaviors triggered by user actions or lifecycle |
Not every component has all four. A presentational component may have only injected signals and computed derivations; an action-heavy form component may have many methods.
What's NOT in a modern component #
A few things you will see in older code that have either gone away or moved:
@Input()and@Output()decorators — replaced by theinput()andoutput()functions called as field initializers (lessons 2.4 and 2.5).@ViewChild()and@ContentChild()decorators — replaced byviewChild()andcontentChild()signal queries (lesson 2.13).- Lifecycle interfaces (
OnInit,OnDestroy) — still supported but mostly replaced byeffect(),afterNextRender(), andDestroyRef.onDestroy()(lesson 2.7). changeDetection: ChangeDetectionStrategy.OnPush— still valid, but redundant in v22+ where OnPush is the default for new components.standalone: true— also redundant, default since v19. Setting it explicitly is a no-op.
If you see any of these in older blog posts or Stack Overflow answers, recognize them as historical artifacts — your new code should not need them.
Common gotchas #
| Symptom | Cause | Fix |
|---|---|---|
Can't bind to 'X' since it isn't a known property of 'app-Y' |
The child component doesn't have an input() named X, or its input() is misspelled |
Check the child component's field definitions |
'app-foo' is not a known element |
The parent component's imports array doesn't include FooComponent |
Add it to the imports array |
| Styles bleed out of the component | The selector started with :host or used ::ng-deep (which is global) |
Drop ::ng-deep, use a CSS variable on the host element instead |
| Template variable typo not caught | strictTemplates is off |
Turn strictTemplates on in tsconfig.json (lesson 1.4) |
| Two components with same selector | A new component reused an existing selector | Selectors must be unique in the imported set — pick a different prefix or suffix |
What's next #
Lesson 2.2 walks the template syntax in depth — every binding form ({{ }}, [], (), [()]), the @if @for @switch @let block syntax, two-way binding desugaring, expression syntax, and the gotchas around safe-navigation and async pipes. Lesson 2.3 follows with selectorless components — the v22 syntax that lets you import a class directly into a template without an imports array entry.
Lessons 2.4 and 2.5 cover the input/output APIs that components use to communicate. Lesson 2.6 handles content projection (<ng-content>). Lesson 2.7 walks lifecycle hooks. By the end of Module 2 you will have written components in every shape Angular supports.
Try it yourself #
The shortest demo of every essential piece in one block:
import { Component, signal, input, output } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<button (click)="inc()">{{ label() }}: {{ count() }}</button>
`,
styles: `button { font-size: 1.2em; padding: 8px 14px; }`,
})
export class CounterComponent {
label = input('Count'); // 2.4 — input signal
count = signal(0); // signal state
changed = output<number>(); // 2.5 — output signal
inc() {
this.count.update(n => n + 1);
this.changed.emit(this.count());
}
}
Three fields (label, count, changed), one method (inc), one inline template, one inline style block — and the component is feature-complete.
get_best_practicesRough rule: under 10 lines stays inline, anything bigger goes external. Inline templates are easier to grep for and easier to refactor for tiny presentational components. External files give you a real HTML language service in your IDE, better diff readability in PRs, and a cleaner Lighthouse view of which component owns what markup. The CLI’s ng g component defaults to external because it’s the right answer for most components.Lesson 2.2 picks up the template syntax — every binding form, every block, every gotcha.
Up next in Angular
More from this topic
Enjoyed this article?
Get new Angular tutorials delivered. No spam — just code-first articles when they ship.


