Technology  /  Angular

๐Ÿ…ฐ๏ธ Angular 15 guides ยท updated 2026

Standalone components, signals, RxJS, and dependency injection โ€” building maintainable frontends with the framework enterprises trust.

Angular Components and Templates: Structure, Styles, and Composition

Everything on screen in an Angular app is a component. The page is a component containing components containing components โ€” a tree, with data flowing down and events flowing up. Master the component model and the rest of Angular is details; misunderstand it and every feature fights you.

This guide covers the component in depth: file anatomy, template syntax fundamentals, style encapsulation (one of Angularโ€™s best features), composition and content projection, and the structural conventions that keep large apps navigable.


Anatomy: Class, Template, Styles, Selector

The CLI generates the standard shape โ€” ng generate component order-summary produces:

order-summary.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-order-summary',
templateUrl: './order-summary.html',
styleUrl: './order-summary.css',
})
export class OrderSummary {
// state and behavior live here
}

The division of labor is crisp: the class holds state and logic (plain TypeScript โ€” everything from the JS and TS worlds applies), the template declares what the UI looks like given that state, the styles scope to this component, and the selector is the custom HTML tag other templates use. Inline template: (as in the introโ€™s example) versus templateUrl: is pure preference โ€” inline suits small components; separate files suit anything substantial. Same for styles.

One naming note for the modern era: recent CLI versions drop the .component suffix from filenames and class names (OrderSummary, not OrderSummaryComponent) โ€” youโ€™ll see both conventions in the wild; follow your projectโ€™s.

The component is the unit of everything in Angular: reuse, testing, lazy loading, and reasoning. The craft is deciding what deserves to be one โ€” and the working heuristic is responsibility, not size: a component should render one concept (a product card, a filter bar, a checkout summary). When a template needs a scroll to read, or its class juggles unrelated state, itโ€™s several components in a trench coat โ€” the same โ€œnameable things, one jobโ€ rule as classes generally.


Template Syntax: The Fundamentals

Angular templates are HTML plus a compiled expression language. The three core notations:

<!-- interpolation: render expressions -->
<h2>{{ order.customer.name }}</h2>
<p>{{ itemCount() }} items ยท {{ totalPaise() / 100 | currency:'INR' }}</p>
<!-- property binding: set DOM/component properties from expressions -->
<img [src]="product.imageUrl" [alt]="product.name" />
<button [disabled]="itemCount() === 0">Checkout</button>
<!-- event binding: DOM events call class methods -->
<button (click)="checkout()">Checkout</button>
<input (input)="onSearch($event)" />

The mnemonic that sticks: [ ] data flows in, ( ) events flow out โ€” square brackets receive, parentheses emit. Without brackets, attributes are static strings: src="product.imageUrl" literally sets the URL to that text โ€” a classic first-week bug.

Template expressions are deliberately restricted JavaScript: property access, method calls, operators, ternaries and ?? โ€” but no assignments, new, or statements. The restriction is a feature: logic belongs in the class where itโ€™s testable; templates read state. If an expression needs three ternaries, extract a method or a computed signal.

Two conveniences youโ€™ll use constantly:

<button (click)="remove(item.id)">โœ•</button> <!-- pass data on events -->
<input #searchBox (keyup.enter)="search(searchBox.value)" /> <!-- template ref + key filter -->

#searchBox declares a template reference variable โ€” a handle on an element usable elsewhere in the template. keyup.enter is Angular filtering key events declaratively โ€” no if (e.key === 'Enter') boilerplate.


Styles That Canโ€™t Leak: View Encapsulation

Every componentโ€™s styles apply only to its own template โ€” automatically:

