Tutorials Logic, IN info@tutorialslogic.com

Angular Components Anatomy and Communication

Component Anatomy

A component owns one view boundary: a TypeScript class, template, selector, host element, optional styles, and declared dependencies. This lesson builds the full component contract, from composition and selectors to signal inputs, typed outputs, models, queries, dynamic views, and cleanup.

An Angular component owns a view boundary. Its TypeScript class holds behavior and state, its template describes the DOM, and its selector tells Angular where to create that view.

The object passed to @Component is component metadata. Current Angular components are standalone by default, so a component normally imports its own template dependencies instead of belonging to an NgModule.

Metadata Responsibility
selector Defines the compile-time CSS selector used to create the component.
imports Makes standalone components, directives, pipes, or NgModules available to this template.
template / templateUrl Provides inline HTML or one external template file. templateUrl is relative to the component file.
styles / styleUrl / styleUrls Provides inline styles or one or more component stylesheet files.
providers Creates providers available from the component host and its descendants.
viewProviders Creates providers visible to the component view but not to projected content.
host Declares properties, attributes, classes, styles, and events on the host element.
changeDetection Selects the Default or OnPush change-detection strategy.
encapsulation Selects Emulated, ShadowDom, or None style encapsulation.
standalone Defaults to true in current Angular. Set false only for a component declared by an NgModule.
  • Class: handles user actions, coordinates data, and exposes state read by the template.
  • Template: declares elements, bindings, control flow, child components, and projected-content slots.
  • Selector: matches the host element that receives one component instance and contains the component view.

Complete Component Metadata

Complete Component Metadata
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { CurrencyPipe } from '@angular/common';

