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.