AngularJSComponents

Components

Components are the fundamental building blocks of every Angular application. A component controls a portion of the screen — called a view — and consists of:

  • A TypeScript class that holds the component's data and logic
  • An HTML template that defines what the user sees
  • An optional CSS stylesheet that styles the component's view
  • Metadata provided through the @Component decorator

Every Angular app has at least one component — the root component (AppComponent). All other components are nested beneath it in a tree structure.

Anatomy of a Component

TS
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-user-card',      // (1) The HTML tag: <app-user-card>
  standalone: true,               // (2) No NgModule needed
  imports: [CommonModule],        // (3) Other components/pipes/directives used in template
  templateUrl: './user-card.component.html',  // (4) External template file
  styleUrl: './user-card.component.css',      // (5) External style file
  // OR inline:
  // template: `<h2>{{ user.name }}</h2>`,
  // styles: [`h2 { color: #333; }`],
})
export class UserCardComponent {
  @Input({ required: true }) user!: { name: string; email: string };
  @Output() selected = new EventEmitter<string>();

  selectUser() {
    this.selected.emit(this.user.email);
  }
}

The numbered comments map to these @Component metadata properties:

  1. selector — defines the custom HTML tag for this component
  2. standalonetrue means no NgModule is needed (Angular 17+ default)
  3. imports — other standalone components, directives, and pipes used in the template
  4. templateUrl — path to an external HTML file (use template for inline)
  5. styleUrl — path to an external CSS file (use styles for inline; styleUrls for multiple)
Generating a Component with the CLI

Bash
# Full command
ng generate component features/product-card --standalone

# Shorthand
ng g c features/product-card

# Inline template and styles (no separate files)
ng g c features/product-card --inline-template --inline-style

# Skip test file
ng g c features/product-card --skip-tests

This creates four files:

  • product-card.component.ts — the component class
  • product-card.component.html — the template
  • product-card.component.css — the styles
  • product-card.component.spec.ts — the unit test
The @Component Decorator — All Options

Property

Type

Description

selector

string

Custom HTML element name (e.g., app-header)

standalone

boolean

If true, no NgModule required

template

string

Inline HTML template

templateUrl

string

Path to external HTML file

styles

string[]

Inline CSS styles (array of strings)

styleUrl

string

Path to single external stylesheet (Angular 17+)

styleUrls

string[]

Paths to multiple external stylesheets

imports

any[]

Components, directives, pipes used in the template

providers

any[]

Services provided at component scope

changeDetection

ChangeDetectionStrategy

Default or OnPush

encapsulation

ViewEncapsulation

Emulated, None, or ShadowDom

host

object

Host element bindings and listeners

animations

any[]

Component-level animation triggers

Component Inputs — Receiving Data from a Parent

Use @Input() to pass data from a parent component down to a child. This is the primary mechanism for parent → child communication.

TS
// child: badge.component.ts
import { Component, Input } from '@angular/core';

type BadgeColor = 'red' | 'green' | 'blue' | 'gray';

@Component({
  selector: 'app-badge',
  standalone: true,
  template: `
    <span [class]="'badge badge-' + color">{{ label }}</span>
  `,
  styles: [`
    .badge { padding: 0.2rem 0.6rem; border-radius: 999px; font-size: 0.75rem; }
    .badge-red { background: #fee2e2; color: #dc2626; }
    .badge-green { background: #dcfce7; color: #16a34a; }
    .badge-blue { background: #dbeafe; color: #2563eb; }
    .badge-gray { background: #f3f4f6; color: #374151; }
  `],
})
export class BadgeComponent {
  @Input({ required: true }) label!: string;  // required — Angular will error if not passed
  @Input() color: BadgeColor = 'gray';         // optional with default value

  // Input with a transform function (Angular 16+)
  @Input({ transform: (v: string) => v.trim().toUpperCase() })
  code = '';
}

HTML
<!-- parent template — using the Badge component -->
<app-badge label="New" color="green" />
<app-badge label="Sale" color="red" />
<app-badge [label]="product.status" [color]="statusColor" />
Component Outputs — Emitting Events to a Parent

Use @Output() with EventEmitter to send data or notifications from a child component up to its parent. This is child → parent communication.

TS
// child: like-button.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-like-button',
  standalone: true,
  template: `
    <button (click)="onLike()" [class.liked]="liked">
      {{ liked ? '❤️' : '🤍' }} {{ count }}
    </button>
  `,
})
export class LikeButtonComponent {
  @Input() count = 0;
  @Input() liked = false;

  // EventEmitter<T> — T is the type of the emitted value
  @Output() likeToggled = new EventEmitter<{ liked: boolean; count: number }>();

  onLike() {
    const newLiked = !this.liked;
    const newCount = newLiked ? this.count + 1 : this.count - 1;
    this.likeToggled.emit({ liked: newLiked, count: newCount });
  }
}

HTML
<!-- parent template -->
<app-like-button
  [count]="post.likes"
  [liked]="post.isLiked"
  (likeToggled)="onLikeToggled(post.id, $event)"
/>

