DEV Community

Atilla Baspinar
Atilla Baspinar

Posted on

Component Selectors, Content Projection, and Host Bindings


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 {}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Class selector example

You can also target elements by CSS class:

@Component({
  selector: '.app-highlight',
  templateUrl: './highlight.html',
})
export class HighlightComponent {}
Enter fullscreen mode Exit fullscreen mode
<div class="app-highlight">Highlighted content</div>
Enter fullscreen mode Exit fullscreen mode

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 />
Enter fullscreen mode Exit fullscreen mode
<!-- parent template -->
<button appButton>Click me</button>
Enter fullscreen mode Exit fullscreen mode

"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" -->
Enter fullscreen mode Exit fullscreen mode
<!-- parent template -->
<button appButton>
  Default text
  <span class="label">Submit</span>
  <mat-icon icon>send</mat-icon>
</button>
Enter fullscreen mode Exit fullscreen mode

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" />
Enter fullscreen mode Exit fullscreen mode
<!-- parent template -->
<app-custom-form>
  <span label>Title</span>
  <input name="title" />
</app-custom-form>
Enter fullscreen mode Exit fullscreen mode

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 /> (no select).
  • select accepts 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 {}
Enter fullscreen mode Exit fullscreen mode

Note: ViewEncapsulation.None styles 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 {}
Enter fullscreen mode Exit fullscreen mode
<!-- card.html -->
<h2>{{ title }}</h2>
<p><ng-content /></p>
Enter fullscreen mode Exit fullscreen mode
/* 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);
}
Enter fullscreen mode Exit fullscreen mode
<!-- parent template -->
<app-card>Standard card</app-card>
<app-card class="elevated">Elevated card</app-card>
Enter fullscreen mode Exit fullscreen mode

: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.None is set, :host loses 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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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">
Enter fullscreen mode Exit fullscreen mode

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; }
}
Enter fullscreen mode Exit fullscreen mode
// 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; }
}
Enter fullscreen mode Exit fullscreen mode

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; host keeps it in one place.
  • The host property works better with signal-based components and upcoming Angular APIs.

Top comments (0)