Directives, ng-container, and pipes are three distinct Angular template features that are easy to conflate when first learning the framework — each solves a genuinely different problem.
What a directive actually is
A directive is a class that attaches behavior to a DOM element — Angular ships with built-in structural directives (*ngIf, *ngFor, which add or remove elements from the DOM) and attribute directives (ngClass, ngStyle, which change an existing element's appearance or behavior without adding/removing it).
Creating a custom attribute directive
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(private el: ElementRef) {}
@HostListener('mouseenter') onMouseEnter() {
this.el.nativeElement.style.backgroundColor = 'yellow';
}
@HostListener('mouseleave') onMouseLeave() {
this.el.nativeElement.style.backgroundColor = '';
}
}
Hover over this text
This directive attaches hover-highlight behavior to any element carrying the appHighlight attribute — reusable across as many elements as needed without duplicating the mouseenter/mouseleave logic on each one.
What ng-container is for
{{ user.name }}
{{ user.email }}
Angular doesn't allow two structural directives (like A pipe transforms a value for display in the template without changing the underlying data — A custom pipe follows the same pattern as the built-in ones — implement A directive changes behavior or structure, ng-container groups multiple elements under a single structural directive (like *ngIf) without rendering an actual wrapping DOM element — using a ng-container avoids, which matters when that extra wrapper would break CSS flex/grid layout or table structure.
Using ng-container to combine two structural directives
*ngFor and *ngIf) on the same element directly — nesting them across an ng-container and the actual element is the standard workaround.What a pipe does
{{ user.createdAt | date:'mediumDate' }}
{{ price | currency:'USD' }}
{{ description | uppercase }}date, currency, and uppercase are all built in.Creating a custom pipe
@Pipe({ name: 'truncate' })
export class TruncatePipe implements PipeTransform {
transform(value: string, limit: number = 50): string {
return value.length > limit ? value.substring(0, limit) + '...' : value;
}
}{{ post.body | truncate:100 }}PipeTransform, register it in a module (or mark it standalone: true in newer Angular versions), and use it in a template with the same pipe (|) syntax.How these three fit together
ng-container is a structural tool for organizing directives without adding markup, and a pipe transforms a displayed value — a single template commonly uses all three together, as in the ng-container example above combining a structural directive with a value that could just as easily be piped for formatting.