Skip to main content

Angular Component Basics

Angular components serve as UI building blocks similar to React components, but with significantly different structure and mechanisms.

1. Basic Component Structure

An Angular component is a TypeScript class decorated with @Component.

@Component Decorator

src/app/hello/hello.component.ts
import { Component } from '@angular/core';

@Component({
selector: 'app-hello', // HTML tag name for this component
templateUrl: './hello.component.html', // Template file
styleUrl: './hello.component.scss', // Style file
})
export class HelloComponent {
name = 'Angular';
}
PropertyDescription
selectorThe HTML tag name used in templates (e.g., <app-hello>)
templateUrlPath to the HTML template file
templateInline HTML (mutually exclusive with templateUrl)
styleUrlPath to the style file (Angular 17+, singular form)
styleUrlsArray of style file paths
stylesArray of inline styles
standaloneWhen true, usable without NgModule (default since Angular 17)

File Structure: 3-file Split vs JSX All-in-One

By default, Angular splits one component into 3 files.

hello/
├── hello.component.ts # Logic (TypeScript)
├── hello.component.html # Template (HTML)
└── hello.component.scss # Styles (SCSS)

React typically uses a single file (JSX), but Angular's separation makes the concerns more explicit.

Comparison with React
// React: All-in-one JSX
export function Hello() {
const name = 'React';
return <h1>Hello, {name}!</h1>;
}
<!-- Angular template (hello.component.html) -->
<h1>Hello, {{ name }}!</h1>

Generating Components with ng generate

# Generate a component (creates 3 files + spec file)
ng generate component hello
# Or shorthand
ng g c hello

# Generate in a subdirectory
ng g c features/user-profile

# Generate with inline template and inline style
ng g c hello --inline-template --inline-style

# Generate without spec file
ng g c hello --skip-tests

2. Templates and Inline Templates

There are two ways to define a template: external file or inline.

templateUrl (External File)

@Component({
selector: 'app-user-card',
templateUrl: './user-card.component.html', // Reference external HTML file
styleUrl: './user-card.component.scss',
})
export class UserCardComponent { }

Keeping the HTML in a separate file gives better editor support and is recommended for larger templates.

template (Inline)

@Component({
selector: 'app-badge',
template: `
<span class="badge">{{ label }}</span>
`,
styles: [`
.badge { padding: 4px 8px; border-radius: 4px; }
`],
})
export class BadgeComponent {
label = 'New';
}

Inline templates are convenient for small, simple components.

Template Syntax vs React JSX
FeatureAngular TemplateReact JSX
Variable interpolation{{ value }}{value}
Conditional rendering@if (cond) { } / *ngIf{cond && <...>}
List rendering@for (item of items; track item.id) { } / *ngFor{items.map(...)}
Class binding[class.active]="isActive"className={isActive ? 'active' : ''}

3. The Four Forms of Data Binding

Angular has four types of data binding.

3-1. Interpolation: {{ }}

Displays values in the template.

@Component({
selector: 'app-greeting',
template: `
<p>Hello, {{ username }}!</p>
<p>Today is {{ today | date:'yyyy/MM/dd' }}</p>
<p>Total: {{ price * quantity }}</p>
`,
})
export class GreetingComponent {
username = 'Taro';
today = new Date();
price = 1000;
quantity = 3;
}

3-2. Property Binding: [property]="value"

Binds a value to a DOM element or component property (TypeScript → HTML).

@Component({
selector: 'app-image',
template: `
<!-- Binding to DOM properties -->
<img [src]="imageUrl" [alt]="imageAlt" />
<button [disabled]="isLoading">Submit</button>
<input [value]="inputValue" />

<!-- Special class and style bindings -->
<div [class.highlight]="isSelected">Selected</div>
<p [style.color]="textColor">Colored text</p>
`,
})
export class ImageComponent {
imageUrl = 'https://example.com/photo.jpg';
imageAlt = 'Sample image';
isLoading = false;
inputValue = 'initial value';
isSelected = true;
textColor = 'blue';
}
Comparison with React
// React: Write expressions directly in JSX attributes
<img src={imageUrl} alt={imageAlt} />
<button disabled={isLoading}>Submit</button>

Angular's [src] is equivalent to React's src={...}.

3-3. Event Binding: (event)="handler()"

Calls methods in response to user actions (HTML → TypeScript).

@Component({
selector: 'app-counter',
template: `
<p>Count: {{ count }}</p>
<button (click)="increment()">+1</button>
<button (click)="decrement()">-1</button>
<input (input)="onInput($event)" (keydown.enter)="submit()" />
`,
})
export class CounterComponent {
count = 0;

increment() {
this.count++;
}

decrement() {
this.count--;
}

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

submit() {
console.log('Submitted');
}
}
Comparison with React
// React: Pass handlers via onXxx attributes
<button onClick={increment}>+1</button>
<input onChange={e => console.log(e.target.value)} />

Angular's (click) is equivalent to React's onClick.

3-4. Two-way Binding: [(ngModel)]="value"

Simultaneously reads and writes a value, ideal for form inputs.

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
selector: 'app-text-input',
imports: [FormsModule], // Required for standalone components
template: `
<input [(ngModel)]="message" />
<p>Input value: {{ message }}</p>
`,
})
export class TextInputComponent {
message = '';
}

[(ngModel)] is called the "banana in a box" syntax. It is syntactic sugar combining [ngModel] (property binding for display) and (ngModelChange) (event binding for updates).

Comparison with React
// React: Controlled component using useState + onChange
const [message, setMessage] = useState('');
<input value={message} onChange={e => setMessage(e.target.value)} />

4. Input and Output (@Input / @Output)

