Back to blog
angularbeginner

Angular Components & Templates

Master Angular components — the @Component decorator, all four binding types, component inputs/outputs, lifecycle hooks, and content projection.

LearnixoApril 15, 20265 min read
AngularComponentsTemplatesData BindingLifecycle Hooks
Share:𝕏

Every Angular app is a tree of components. A component controls a piece of the UI — it has a TypeScript class (logic), an HTML template (view), and optional CSS styles. This lesson covers everything about components from creation to communication.


Anatomy of a Component

TYPESCRIPT
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-user-card',   // how you use it in HTML
  standalone: true,
  imports: [CommonModule],      // other components/directives used in template
  templateUrl: './user-card.component.html',
  styleUrls: ['./user-card.component.css'],
})
export class UserCardComponent implements OnInit {
  name = 'Alice';
  age = 30;

  ngOnInit(): void {
    // runs once after inputs are set
    console.log('Component initialized');
  }
}

Use it in a parent template:

HTML
<app-user-card />

The Four Binding Types

1. Interpolation — {{ expression }}

Render data in the template:

HTML
<h2>{{ name }}</h2>
<p>Age: {{ age }}</p>
<p>{{ 2 + 2 }}</p>
<p>{{ name.toUpperCase() }}</p>

2. Property Binding — [property]="expression"

Bind a DOM property or component input to a class field:

HTML
<img [src]="avatarUrl" [alt]="name" />
<button [disabled]="isLoading">Submit</button>
<input [value]="searchTerm" />

3. Event Binding — (event)="handler($event)"

Listen to DOM events and call class methods:

HTML
<button (click)="save()">Save</button>
<input (input)="onInput($event)" />
<form (submit)="onSubmit($event)">...</form>

In the class:

TYPESCRIPT
save() {
  console.log('Saved!');
}

onInput(event: Event) {
  const value = (event.target as HTMLInputElement).value;
  console.log(value);
}

4. Two-Way Binding — [(ngModel)]

Sync a property with a form input (requires FormsModule):

TYPESCRIPT
@Component({
  standalone: true,
  imports: [FormsModule],
  template: `
    <input [(ngModel)]="name" />
    <p>Hello, {{ name }}</p>
  `,
})
export class ExampleComponent {
  name = 'Alice';
}

[(ngModel)] is shorthand for [ngModel]="name" (ngModelChange)="name = $event".


Component Inputs — @Input()

Pass data from a parent component down to a child:

Child component:

TYPESCRIPT
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-user-card',
  standalone: true,
  template: `
    <div class="card">
      <h3>{{ user.name }}</h3>
      <p>{{ user.email }}</p>
    </div>
  `,
})
export class UserCardComponent {
  @Input() user!: { name: string; email: string };
  @Input() showEmail = true;   // optional input with default
}

Parent template:

HTML
<app-user-card [user]="currentUser" [showEmail]="false" />

Required Inputs (Angular 16+)

TYPESCRIPT
@Input({ required: true }) user!: User;

Angular will throw a build error if the parent doesn't provide this input.


Component Outputs — @Output() + EventEmitter

Send events from child to parent:

Child component:

TYPESCRIPT
import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-confirm-dialog',
  standalone: true,
  template: `
    <div class="dialog">
      <p>Are you sure?</p>
      <button (click)="confirm()">Yes</button>
      <button (click)="cancel()">No</button>
    </div>
  `,
})
export class ConfirmDialogComponent {
  @Output() confirmed = new EventEmitter<void>();
  @Output() cancelled = new EventEmitter<void>();

  confirm() { this.confirmed.emit(); }
  cancel()  { this.cancelled.emit(); }
}

Parent template:

HTML
<app-confirm-dialog
  (confirmed)="onConfirm()"
  (cancelled)="onCancel()"
/>

Lifecycle Hooks

Angular calls these methods at defined points in a component's life:

| Hook | When it runs | |------|-------------| | ngOnInit | Once, after first ngOnChanges. Use for data fetching. | | ngOnChanges | Every time an @Input() value changes. | | ngOnDestroy | Just before the component is destroyed. Use to unsubscribe. | | ngAfterViewInit | After the component's view is fully initialized. | | ngDoCheck | On every change detection run. Use carefully — runs often. |

TYPESCRIPT
import { Component, OnInit, OnDestroy, OnChanges, SimpleChanges, Input } from '@angular/core';
import { Subscription } from 'rxjs';

@Component({ selector: 'app-example', standalone: true, template: '' })
export class ExampleComponent implements OnInit, OnChanges, OnDestroy {
  @Input() userId!: number;

  private sub!: Subscription;

  ngOnChanges(changes: SimpleChanges): void {
    if (changes['userId']) {
      console.log('userId changed:', changes['userId'].currentValue);
    }
  }

  ngOnInit(): void {
    // safe to use this.userId here
    this.loadUser(this.userId);
  }

  ngOnDestroy(): void {
    // clean up subscriptions to prevent memory leaks
    this.sub?.unsubscribe();
  }

  private loadUser(id: number) { /* ... */ }
}

Template Variables — #ref

Reference a DOM element or child component directly in the template:

HTML
<input #searchInput type="text" />
<button (click)="search(searchInput.value)">Search</button>
HTML
<!-- Access a child component's public methods -->
<app-counter #counter />
<button (click)="counter.reset()">Reset Counter</button>

Content Projection — <ng-content>

Let parent components inject HTML into a child component's template (like React's children):

Card component:

TYPESCRIPT
@Component({
  selector: 'app-card',
  standalone: true,
  template: `
    <div class="card">
      <div class="card-header">
        <ng-content select="[card-title]" />
      </div>
      <div class="card-body">
        <ng-content />
      </div>
    </div>
  `,
})
export class CardComponent {}

Parent:

HTML
<app-card>
  <h2 card-title>User Profile</h2>
  <p>This goes into the card body.</p>
</app-card>

ViewChild — Access Child Components in Code

TYPESCRIPT
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { ChartComponent } from './chart.component';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [ChartComponent],
  template: `<app-chart #chart />`,
})
export class DashboardComponent implements AfterViewInit {
  @ViewChild('chart') chart!: ChartComponent;

  ngAfterViewInit(): void {
    this.chart.refresh();   // call child method after view initializes
  }
}

Inline Templates and Styles

For small components, keep everything in one file:

TYPESCRIPT
@Component({
  selector: 'app-badge',
  standalone: true,
  template: `<span class="badge">{{ label }}</span>`,
  styles: [`
    .badge {
      background: #7c3aed;
      color: white;
      padding: 2px 8px;
      border-radius: 4px;
      font-size: 12px;
    }
  `],
})
export class BadgeComponent {
  @Input() label = '';
}

Quick Reference

Create component:   ng g c component-name
Interpolation:      {{ value }}
Property binding:   [property]="value"
Event binding:      (event)="handler()"
Two-way binding:    [(ngModel)]="value" (needs FormsModule)
Pass data down:     @Input() prop!: Type
Emit events up:     @Output() event = new EventEmitter()
Template ref:       #refName → use in template or @ViewChild
Content slot:        in child, projected from parent
Key lifecycle:      ngOnInit (fetch), ngOnDestroy (unsubscribe)

Enjoyed this article?

Explore the learning path for more.

Found this helpful?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.