1. Component selector types: element, attribute, and class
By default, Angular components use an element selector (e.g. <app-button>), which adds a new custom HTML element to the DOM. But Angular also supports attribute and class selectors, which let you attach a component to an existing HTML element instead of creating a new one.
Why use attribute selectors?
When you want to enhance a native HTML element (like <button>) without wrapping it in an extra DOM node, use an attribute selector. This keeps the DOM clean and preserves native element semantics (accessibility, styling, event behavior).
Attribute selector example
@Component({
selector: 'button[appButton]',
templateUrl: './button.html',
styleUrl: './button.css',
})
export class ButtonComponent {}
The selector button[appButton] matches any <button> element that also has the appButton attribute. Angular will attach this component to that element instead of creating a new DOM node.
Usage in a template:
<button appButton>Click me</button>
Class selector example
You can also target elements by CSS class:
@Component({
selector: '.app-highlight',
templateUrl: './highlight.html',
})
export class HighlightComponent {}
<div class="app-highlight">Highlighted content</div>
Summary
| Selector type | Syntax | Use case |
|---|---|---|
| Element | app-button |
New custom element in the DOM |
| Attribute | button[appButton] |
Enhance an existing HTML element |
| Class | .app-highlight |
Enhance an element by CSS class |
2. Content projection with ng-content
ng-content lets a component render content passed in from its parent, rather than defining all its markup internally. This is Angular's equivalent of a "slot" in web components.
Basic usage
In the component template, place <ng-content /> where the projected content should appear:
<!-- button.html -->
<ng-content />
<!-- parent template -->
<button appButton>Click me</button>
"Click me" is projected into the slot defined by <ng-content />.
Multiple slots with selectors
When you need more than one slot, add a select attribute with a CSS selector to each <ng-content>. The element in the parent that matches the selector is projected into the corresponding slot. Content that matches no selector falls into the default (unselectored) slot.
<!-- button.html -->
<ng-content /> <!-- default slot -->
<ng-content select=".label" /> <!-- matches elements with class "label" -->
<ng-content select="[icon]" /> <!-- matches elements with attribute "icon" -->
<!-- parent template -->
<button appButton>
Default text
<span class="label">Submit</span>
<mat-icon icon>send</mat-icon>
</button>
Each child is routed to its matching slot independently. Unmatched content goes to the default slot.
Multi-element selectors
A single select attribute can match multiple element types by using a comma-separated CSS selector, the same way you would in a stylesheet.
<!-- custom-form.html -->
<ng-content select="[label]" />
<ng-content select="input, textarea" />
<!-- parent template -->
<app-custom-form>
<span label>Title</span>
<input name="title" />
</app-custom-form>
Here, the second slot accepts only <input> or <textarea> elements — any other content passed in would fall through to the default slot (or be discarded if there is none).
Key rules
- A component can have at most one default
<ng-content />(noselect). -
selectaccepts any valid CSS selector: class (.foo), attribute ([bar]), element (span), or combinations. - Projected content is owned by the parent component — its styles and change detection context belong to the parent, not the host component.
3. View encapsulation
By default, Angular scopes a component's styles so they only apply to that component's own template. This behavior is controlled by the encapsulation option in @Component.
| Mode | Behavior |
|---|---|
ViewEncapsulation.Emulated |
(default) Styles are scoped to the component via generated attribute selectors. |
ViewEncapsulation.None |
Styles are added to the global stylesheet — they affect the entire application. |
ViewEncapsulation.ShadowDom |
Uses the browser's native Shadow DOM to isolate styles. |
When to use ViewEncapsulation.None
Useful when your component attaches to a native element (e.g. button[appButton]) rather than introducing its own host element. Because the component has no wrapping element of its own, Angular's emulated scoping has nothing to attach its generated attributes to, and the styles may not apply correctly. Disabling encapsulation lets those styles reach the element directly.
@Component({
selector: 'button[appButton]',
templateUrl: './button.html',
styleUrl: './button.css',
encapsulation: ViewEncapsulation.None,
})
export class ButtonComponent {}
Note:
ViewEncapsulation.Nonestyles are global — name them carefully to avoid unintended side effects elsewhere in the app.
4. The :host selector
:host is a CSS pseudo-class that targets the host element of a component — the element the component is attached to — rather than anything inside its template.
Example — styling the host element of a regular component
Consider a CardComponent with an element selector:
@Component({
selector: 'app-card',
templateUrl: './card.html',
styleUrl: './card.css',
})
export class CardComponent {}
<!-- card.html -->
<h2>{{ title }}</h2>
<p><ng-content /></p>
/* card.css */
:host {
display: block;
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
}
:host(.elevated) {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
<!-- parent template -->
<app-card>Standard card</app-card>
<app-card class="elevated">Elevated card</app-card>
:host targets the <app-card> element in the DOM. Without it, you could not style that element from inside the component's own stylesheet — you would have to rely on the parent doing it. :host(.elevated) picks up the elevated class placed on <app-card> by the parent and applies extra styles.
Why use :host instead of a plain element selector?
A plain button { ... } rule inside an encapsulated component would be scoped and might not reach the host element. :host is the correct, encapsulation-aware way to style the component's own root element.
Note: When
encapsulation: ViewEncapsulation.Noneis set,:hostloses its scoping and behaves like a plain element selector — the styles apply globally. This is rarely useful; if you need global styles, add them to the global stylesheet directly.
5. host property and the legacy @HostBinding() / @HostListener()
The host property in @Component (and @Directive) lets you bind attributes, properties, and events directly to the host element — the element the component is attached to. It replaces the older @HostBinding() and @HostListener() decorators that served the same purpose in earlier Angular versions. The host property is the preferred approach from Angular 15 onwards.
Binding types
There are three kinds of host bindings, each with a different value expectation:
Class bindings — class.<name> → boolean
Adds or removes a CSS class on the host element. The bound value must be truthy to add the class, falsy to remove it.
| Syntax | @HostBinding |
host property |
|---|---|---|
Add active class |
@HostBinding('class.active') isActive = false; |
'[class.active]': 'isActive' |
// class is present when isActive is true, absent when false
isActive = true; // → <app-card class="active">
isActive = false; // → <app-card>
Style bindings — style.<property> → CSS value
Sets an inline style on the host element. The bound value must be a valid CSS value string (or null/undefined to remove it).
| Syntax | @HostBinding |
host property |
|---|---|---|
Set border-color
|
@HostBinding('style.border-color') borderColor = 'red'; |
'[style.border-color]': 'borderColor' |
// style is applied as-is
borderColor = 'red'; // → <app-card style="border-color: red;">
borderColor = null; // → style removed
Property bindings — [propName] → any
Sets a DOM property directly on the host element. Common examples: disabled, title, tabIndex. These are DOM properties, not HTML attributes.
| Syntax | @HostBinding |
host property |
|---|---|---|
Set disabled
|
@HostBinding('disabled') isDisabled = false; |
'[disabled]': 'isDisabled' |
Set title
|
@HostBinding('title') label = 'Card'; |
'[title]': 'label' |
isDisabled = true; // → <app-card disabled>
label = 'Info'; // → <app-card title="Info">
Full example — legacy vs. modern
// Legacy
@Component({ selector: 'app-card', templateUrl: './card.html' })
export class CardComponent {
@HostBinding('class.active') isActive = false;
@HostBinding('style.border-color') borderColor = 'transparent';
@HostBinding('disabled') isDisabled = false;
@HostBinding('title') label = 'Card';
@HostListener('click')
onClick() { this.isActive = !this.isActive; }
}
// Modern (Angular 15+)
@Component({
selector: 'app-card',
templateUrl: './card.html',
host: {
'[class.active]': 'isActive',
'[style.border-color]': 'borderColor',
'[disabled]': 'isDisabled',
'[title]': 'label',
'(click)': 'onClick()',
},
})
export class CardComponent {
isActive = false;
borderColor = 'transparent';
isDisabled = false;
label = 'Card';
onClick() { this.isActive = !this.isActive; }
}
All bindings target the <app-card> host element, not anything inside the template.
Why prefer host over the decorators?
- All host bindings are co-located with the rest of the component metadata, making them easier to find.
- Decorators scatter host-related logic across class properties;
hostkeeps it in one place. - The
hostproperty works better with signal-based components and upcoming Angular APIs.
Top comments (0)