@Input: Passing Data from Parent to Child

Child component: user-card.component.ts
import { Component, Input } from '@angular/core';

@Component({
selector: 'app-user-card',
template: `
<div class="card">
<h3>{{ name }}</h3>
<p>{{ email }}</p>
</div>
`,
})
export class UserCardComponent {
@Input() name!: string;
@Input() email!: string;
@Input({ required: true }) userId!: number; // Required input (Angular 16+)
@Input() role = 'user'; // With default value
}
Parent component template
<app-user-card
[name]="user.name"
[email]="user.email"
[userId]="user.id"
role="admin"
/>

@Output: Sending Events from Child to Parent

Child component: confirm-dialog.component.ts
import { Component, Output, EventEmitter } from '@angular/core';

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

onConfirm() {
this.confirmed.emit();
}

onCancel() {
this.cancelled.emit();
}
}
Parent component template
<app-confirm-dialog
(confirmed)="deleteItem()"
(cancelled)="closeDialog()"
/>
Comparison with React
AngularReact
@Input() valueprops.value
@Input({ required: true })Required via TypeScript type
@Output() clicked = new EventEmitter()onClicked?: () => void prop
this.clicked.emit(data)props.onClicked(data)

Signal Input / Output (Angular 17.1+)

Starting with Angular 17.1, a new Signal-based API for inputs and outputs is available.

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

@Component({
selector: 'app-product-card',
template: `
<div>
<h3>{{ name() }}</h3>
<p>{{ price() }}</p>
<button (click)="buy()">Buy</button>
</div>
`,
})
export class ProductCardComponent {
// signal input (read as a Signal function call)
name = input.required<string>();
price = input<number>(0);

// output function (replacement for EventEmitter)
purchased = output<{ name: string; price: number }>();

buy() {
this.purchased.emit({ name: this.name(), price: this.price() });
}
}

Benefits of Signal Input:

  • No need for the ! non-null assertion
  • Treated as a Signal in templates, enabling optimized change detection
  • Supports reactive operations like computed()
import { Component, input, computed } from '@angular/core';

@Component({
selector: 'app-price-display',
template: `<p>Tax included: {{ priceWithTax() }}</p>`,
})
export class PriceDisplayComponent {
price = input<number>(0);
taxRate = input<number>(0.1);

// Derive a value from Signals using computed()
priceWithTax = computed(() => Math.floor(this.price() * (1 + this.taxRate())));
}

5. Content Projection

ng-content allows callers of a component to inject content into it, similar to React's children prop.

Basic Content Projection

card.component.ts
import { Component } from '@angular/core';

@Component({
selector: 'app-card',
template: `
<div class="card">
<ng-content></ng-content>
</div>
`,
styles: [`.card { border: 1px solid #ccc; padding: 16px; border-radius: 8px; }`],
})
export class CardComponent {}
Usage in parent template
<app-card>
<h2>Title</h2>
<p>This content is projected into ng-content.</p>
</app-card>

Multiple Slots (Named Content Projection)

You can distribute content to multiple slots using CSS selectors in the select attribute.

panel.component.ts
@Component({
selector: 'app-panel',
template: `
<div class="panel">
<div class="panel-header">
<ng-content select="[slot=header]"></ng-content>
</div>
<div class="panel-body">
<ng-content></ng-content>
</div>
<div class="panel-footer">
<ng-content select="[slot=footer]"></ng-content>
</div>
</div>
`,
})
export class PanelComponent {}
Usage in parent template
<app-panel>
<h2 slot="header">Panel Title</h2>
<p>Main body content</p>
<button slot="footer">Close</button>
</app-panel>
Comparison with React's children
// React: children prop
function Card({ children }: { children: React.ReactNode }) {
return <div className="card">{children}</div>;
}

// React: Multiple slots (passed as separate props)
function Panel({ header, children, footer }: PanelProps) {
return (
<div>
<div>{header}</div>
<div>{children}</div>
<div>{footer}</div>
</div>
);
}

6. Style Scoping

Angular component styles are scoped by default, meaning they do not affect other components. This behavior is controlled by ViewEncapsulation.

The Three ViewEncapsulation Modes

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

@Component({
selector: 'app-example',
template: `<p class="text">Sample</p>`,
styles: [`.text { color: red; }`],
encapsulation: ViewEncapsulation.Emulated, // Default
})
export class ExampleComponent {}
ModeDescriptionUse Case
ViewEncapsulation.Emulated (default)Angular adds unique attributes (e.g., _ngcontent-xxx) to scope CSSNormal development
ViewEncapsulation.ShadowDomUses the browser's native Shadow DOM for scopingWhen complete isolation is required
ViewEncapsulation.NoneNo scoping (styles treated as global CSS)When intentionally defining global styles

How Emulated Mode Works

<!-- Angular renders this: -->
<p _ngcontent-abc-c123 class="text">Sample</p>
/* Generated CSS (scoped) */
.text[_ngcontent-abc-c123] { color: red; }

:host Selector

To apply styles to the component's host element (the <app-example> tag itself), use the :host selector.

/* Apply styles to the component host element */
:host {
display: block;
padding: 16px;
}

:host(.active) {
background-color: #e0f7fa;
}
Comparison with React's CSS Modules / styled-components
AngularReact
ViewEncapsulation.Emulated (default)CSS Modules (import styles from './Component.module.css')
ViewEncapsulation.NoneGlobal CSS / createGlobalStyle (styled-components)
ViewEncapsulation.ShadowDomWeb Components + Shadow DOM
:hostDirect styling of the component root element

React requires explicitly choosing a tool (CSS Modules, styled-components, etc.), while Angular has scoping built in by default.