Angular Selectorless Components (Preview): Import Classes Directly into Templates [2026]
Selectorless components are the authoring style the Angular team is building next — a template syntax that drops the selector string and the imports array, letting you reference a component class directly in another template. The Angular 22 compiler already contains an implementation, but it is switched off behind a private compiler option and is not a documented, supported feature yet. Existing selector-based components are unaffected.
This is lesson 2.3 of the Angular Tutorial. It builds on lesson 2.1 (component anatomy) and lesson 2.2 (template syntax). Treat this lesson as a preview of where templates are heading: the syntax and the way it is enabled can still change before it ships publicly, so do not use it in production code.
The selector indirection problem #
Classic Angular ties a component class to a template tag via a string selector:
// child.component.ts
@Component({
selector: 'app-child',
template: `<p>Hello!</p>`,
})
export class ChildComponent {}
// parent.component.ts
@Component({
selector: 'app-parent',
imports: [ChildComponent], // ← required: tell the template compiler about ChildComponent
template: `<app-child />`, // ← reference by selector string
})
export class ParentComponent {}
Three places repeat the same information:
selector: 'app-child'— the string the template usesimports: [ChildComponent]— the class reference for the compiler<app-child />— the string in the parent template
The selector string is the indirection — a runtime name that the compiler matches against imports. It is what makes Angular components feel different from regular TypeScript classes, and it is what selectorless components remove.
The selectorless shape #
The proposed syntax drops the selector and imports:
// child.component.ts
@Component({
template: `<p>Hello!</p>`, // no selector
})
export class Child {} // class name IS the tag name
// parent.component.ts
import { Child } from './child.component';
@Component({
template: `<Child />`, // reference the class directly
})
export class Parent {} // no imports array needed
Four things change:
- No
selectorfield. The component is anonymous to Angular's selector matcher. - No
importsarray. The compiler resolves the class from your file's TypeScript imports; the v22 compiler even reports an error if a selectorless template also declaresimports. - The class name is the tag name. Capitalized —
<Child />not<child />. Only names starting with an uppercase letter are treated as class references. - Standard TypeScript import.
import { Child } from './child.component'.
This is the same mental model as JSX components in React, Solid, and Preact: "the class is the tag."
Why it matters #
Three concrete benefits:
| Benefit | Impact |
|---|---|
No imports boilerplate |
A standalone component with five child components currently has a five-line imports array. Selectorless drops it entirely. |
| Real go-to-definition | IDE click-through on <Child /> lands in the class file. Selector-based <app-child /> requires the IDE's Angular language service to resolve. |
| Tree-shaking is fully type-driven | Imports the bundler sees are exactly the imports the template uses. No "this component is in imports: but unused in the template, the compiler doesn't know" cases. |
| Cleaner ecosystem semantics | Library authors export classes; consumers import them and use them. Same model as every other modern framework. |
The standalone API (stable since v15) moved Angular halfway to this. Selectorless is the second half.
Inputs and outputs work identically #
The selectorless syntax does not change how input() and output() work:
import { Component, input, output } from '@angular/core';
@Component({
template: `
<button (click)="clicked.emit()">{{ label() }} ({{ count() }})</button>
`,
})
export class Counter {
label = input.required<string>();
count = input(0);
clicked = output<void>();
}
import { Counter } from './counter';
@Component({
template: `
<Counter label="Total" [count]="current()" (clicked)="onClick()" />
`,
})
export class Parent { /* ... */ }
Attribute syntax (label="Total"), property binding ([count]="current()"), event binding ((clicked)="onClick()") — all unchanged. Only the tag name and import mechanism differ.
Coexisting with selector-based components #
The two styles are decided per template: as soon as a template uses class-name tags, it is a selectorless template and follows the selectorless rules:
// Selector-based: selector string + imports array
@Component({
selector: 'app-old-card',
imports: [BadgeComponent],
template: `<app-badge text="NEW" />`,
})
export class OldCardComponent {}
// Selectorless template: class reference, no imports array
import { OldCardComponent } from './old-card';
@Component({
template: `<OldCardComponent />`,
})
export class NewPage {}
The matrix:
| Consumer | Imports | Can use |
|---|---|---|
| Selectorless template | TypeScript import (an imports field is a compiler error) |
Components and directives referenced by class name |
| Selector-based template | imports: [...] array |
Components and directives matched by selector |
Exactly how the two styles will interoperate in larger codebases is one of the details still being settled. The selector-based syntax is not deprecated, and nothing in v22 asks you to migrate.
Enabling selectorless #
As of v22 there is no public switch. The compiler reads a private option, _enableSelectorless (default false):
// NOT a supported option: internal, undocumented, may change in any release
{
"angularCompilerOptions": {
"_enableSelectorless": true
}
}
The leading underscore marks it as internal. With it on, the template compiler accepts <ClassName /> tags and @ClassName directive syntax for imported classes; with it off (the default) the syntax is not recognised at all. Use it only to experiment in a throwaway project.
When the Angular team ships the feature publicly, expect an official option or an RFC-driven default change. Selector-based code is expected to keep working either way.
What this means for libraries #
Library authors should still export their components with selectors for the foreseeable future. Selector-based consumption works in every Angular version since v2; selectorless consumption is not publicly available yet. Until it ships and the ecosystem adopts it, selectors stay.
Once it does ship, new internal-to-your-app components will be the obvious candidates for selectorless — no public API contract, full control over the Angular version.
Directives in selectorless code #
Directives (lesson 2.9) attach to existing elements, so the preview gives them their own syntax — the class name with an @ prefix:
// Directive: still declared with @Directive
@Directive({
selector: '[hoverGlow]',
})
export class HoverGlow {}
// Selectorless template applies the directive by class
import { HoverGlow } from './hover-glow';
@Component({
template: `<button @HoverGlow>Click</button>`, // no imports array
})
export class Card { /* ... */ }
The v22 compiler already parses this @Directive form and has a dedicated error for bindings that don't match the directive's inputs. Because the template is selectorless, the imports array is not allowed here either.
Comparison table — what changes #
| Aspect | Selector-based (all versions) | Selectorless (preview) |
|---|---|---|
| Component declaration | selector: 'app-child' |
Omit selector |
| Template tag | <app-child /> |
<Child /> (class name) |
imports array |
Required | Not allowed (compiler error) |
| Directives | Use imports array |
Applied by class with @Name syntax |
| Pipes | Use imports array |
Not settled yet |
Bindings ([x], (y)) |
Identical | Identical |
| Type-checking | Selector-string lookup | Real TypeScript class reference |
| Bundler tree-shaking | Depends on imports analysis |
Depends on TypeScript imports |
The model converges on standard TypeScript: import a class, use it — for components and directives alike.
When to use which (during v22 migration) #
A practical recommendation:
- Existing components — leave them on selectors. There is nothing to migrate to until the feature is public.
- New internal components — keep using selectors for now; try selectorless in a scratch project to get a feel for it.
- Components you ship as a library — selectors only, for the foreseeable future. Cross-version compatibility matters.
- Web Components (Angular Elements) — must use selectors (they become custom element names).
Common gotchas #
| Symptom | Cause | Fix |
|---|---|---|
<Child /> is not recognized in the template |
Class is not imported in TypeScript | Add import { Child } from './child' at the top of the file |
<Child /> lowercase fails |
Element-name detection treats lowercase as native HTML | Capitalize class names (already the TypeScript convention) |
Cannot use the "imports" field in a selectorless component |
The template uses class-name tags, so the imports field is not allowed |
Drop the imports field; the TypeScript import is enough |
| Directive does nothing | Applied as a plain attribute instead of with @ syntax |
Write @HoverGlow on the element |
<Child /> is treated as an unknown element |
The private _enableSelectorless option is off (the default) |
Expected in v22 — the feature is not public; use selectors |
What's next #
The second half of Module 2 covers content projection (2.6), lifecycle hooks (2.7), pipes (2.8), directives (2.9), <ng-template> and <ng-container> (2.10), styling (2.11), host metadata (2.12), signal queries (2.13), and programmatic component creation (2.14). By the end of Module 2 you can write components in every shape Angular supports, selectorless or not.
Module 3 then dives deep into the signal system.
Try it yourself #
A tiny app entirely in selectorless style:
// counter.ts
import { Component, signal } from '@angular/core';
@Component({
template: `<button (click)="count.update(n => n+1)">Count: {{ count() }}</button>`,
})
export class Counter {
count = signal(0);
}
// app.ts
import { Component } from '@angular/core';
import { Counter } from './counter';
import { bootstrapApplication } from '@angular/platform-browser';
@Component({
template: `
<h1>Selectorless demo</h1>
<Counter />
<Counter />
<Counter />
`,
})
export class App {}
bootstrapApplication(App);
Three counters, three independent instances. No selector, no imports. It only compiles with the private _enableSelectorless option switched on, so try it in a scratch project, never in production.
get_best_practicesNo — your tests use TestBed.createComponent(Counter) with the class reference, not the selector string. The runtime API is unchanged. The only place a selector string appears is in templates that other components use to reference yours; tests instantiate components by class. If you have integration tests that query by tag name (By.css('app-counter')), switch them to By.directive(Counter) so they stay selector-agnostic. And remember that in v22 selectorless is still behind a private, unsupported compiler flag.Lesson 2.4 already covered input signals; the rest of Module 2 picks up content projection (2.6), lifecycle (2.7), and the templating primitives in 2.8–2.14.
Up next in Angular
More from this topic
Enjoyed this article?
Get new Angular tutorials delivered. No spam — just code-first articles when they ship.
1 comment



Pingback: Angular Template Syntax: @if, @for, Bindings [2026]