TS
// parent component class
onLikeToggled(postId: number, event: { liked: boolean; count: number }) {
  this.postService.updateLike(postId, event.liked);
}
Note
\$event in event binding refers to the value emitted by the EventEmitter. Its type is automatically inferred from EventEmitter<T>.
Inline vs External Templates and Styles

Approach

When to Use

Pros

Cons

Inline template

Small, simple components

Everything in one file

No HTML syntax highlighting in some editors

External template

Complex templates (>10 lines)

Full editor support, better formatting

Separate file to maintain

Inline styles

Minimal styling

Convenient for tiny tweaks

No SCSS support

External styles

Any real styling

SCSS, full tooling, easy to maintain

Separate file

TS
// Inline — good for simple components
@Component({
  selector: 'app-spinner',
  standalone: true,
  template: `<div class="spinner"></div>`,
  styles: [`.spinner { width: 40px; height: 40px; border: 4px solid #ccc; border-top-color: #333; border-radius: 50%; animation: spin 0.8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } }`],
})
export class SpinnerComponent {}

// External files — good for real components
@Component({
  selector: 'app-dashboard',
  standalone: true,
  templateUrl: './dashboard.component.html',
  styleUrl: './dashboard.component.scss',
})
export class DashboardComponent {}
Component Interaction Patterns

Angular components communicate in these patterns:

Pattern

Mechanism

Use Case

Parent → Child

@Input() property binding

Pass configuration or data into a child

Child → Parent

@Output() EventEmitter

Child notifies parent of user actions

Parent → Child (template var)

@ViewChild() / template ref

Parent calls child methods directly

Sibling communication

Shared service

Two unrelated components share state

Any → Any (signals)

Signal in a service

App-wide reactive state

Any → Any (RxJS)

BehaviorSubject in a service

Complex async state

Two-Way Binding with Inputs and Outputs

When an @Input and @Output follow the naming pattern valueName / valueNameChange, Angular supports the banana-in-a-box [(banana)] two-way binding syntax as shorthand:

TS
@Component({
  selector: 'app-quantity-picker',
  standalone: true,
  template: `
    <button (click)="decrement()">-</button>
    <span>{{ value }}</span>
    <button (click)="increment()">+</button>
  `,
})
export class QuantityPickerComponent {
  @Input() value = 1;
  @Output() valueChange = new EventEmitter<number>();  // must be "valueChange"

  increment() { this.valueChange.emit(this.value + 1); }
  decrement() { if (this.value > 1) this.valueChange.emit(this.value - 1); }
}

HTML
<!-- Both are equivalent: -->
<app-quantity-picker [value]="quantity" (valueChange)="quantity = $event" />
<app-quantity-picker [(value)]="quantity" />
Host Element Bindings

You can bind classes, attributes, and event listeners to the host element (the &lt;app-my-component&gt; tag itself) using the host metadata property:

TS
@Component({
  selector: 'app-card',
  standalone: true,
  template: `<ng-content />`,
  host: {
    class: 'card',                         // always add this CSS class
    '[class.card--loading]': 'isLoading',  // conditional class
    '[attr.aria-busy]': 'isLoading',       // attribute binding
    '(click)': 'onHostClick()',            // host event
  },
})
export class CardComponent {
  @Input() isLoading = false;

  onHostClick() {
    console.log('Card clicked');
  }
}
Tip
Host bindings are an alternative to the @HostBinding and @HostListener decorators. The host object approach is preferred in modern Angular as it keeps all metadata in the decorator.
Standalone Component Imports

Standalone components must declare every dependency in their imports array. This replaces the NgModule's imports:

TS
import { Component } from '@angular/core';
import { CommonModule, NgClass, NgIf, DatePipe, CurrencyPipe } from '@angular/common';
import { RouterLink } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { ProductCardComponent } from './product-card/product-card.component';
import { SpinnerComponent } from '../shared/spinner/spinner.component';

@Component({
  selector: 'app-shop',
  standalone: true,
  imports: [
    // You can import individual directives/pipes (tree-shakable)
    NgClass,
    NgIf,
    DatePipe,
    CurrencyPipe,
    RouterLink,
    FormsModule,
    // Or the whole CommonModule (includes all common directives/pipes)
    // CommonModule,
    // Custom components
    ProductCardComponent,
    SpinnerComponent,
  ],
  templateUrl: './shop.component.html',
})
export class ShopComponent {}
Note
Prefer importing individual directives (NgIf, NgFor, DatePipe) over the entire CommonModule for better tree-shaking and smaller bundle sizes.
Component Best Practices
  1. Keep components small and focused — each component should do one thing well

  2. Prefer smart/dumb (container/presentational) component pattern for complex UIs

  3. Use @Input() / @Output() for component communication, not direct parent references

  4. Mark @Input() as required: true if the component cannot work without that value

  5. Use OnPush change detection for performance-sensitive lists and heavy UIs

  6. Never access the DOM directly — use Angular bindings, @ViewChild, or Renderer2

  7. Put business logic in services, not components — components handle only UI concerns

  8. Unsubscribe from Observables in ngOnDestroy (or use the async pipe / takeUntilDestroyed)

Warning
Never manipulate the DOM directly with document.querySelector() in Angular components. This breaks server-side rendering and bypasses Angular's change detection. Use template bindings, @ViewChild, or Renderer2 instead.