<p align="center">
 <img width="20%" height="20%" src="./logo.svg">
</p>

<br />

[![npm](https://img.shields.io/npm/v/@ngneat/overview?style=flat-square)](https://www.npmjs.com/package/@ngneat/overview)
[![MIT](https://img.shields.io/packagist/l/doctrine/orm.svg?style=flat-square)](https://github.com/ngneat/overview/blob/main/LICENSE)
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
[![PRs](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/ngneat/overview/pulls)
[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
[![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-)
[![ngneat](https://img.shields.io/badge/@-ngneat-383636?style=flat-square&labelColor=8f68d4)](https://github.com/ngneat/)
[![spectator](https://img.shields.io/badge/tested%20with-spectator-2196F3.svg?style=flat-square)](https://github.com/ngneat/spectator)

> Overview - The Template for Success in Template Management

## Compatibility with Angular Versions

<table>
  <thead>
    <tr>
      <th>@ngneat/overview</th>
      <th>Angular</th>
    </tr>
  </thead>
  <tbody>
      <tr>
      <td>
        6.x
      </td>
      <td>
        >= 17
      </td>
    </tr>
    <tr>
      <td>
        5.x
      </td>
      <td>
        >= 16
      </td>
    </tr>
    <tr>
      <td>
        4.x
      </td>
      <td>
        >= 14 < 16
      </td>
    </tr>
    <tr>
      <td>
        3.x
      </td>
      <td>
        >= 13 < 14
      </td>
    </tr>
    <tr>
      <td>
        2.x
      </td>
      <td>
        >= 11 < 13
      </td>
    </tr>
  </tbody>
</table>

## Installation

```bash
npm install @ngneat/overview
yarn add @ngneat/overview
pnpm install @ngneat/overview
```

## Table of Contents

- [DynamicView](#DynamicContent)
- [Teleporting](#Teleporting)
- [ViewService](#ViewService)
  - [createView](#createView)
  - [createComponent](#createComponent)
  - [createTemplate](#createTemplate)

## `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

```ts
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`](https://angular.io/api/core/Injector) as `inputs` to the directive:

```html
<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:

```ts
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](https://netbasal.com/beam-me-up-scotty-on-teleporting-templates-in-angular-a924f1a7798).

Use the `teleportOutlet` directive to define a new `outlet`. An `outlet` is an anchor where the view will be projected as a sibling.

```typescript
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`:

```typescript
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.

```ts
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 `imports` array so Angular compiles both together. Both the `teleportOutlet` and `*teleportTo` directives are active in the same test.
- Both outlets register before the `*teleportTo` subscribes. 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](https://github.com/ngneat/hot-toast), and [helipopper](https://github.com/ngneat/helipopper).

### `createComponent`

The `createComponent` method takes a `Component`, and returns an instance of `CompRef`:

```ts
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](https://netbasal.com/using-angular-components-with-third-party-libraries-522a1f33003) 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.

```ts
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

```ts
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`.

```ts
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`:

```ts
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](https://allcontributors.org/docs/en/emoji-key)):

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
  <tr>
    <td align="center"><a href="https://www.netbasal.com/"><img src="https://avatars.githubusercontent.com/u/6745730?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Netanel Basal</b></sub></a><br /><a href="https://github.com/@ngneat/overview/commits?author=NetanelBasal" title="Code">💻</a> <a href="#ideas-NetanelBasal" title="Ideas, Planning, & Feedback">🤔</a></td>
    <td align="center"><a href="https://github.com/shhdharmen"><img src="https://avatars.githubusercontent.com/u/6831283?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dharmen Shah</b></sub></a><br /><a href="#content-shhdharmen" title="Content">🖋</a> <a href="https://github.com/@ngneat/overview/commits?author=shhdharmen" title="Documentation">📖</a></td>
  </tr>
</table>

<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

<div>Icons made by <a href="http://www.freepik.com/" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a></div>
