ng-polymorpheus

repository·main·Indexed 19 days ago

https://github.com/taiga-family/polymorpheus

A lightweight, dependency-free Angular library (v5.0.1) for creating polymorphic templates. It provides the `*polymorpheusOutlet` structural directive to render primitives, functions, templates, or components interchangeably while maintaining a shared, live context. Features include type-safe template contexts via the `polymorpheus` directive, context injection using `injectContext` or the `POLYMORPHEUS_CONTEXT` token, and a `PolymorpheusComponent` wrapper for dynamic component instantiation.

Tokens
4K
Snippets
16
Records
18
Agent score
64%

What's inside ng-polymorpheus

  1. What is Polymorpheus?

    main
    Polymorpheus is a lightweight (1 KB gzip), dependency-free Angular library designed for polymorphic templates. It provides a single structural directive, *polymorpheusOutlet, that abstracts various ways of customizing views in Angular. This allows a component to accept different types of content (primitives, functions, templates, or components) and provide them with a specific context.
  2. Use the `*polymorpheusOutlet` directive

    main

    The core of Polymorpheus is the *polymorpheusOutlet structural directive. It renders the provided content and exposes it via a local template variable. The content can be a primitive, a function, a template, or a component.

    Content types supported:

    • Primitives (e.g., number, string)
    • Functions that take context as an argument and return a primitive
    • Templates that are instantiated with the provided context
    • Components that receive the context via Dependency Injection (DI)

    Context: An optional object passed to the directive that allows the content to adapt to the current state/data provided by the host component.

    <ng-container *polymorpheusOutlet="content as text; context: context">
      {{text}}
    </ng-container>
  3. Access context in dynamic components

    main

    When using components as content within a Polymorpheus outlet, you can access the provided context using the injectContext helper or the POLYMORPHEUS_CONTEXT token.

    Note: The context object is live. Any changes made to the context object in the host component will automatically trigger updates in the dynamic component's view.

    Alternatively, you can use Angular input() signals. If the keys in the context object match the names of your inputs, they will be automatically synced with the context changes.

    import {injectContext} from '@taiga-ui/polymorpheus';
    
    @Component({
      template: '{{ context.active }}',
    })
    export class MyComponent {
      // The context object is live and updates the view automatically
      protected readonly context = injectContext<{active: boolean}>();
    }

    Or using inputs for automatic syncing:

    import {input} from '@angular/core';
    
    @Component({
      template: '{{ active() }}',
    })
    export class MyComponent {
      // Automatically updated by context.active changes
      protected readonly active = input(false);
    }
  4. Add type safety to template context

    main

    To ensure type safety when using templates with Polymorpheus, use the polymorpheus directive on your ng-template. This allows you to define the shape of the context (including the $implicit key) so that template variables are correctly typed.

    // In your component class
    readonly context!: { $implicit: number };
    <ng-template
      #template="polymorpheus"
      [polymorpheus="context"
      let-item
    >
      {{ item.toFixed(2) }} <!-- 'item' is correctly typed as 'number' -->
    </ng-template>
  5. Use the polymorpheusOutlet directive to render dynamic content

    main

    The polymorpheusOutlet structural directive is used to render polymorphic content (components, templates, or primitives) within an Angular template. It handles the lifecycle and context injection for the provided content.

    Inputs

    • polymorpheusOutlet: The content to be rendered. This can be a PolymorpheusComponent, a TemplateRef, a PolymorpheusTemplate (directive), or a primitive value.
    • polymorpheusOutletContext: The context object passed to the content. If the content is a primitive, it is wrapped in a PolymorpheusContext.

    Usage Scenarios

    1. Components: If polymorpheusOutlet is a PolymorpheusComponent, the directive instantiates the component and uses the provided context to set component inputs.
    2. Templates: If polymorpheusOutlet is a TemplateRef or a PolymorpheusTemplate (directive), the directive renders the template using the provided context.
    3. Primitives: If polymorpheusOutlet is a primitive, it is wrapped in a PolymorpheusContext and rendered via the surrounding template.
    <ng-template [polymorpheusOutlet="myContent" [polymorpheusOutletContext]="myContext">
    </ng-template>
  6. Wrap components with PolymorpheusComponent

    main

    To use an Angular component as content within a PolymorpheusOutlet, you must wrap it in a PolymorpheusComponent instance. This wrapper allows the outlet to dynamically instantiate the component and provides a mechanism to inject context into it.

    PolymorpheusComponent takes two arguments in its constructor:

    • component: The Angular Type<T> of the component you want to render.
    • i (optional): An Injector used as a parent for the component's injector, useful for lazy-loaded modules.

    To provide data (context) to the dynamic component, use the createInjector method. This method returns a new Injector that includes the provideContext token with your provided value.

    import { Type } from '@angular/core';
    import { PolymorpheusComponent } from 'ng-polymorpheus';
    
    // Define your component and context type
    class MyComponent { /* ... */ }
    interface MyContext { name: string; }
    
    // Wrap the component
    const polymorphicComponent = new PolymorpheusComponent(MyComponent);
    
    // Create an injector with context to be used by the outlet
    const context: MyContext = { name: 'World' };
    const injectorWithContext = polymorphicComponent.createInjector(parentInjector, context);
  7. Import core modules from ng-polymorpheus

    main

    The ng-polymorpheus package provides a mechanism for dynamic component rendering and context management in Angular. You can import its primary building blocks from the main entry point:

    • Classes: Component (the core logic for managing dynamic components).
    • Directives: Outlet (for rendering components) and Template (for handling template-based content).
    • Tokens: Context (used for accessing the dynamic component's context).
    • Types: Content, Handler, and Primitive types for defining custom content and handlers.
    import {
      Component,
      Outlet,
      Template,
      Context,
      Content,
      Handler,
      Primitive
    } from 'ng-polymorpheus';
  8. Supported content types for PolymorpheusOutlet

    main

    The PolymorpheusContent<C> type defines all the different ways you can provide content to a PolymorpheusOutlet. The generic parameter C represents the type of the context object used within the content. Supported types include:

    • PolymorpheusComponent<unknown>: A component that implements the Polymorpheus interface.
    • PolymorpheusHandler<C>: A custom handler that manages how content is rendered.
    • PolymorpheusPrimitive: A primitive content type.
    • PolymorpheusTemplate<Partial<C> | ''>: A template-based content where the context is partially typed or an empty string (untyped).
    • TemplateRef<Partial<C>>: A standard Angular TemplateRef with a partially typed context.
    export type PolymorpheusContent<C = any> =
        | PolymorpheusComponent<unknown>
        | PolymorpheusHandler<C>
        | PolymorpheusPrimitive
        | PolymorpheusTemplate<Partial<C> | ''>
        | TemplateRef<Partial<C>>;
  9. Use the PolymorpheusTemplate directive to wrap templates

    main

    The polymorpheus directive is used on an ng-template to enable polymorphic rendering and provide access to the template's context. By applying polymorpheus to an ng-template, you can store a context object in the polymorpheus input property. The directive also exports itself as #polymorpheus, allowing you to reference the directive instance in your template.

    Additionally, the directive provides a check() method that manually triggers change detection via markForCheck(), which is useful when working with OnPush change detection strategies in dynamic component scenarios.

    <ng-template [polymorpheus]="myContext" #polymorpheus>
      <div>{{ myContext.someProperty }}</div>
    </ng-template>
  10. Use PolymorpheusContext to access implicit data

    main

    The PolymorpheusContext<T> class is used to wrap and provide data to polymorphic content. It holds an implicit value of type T. You can access this value in two ways:

    1. Via the $implicit property: This is the standard property used by Angular's NgTemplateOutlet and similar directives to provide the context value.
    2. Via the polymorpheusOutlet getter: This provides a semantic way to retrieve the same implicit value.

    Both properties are read-only.

    // Example of how the context object is structured internally
    const context = new PolymorpheusContext<string>('my-value');
    
    console.log(context.$implicit);           // 'my-value'
    console.log(context.polymorpheusOutlet); // 'my-value'
  11. Type-safe template context with ngTemplateContextGuard

    main
    The polymorpheusOutlet directive includes an ngTemplateContextGuard that provides type safety for the template context. When using primitives as content, the directive automatically wraps them in a PolymorpheusContext. This ensures that the template context is correctly typed as PolymorpheusContext<T> where T is the primitive type, preventing type errors when accessing context properties in the template.