@ngneat/overview
v8.1.0
Published
The Template for Success in Template Management
Maintainers
Readme
Overview - The Template for Success in Template Management
Compatibility with Angular Versions
Installation
npm install @ngneat/overview
yarn add @ngneat/overview
pnpm install @ngneat/overviewTable of Contents
DynamicView
Use the dynamicView structural directive to render a component, a template, HTML, or default template dynamically.
Let’s say we build a generic error component. What we want is to give our consumers the flexibly to create it by using one of three options:
- They can choose to use the default template
- They can choose to use their own text which can be static or dynamic
- They can choose to pass their own template or component
import { DynamicViewDirective, Content } from '@ngneat/overview';
@Component({
template: ` <div *dynamicView="view">Default view</div> `,
standalone: true,
imports: [DynamicViewDirective],
})
export class ErrorComponent {
@Input() view: Content | undefined;
}You can also pass a context or an injector as inputs to the directive:
<h5>Component</h5>
<ng-container
*dynamicView="component;
injector: myInjector;
context: { $implicit: 'my title' }"
/>
<h5>Template</h5>
<ng-template #tpl let-title>
<b>{{ title }}</b>
</ng-template>
<ng-container *dynamicView="tpl; context: { $implicit: 'my title' }" />If you pass context to a component and the value can be accessed via the injectViewContext function:
import { injectViewContext } from '@ngneat/overview';
interface Context {
title: string;
}
@Component({
template: `<div>{{ context().title }}</div>`,
standalone: true,
})
export class MyDynamicComponent {
context: Signal<Context> = injectViewContext<Context>();
}injectViewContext returns a readonly signal with the view's context object.
Teleporting
Teleporting means rendering a view at a different location from its declaration. There are two cases it might be helpful:
- Avoid prop drilling to a nested component.
- Rendering a view at another place in the DOM while keeping its context where it’s defined.
You can read more about this approach in this article.
Use the teleportOutlet directive to define a new outlet. An outlet is an anchor where the view will be projected as a sibling.
import { TeleportOutletDirective } from '@ngneat/overview';
@Component({
template: `
<div class="flex">
<ng-container teleportOutlet="someId"/>
</div>
`,
standalone: true,
imports: [TeleportOutletDirective],
})
export class FooComponent {}Use the teleportTo directive to teleport the view to a specific outlet:
import { TeleportDirective } from '@ngneat/overview';
@Component({
template: `
<section *teleportTo="someId">
{{ value }}
</section>
`,
standalone: true,
imports: [TeleportDirective],
})
export class BarComponent {
value = '...';
}Testing Teleported Components
When the outlet and the teleporting content live in different components (the common real-world case), the trick is to declare both components in the same test and render the parent that hosts the outlet.
import { Component } from '@angular/core';
import { provideZonelessChangeDetection } from '@angular/core';
import { createComponentFactory } from '@ngneat/spectator';
import { TeleportDirective, TeleportOutletDirective } from '@ngneat/overview';
// The shell that owns the outlet — mirrors your real AppComponent / layout.
@Component({
selector: 'app-shell',
template: `
<header>
<ng-container teleportOutlet="app-title"></ng-container>
</header>
<ng-content />
`,
imports: [TeleportOutletDirective],
})
class ShellComponent {}
// The page that teleports content into the outlet.
@Component({
template: `
<app-shell>
<h1 *teleportTo="'app-title'">My Page</h1>
<p>Page body</p>
</app-shell>
`,
imports: [ShellComponent, TeleportDirective],
})
class PageComponent {}
describe('PageComponent', () => {
const createComponent = createComponentFactory({
component: PageComponent,
providers: [provideZonelessChangeDetection()],
});
it('should teleport the title into the shell header', () => {
const spectator = createComponent();
expect(spectator.query('header')).toHaveText('My Page');
});
});The key points:
- Render the page component, not the shell — the page is the unit under test.
- Import the shell inside the page's
importsarray so Angular compiles both together. Both theteleportOutletand*teleportTodirectives are active in the same test. - Both outlets register before the
*teleportTosubscribes. This is safe — the service checks current ports on subscription, so no events are missed regardless of registration order.
ViewService
The ViewService provides facade methods to create modular views in Angular. It's been used in various projects like hot-toast, and helipopper.
createComponent
The createComponent method takes a Component, and returns an instance of CompRef:
import { ViewService, CompRef } from '@ngneat/overview';
@Injectable()
class ToastService {
private viewService = inject(ViewService);
componentRef: CompRef;
init() {
this.componentRef = this.viewService
.createComponent(HotToastContainerComponent)
.setInput('defaultConfig', defaultConfig)
.appendTo(document.body);
}
}There are cases where we want to use an Angular component template in a third-party library that takes a native DOM element or a string. In this case, we can use the getRawContent or the getElement method, respectively.
import { ViewService } from '@ngneat/overview';
@Directive()
class ChartDirective {
private viewService = inject(ViewService);
createChart(color: string) {
const ref = this.viewService.createComponent(FooTooltip).setInput('color', color).detectChanges(document.body);
const content = ref.getRawContent();
ref.destroy();
Highcharts.chart('container', {
tooltip: {
formatter: function () {
return content;
},
useHTML: true,
},
});
}
}createComponent Options
createComponent<Comp, Context>(component: Type<Comp>, {
injector?: Injector,
environmentInjector?: EnvironmentInjector,
context?: Context,
vcr?: ViewContainerRef,
})createTemplate
The createTemplate method takes a TemplateRef, and returns an instance of ViewRef.
createTemplate<Context>(tpl: TemplateRef<Context>, {
context?: Context,
vcr?: ViewContainerRef,
injector?: Injector,
})createView
The createView method takes a Component, a TemplateRef or a string, and creates a View:
import { ViewService, Content } from '@ngneat/overview';
@Injectable()
class ToastService {
private viewService = inject(ViewService);
createToast(content: Content) {
const ref = this.viewService.createView(content);
document.body.appendChild(ref.getElement());
}
}Contributors ✨
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!