@Component({
  selector: 'app-price-badge',
  imports: [CurrencyPipe],
  template: `
    <strong>{{ label() }}</strong>
    <span>{{ price() | currency }}</span>
  `,
  styles: `:host { display: inline-flex; gap: 0.5rem; }`,
  host: { '[class.sale]': 'onSale()' },
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class PriceBadge {
  label = signal('Keyboard');
  price = signal(49);
  onSale = signal(true);
}
Output
Keyboard $49.00

The component imports only the pipe its template uses. The host binding adds the sale class to <app-price-badge>, while OnPush keeps the view compatible with signal-driven updates.

Lifecycle Boundary

Lifecycle APIs are timing boundaries for work that cannot be expressed as normal state derivation. A component page needs the ownership rule; the dedicated lifecycle lesson explains every hook and render callback in sequence.

Prefer signals and computed state for data flow. Use lifecycle APIs for input-transition logic, initialized content or view access, post-render DOM integration, and resource cleanup.

Boundary Component responsibility
constructor Inject dependencies and initialize fields; inputs and the component view are not ready.
ngOnChanges / ngOnInit Respond to assigned inputs or perform one-time setup after initial inputs.
ngDoCheck Implement an exceptional custom check only after measuring the cost and ruling out reactive state.
Content hooks Read initialized projected-content queries without mutating already-checked state.
View hooks Read initialized view queries; avoid repeated expensive work in checked hooks.
afterNextRender Perform one-time browser DOM measurement or integration after the next complete render.
afterEveryRender Synchronize a non-reactive DOM integration after every complete render; keep the callback small.
ngOnDestroy / DestroyRef Stop timers, listeners, observers, sockets, and subscriptions that could outlive the component.

Component-owned Cleanup

Component-owned Cleanup
import { Component, DestroyRef, inject, signal } from '@angular/core';

@Component({
  selector: 'app-clock',
  template: `<time>{{ now() }}</time>`
})
export class Clock {
  private destroyRef = inject(DestroyRef);
  now = signal(new Date().toLocaleTimeString());

  constructor() {
    const timer = window.setInterval(
      () => this.now.set(new Date().toLocaleTimeString()),
      1000
    );
    this.destroyRef.onDestroy(() => window.clearInterval(timer));
  }
}

The interval belongs to this component instance. DestroyRef keeps the teardown beside the setup and prevents the timer from continuing after the view is removed.

Component Composition

A parent composes a child by importing the child class and placing an element that matches its selector in the parent template. Angular creates one child instance for each matching host element.

The host element is the element matched by the selector. The DOM produced from the component template is its view. Repeating this relationship creates the application component tree, which also shapes injector and query boundaries.

Composition choice Use it when
Standalone import The dependency is standalone and the component template uses it directly.
NgModule import Existing compatibility code exports a non-standalone declaration.
ApplicationConfig provider A router, HTTP client, hydration feature, or application-wide service belongs at bootstrap.
Route-level lazy import The component should load only when a route is activated.
  • Import every standalone component, directive, and pipe used by the template.
  • For a non-standalone compatibility component, import the NgModule that declares and exports it.
  • A missing import usually appears as an unknown element, unknown property, or unavailable pipe during compilation.

Import and Render a Child

Import and Render a Child
import { Component } from '@angular/core';
import { PriceBadge } from './price-badge';

@Component({
  selector: 'app-catalog-page',
  imports: [PriceBadge],
  template: `
    <h1>Catalog</h1>
    <app-price-badge />
  `
})
export class CatalogPage {}

The import makes PriceBadge available to this template. The app-price-badge element is the child host, and the PriceBadge template renders inside it.

Component Communication

Inputs carry parent-owned data into a child, outputs report child events to a parent, and model creates an intentionally writable two-way contract. Keep ownership visible: most values should flow in through inputs and changes should flow out as events.

Angular records input, output, and model declarations statically. Declare them in property initializers; do not try to add component APIs dynamically at runtime.

API Contract
value = input(0) Creates a read-only InputSignal with an inferred default value.
value = input<number>() Creates an optional input whose value may be undefined.
value = input.required<number>() Requires the parent template to provide the input at build time.
label = input('', { transform: trimString }) Normalizes an assigned value with a statically analyzable transform.
value = input(0, { alias: 'sliderValue' }) Uses sliderValue as the public template binding name.
saved = output<Order>() Creates a typed OutputEmitterRef; emit with saved.emit(order).
value = model(0) Creates writable component state and a valueChange output for [(value)] binding.
  • Use input when the parent owns the value and the child only reads it.
  • Use output for a meaningful event such as saved, selected, or dismissed; Angular custom events do not bubble through the DOM.
  • Use model when the child must update the bound value directly, such as a custom control value or an open state.

Typed Input and Output

Typed Input and Output
import { Component, input, output } from '@angular/core';

@Component({
  selector: 'app-greeting',
  template: `
    <p>Hello, {{ name() }}!</p>
    <button (click)="greet.emit(name())">Say Hi</button>
  `
})
export class GreetingComponent {
  name = input.required<string>();
  greet = output<string>();
}

name is read-only inside the child. Clicking the button emits a typed event; it does not mutate parent state directly.

Parent Binding Contract

Parent Binding Contract
import { Component } from '@angular/core';
import { GreetingComponent } from './greeting.component';

@Component({
  selector: 'app-root',
  imports: [GreetingComponent],
  template: `
    <app-greeting
      name="Angular 22"
      (greet)="onGreet($event)"
    />
    <p>{{ message }}</p>
  `
})
export class AppComponent {
  message = '';
  onGreet(name: string) {
    this.message = \`Greeted: \${name}\`;
  }
}

The literal sets the required name input. The event binding receives the emitted string as $event and lets the parent decide how its state changes.

Two-way Model Binding

Two-way Model Binding
// quantity-stepper.ts
import { Component, model } from '@angular/core';

@Component({
  selector: 'app-quantity-stepper',
  template: `
    <button (click)="quantity.update(value => Math.max(1, value - 1))">-</button>
    <output>{{ quantity() }}</output>
    <button (click)="quantity.update(value => value + 1)">+</button>
  `
})
export class QuantityStepper {
  quantity = model(1);
}

// parent template
// <app-quantity-stepper [(quantity)]="cartQuantity" />

model allows the child to update quantity and emits quantityChange for the parent binding. Use this only when two-way ownership is the intended component contract.

Selectors and Hosts

Angular matches component selectors statically when it compiles a template. One element can match only one component, selector matching is case-sensitive, and changing classes or attributes later does not create a different component instance.

The matched DOM element is the component host. Use host metadata when the component must expose native semantics, accessibility attributes, classes, styles, or event behavior on that element.

Selector Match
app-product-card A custom <app-product-card> element; this is the normal component selector.
button[app-save] A native button carrying the app-save attribute.
.app-panel An element carrying the app-panel class; use sparingly because element selectors are clearer.
[dropzone]:not(textarea) An element with dropzone except a textarea.
app-card, [app-card] Either selector in a comma-separated selector list.
  • Prefer a custom element selector with a hyphen and a short project prefix, such as app-product-card.
  • Use an attribute selector on a native element when preserving that element's built-in behavior and ARIA API is valuable.
  • Do not use the ng prefix, CSS combinators, namespaces, or pseudo-classes other than :not in a component selector.

Accessible Host Contract

Accessible Host Contract
import { Component, input, output } from '@angular/core';

@Component({
  selector: 'button[app-save]',
  template: `{{ label() }}`,
  host: {
    'type': 'button',
    '[attr.aria-busy]': 'saving()',
    '[disabled]': 'saving()',
    '(click)': 'save.emit()'
  }
})
export class SaveButton {
  label = input('Save');
  saving = input(false);
  save = output<void>();
}

The attribute selector preserves native button semantics. Host metadata reflects busy and disabled state on the host and emits one component event from the native click.

Children and Dynamic Views

A component can reference children in its own view, inspect content supplied by a parent, project that content into named slots, or create a component type dynamically. These APIs cross a view boundary, so use the narrowest one that matches the ownership relationship.

API Boundary
viewChild / viewChildren Returns reactive queries for matching children in the component view.
contentChild / contentChildren Returns reactive queries for matching projected content.
<ng-content> / select Projects all caller markup or routes matching content into named slots.
NgComponentOutlet Renders a component type declaratively in a template.
ViewContainerRef.createComponent Creates and inserts a component programmatically at a view-container location.
createComponent Creates a component with an explicit host and environment when lower-level control is required.
  • Use view queries for elements, directives, or components declared by this component template.
  • Use content queries for descendants supplied through ng-content by the parent.
  • Use projection for caller-owned markup; use an input when the child only needs data rather than markup.
  • Use dynamic rendering when the component type is selected at runtime, not as a replacement for ordinary template control flow.

Reactive View Query

Reactive View Query
import { Component, ElementRef, afterNextRender, viewChild } from '@angular/core';

@Component({
  selector: 'app-search-box',
  template: `<input #searchInput type="search" aria-label="Search" />`
})
export class SearchBox {
  searchInput = viewChild.required<ElementRef<HTMLInputElement>>('searchInput');

  constructor() {
    afterNextRender(() => this.searchInput().nativeElement.focus());
  }
}

The query belongs to the component view. afterNextRender waits until Angular has committed the browser DOM before focusing the required element.

Declarative Dynamic Component

Declarative Dynamic Component
import { NgComponentOutlet } from '@angular/common';
import { Component } from '@angular/core';
import { CompactSummary } from './compact-summary';

@Component({
  selector: 'app-summary-host',
  imports: [NgComponentOutlet],
  template: `<ng-container *ngComponentOutlet="summaryType" />`
})
export class SummaryHost {
  summaryType = CompactSummary;
}

NgComponentOutlet keeps a runtime component choice declarative. Use ViewContainerRef only when code must control the insertion location or lifecycle directly.

Component Design

A useful component has one coherent view responsibility and a small public contract. Split by ownership and behavior, not merely because a file crossed an arbitrary line count.

Need Choose
Own a rendered view Component
Add behavior to an existing host without owning a view Directive
Share data access or business coordination Injectable service
Transform a value for display Pipe or computed state
Reuse caller-provided markup Content projection
  • Keep derived values in computed signals instead of copying input values into additional writable state.
  • Expose domain events such as submitted or dismissed rather than leaking internal button-click details.
  • Keep application-wide data access in services; keep temporary presentation state beside the view that owns it.
  • Preserve native HTML behavior and accessible names before adding custom keyboard or focus logic.
  • Split a child when it has a reusable UI contract, independent state lifetime, projection boundary, or focused test surface.
Confirm the page outcome

Component Review

5 checks
  • I can identify the component class, selector, host, template view, styles, imports, and provider boundary.
  • I can choose an element or attribute selector and explain Angular's selector restrictions.
  • I can design a typed input, output, or model contract based on who owns the value.
  • I can distinguish view children, projected content, and dynamically created components.
  • I can place setup and cleanup work at the correct lifecycle boundary without copying reactive state.

Component Contract Failures

  • Template dependency not imported

    Add the standalone component, directive, or pipe to imports. For compatibility declarations, import the NgModule that exports it.
  • Input copied into stale local state

    Read the input signal directly or derive a value with computed. Use linkedSignal only when the child needs writable state that reconciles with source changes.
  • Custom component replaces native semantics

    Prefer a native element with an attribute selector when button, input, link, or form behavior should remain available.

Apply the page outcome

Build Component Contracts

0 of 2 completed

  1. Create a standalone card with required product input, optional compact input, selected output, CurrencyPipe import, and an accessible host label. Let the parent own the product and cart state; emit only the selection event.
  2. Create a child control with model-based two-way quantity binding and prevent values below one. Use model.update so the parent receives quantityChange automatically.

Component Review Questions

Angular cannot reliably decide which component should own that element, and compilation normally fails with a selector-collision error. Treat selectors as public names: give application components a consistent prefix and keep library prefixes distinct.

input.required<string>() defines a mandatory component contract rather than a signal with a default value. Angular expects every use of app-greeting to provide name, either as a literal such as name="Angular" or as a property binding such as [name]="currentName".

ngAfterViewChecked can run after many change-detection passes, not only when the particular child or DOM value your code cares about changes. Expensive calculations, layout measurements, network calls, or unconditional state updates placed there may execute repeatedly.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.