order-summary.css
h2 { font-size: 1.1rem; color: #333; } /* affects ONLY this component's h2s */

No naming conventions, no CSS-in-JS machinery โ€” Angular rewrites selectors at build time (attaching generated attributes like _ngcontent-xyz) so h2 here canโ€™t touch any other componentโ€™s headings. This is view encapsulation, and it quietly eliminates the global-CSS fear that haunts large codebases: you can write obvious, short selectors forever.

The escape hatches, for when you need them: global styles live in src/styles.css (design tokens, resets, typography); :host styles the componentโ€™s own tag (:host { display: block; } โ€” worth knowing because custom elements default to inline); and CSS custom properties pierce encapsulation by design, making them the sanctioned theming channel (--brand-color defined globally, consumed locally). The deprecated ::ng-deep pierces it by force โ€” treat it as legacy you recognize, not a tool you reach for.


Composition: Building Trees

Components use other components by importing them and using their selector:

@Component({
selector: 'app-cart-page',
imports: [OrderSummary, CartItemRow], // โ† declare what the template uses
template: `
<h1>Your cart</h1>
@for (item of items(); track item.id) {
<app-cart-item-row [item]="item" (removed)="onRemove($event)" />
}
<app-order-summary [items]="items()" />
`,
})
export class CartPage { ... }

That imports array is the standalone-era contract: each component declares its template dependencies, the compiler verifies them, and unused imports are flagged. (In legacy NgModule code, this wiring lived in module declarations โ€” one more reason the old model was harder to trace.)

The bindings on child components โ€” [item]="..." in, (removed)="..." out โ€” are the parent-child data protocol, important enough for a dedicated guide. Here, absorb the architectural shape they create: data down, events up, a one-way flow that makes any componentโ€™s state traceable to a single owner above it. When two distant components need the same data, the answer is never sideways links โ€” itโ€™s lifting the state to a shared ancestor or a service.

Content projection: components with holes

Inputs pass data; sometimes a parent should supply markup. <ng-content> makes a component a frame around caller-provided content:

@Component({
selector: 'app-panel',
template: `
<section class="panel">
<header><ng-content select="[panel-title]" /></header>
<div class="body"><ng-content /></div>
</section>
`,
})
export class Panel {}
<app-panel>
<h3 panel-title>Shipping address</h3> <!-- lands in the header slot -->
<address-form /> <!-- everything else: default slot -->
</app-panel>

Multi-slot projection (via select) is how layout components โ€” cards, dialogs, tabs, accordions โ€” stay generic while callers keep full control of contents. If you find a component sprouting inputs like titleText, bodyHtml, footerButtonLabel, itโ€™s begging to become projection instead: pass markup, not markup-describing data. (React readers: this is children and named slots, same idea.)


Conventions That Keep 300 Components Navigable

Hard-won structural habits, compressed:

Organize by feature, not by type. features/checkout/ containing its components, services, and routes beats global components/ + services/ buckets โ€” the same module-design principle as ever. The CLI supports it: ng g component features/checkout/payment-form.

Smart/presentational split โ€” loosely applied. Some components fetch data and orchestrate (page-level, route targets); most just render inputs and emit events. The presentational majority is trivially testable and reusable precisely because it touches no services. Donโ€™t enforce the split dogmatically; do notice when a deeply-nested component starts injecting services โ€” thatโ€™s usually state that wanted to live higher.

Selectors get a prefix (app-, or a project-specific one like acme-) โ€” configured in angular.json, enforced by lint, preventing collisions with future HTML elements and third-party libraries.

Templates stay declarative. The moment template expressions turn clever โ€” chained ternaries, arithmetic soup โ€” move the computation into the class as a computed signal with a name. The template should read like a description of the UI, because thatโ€™s what it is.


Decomposition, Worked: One Page Becomes Seven Components

The โ€œwhen do I splitโ€ judgment is best transferred by example. A product pageโ€™s requirements: image gallery, title/price block, variant picker, add-to-cart, tabbed details, review list with pagination, related-products strip. The naive version is one ProductPage with a 300-line template. The decomposed version:

ProductPage (route target: fetches, owns page state)
โ”œโ”€โ”€ ProductGallery [images]
โ”œโ”€โ”€ ProductSummary [product] (variantChange) (addToCart)
โ”‚ โ”œโ”€โ”€ VariantPicker [variants] [selected] (selectedChange)
โ”‚ โ””โ”€โ”€ AddToCartButton [disabled] (clicked)
โ”œโ”€โ”€ ProductTabs
โ”‚ โ”œโ”€โ”€ (projected) DetailsPanel [specs]
โ”‚ โ””โ”€โ”€ (projected) ReviewList [productId]
โ””โ”€โ”€ RelatedStrip [productId]

The reasoning at each cut: ProductPage stays โ€œsmartโ€ โ€” itโ€™s the only one touching services; everything below renders inputs and emits events, so everything below is trivially testable and reusable. VariantPicker exists because variant-selection logic (option compatibility, stock per combination) is a coherent responsibility with its own state โ€” the โ€œcan I name it honestly?โ€ test passing. ProductTabs is generic โ€” tabs know nothing about products; the panels arrive by projection, so the same tabs component serves the account page next sprint. ReviewList takes a productId and manages its own pagination โ€” a judgment call: its data needs are self-contained enough that making the page relay pages of reviews would pollute the parent (the communication guide formalizes this trade). AddToCartButton might look too small to exist โ€” until it accumulates loading state, quantity, and a disabled-with-reason tooltip, which is exactly what happens to add-to-cart buttons.

The counter-lesson matters too: ProductSummaryโ€™s title and price didnโ€™t become ProductTitle and ProductPrice components โ€” theyโ€™re two bindings with no behavior. Decomposition serves responsibilities, not line counts; a component with no logic, no reuse, and no independent state is just indirection wearing a selector.

Frequently Asked Questions

How do components differ from web components / custom elements? Angular components compile into the frameworkโ€™s rendering machinery โ€” theyโ€™re not native custom elements (though Angular can export them as such via Angular Elements). The custom-tag syntax is shared vocabulary, not shared implementation.

Can a component render itself recursively? Yes โ€” a componentโ€™s template can use its own selector (trees, nested comments, org charts). Just ensure a terminating condition, as with any recursion.

Where did NgIf/NgFor imports go in my imports array? The new built-in control flow (@if, @for) is part of the template language itself โ€” no imports needed, unlike the old structural directives they replace. If youโ€™re maintaining code with *ngIf, youโ€™ll see CommonModule or NgIf imported; new code skips all that.

One component per file? Yes, as a near-universal convention โ€” with the pragmatic exception of tiny, private helper components co-located with their only consumer. The unit of the file should be the unit of understanding.

How big should the class be? If it exceeds a couple hundred lines, something wants extracting: presentation logic into computed signals, business logic into services, or the component into smaller ones. Components are coordinators, not repositories of logic.


Component Styling in Practice: The Layering

The encapsulation section covered mechanics; the working system most teams converge on has three layers. Global (styles.css): design tokens as CSS custom properties (--space-2, --color-danger, --radius-md), resets, and typography โ€” the vocabulary. Component styles: layout and appearance of this component, written in terms of the tokens (padding: var(--space-2)) โ€” short selectors, fearless because encapsulated. State classes driven by bindings: [class.selected], [class.loading] โ€” the template toggles semantic class names; the stylesheet defines what they mean. What this layering prevents: magic numbers scattered per component (token drift makes redesigns archaeological), deep selectors reaching into child components (encapsulation fights you โ€” restyle children via their inputs or CSS variables, the sanctioned channel), and inline [style.x] for anything that isnโ€™t genuinely computed (a width from a progress value: yes; colors and spacing: tokens). A useful review heuristic: component CSS containing raw hex colors or pixel paddings is either missing a token or ignoring one.

Self-Check

  1. <img src="product.imageUrl"> renders a broken image. Why, and whatโ€™s the fix?
  2. Two components both style .card { padding: 16px } differently. What happens, and what mechanism makes it safe?
  3. A Modal component has inputs titleText, bodyText, showFooter, footerText. What refactor does this API beg for?
  4. In the โ€œdata down, events upโ€ model, where does shared state between sibling components live?
  5. Why are assignments banned in template expressions?

(Answers: missing brackets โ€” the attribute is the literal string; [src] binds the expression. Each applies only within its own component โ€” view encapsulation rewrites selectors. Content projection with slots โ€” pass markup, not text-describing-markup. In the nearest common ancestor (or a service when lifting gets awkward). Templates are declarative renderings of state; mutations belong in class methods where theyโ€™re testable and traceable.)

Next: Data Binding and Control Flow โ€” two-way binding, @if/@for/@switch in depth, track and why it matters, and the legacy *ngIf forms youโ€™ll still meet.