Skip to content

Strategy for @angular/elements that allows to inherit Injectors between any Angular Custom Elements just like default Angular Components do

License

Notifications You must be signed in to change notification settings

gund/ngx-element-boundary

Repository files navigation

ngx-element-boundary

Strategy for @angular/elements that allows to inherit Injectors between all Angular Custom Elements just like default Angular Component do

Compatibility table

Angular ngx-element-boundary NPM package
14.x.x 2.x.x ngx-element-boundary@^2.0.0
10.x.x 1.x.x ngx-element-boundary@^1.0.0
9.x.x 1.x.x ngx-element-boundary@^1.0.0

NOTE: For Angular versions below v10 it may work but was not tested.

Install

$ npm install ngx-element-boundary

Why

Main limitation when exposing Angular Components as Custom Elements is that their Injectors will not inherit properly when placed in proper hierarchy in the DOM tree.

However this is expected behavior when using classical Angular Components - their Injectors are properly inherited and you a lot of times rely on it to implement nice patterns.

Solves angular/angular#24824

How

By default @angular/elements allows you to convert any Angular Component into Custom Element with the limitation above.

It also allows to override default NgElementStrategy that is used inside to manage conversion process from Angular Component to Custom Element.

This library implements CrossBoundaryNgElementStrategy that is capable of tracking Angular Custom Elements created in DOM and setting up proper Injector chain between them.

It also does not re-implement default NgElementStrategy but requires you to pass base strategy factory into it's own factory.

To simplify use-case there is an extractor factory of default NgElementStrategy that is provided by @angular/elements - DefaultElementBoundaryNgElementStrategyFactory

Usage

All you have to do is to use custom NgElementStrategy called CrossBoundaryNgElementStrategy when you are converting your Angular Component to Custom Element:

import { Component } from '@angular/core';
import { createCustomElement } from '@angular/element';
import { CrossBoundaryNgElementStrategyFactory } from 'ngx-element-boundary';
import { DefaultElementBoundaryNgElementStrategyFactory } from 'ngx-element-boundary/element-strategy/default';

@Component()
class MyAwesomeComponent {}

// First create the default strategy
const defaultElementBoundaryStrategyFactory = new DefaultElementBoundaryNgElementStrategyFactory(
  MyAwesomeComponent,
  injector,
);

// Then create the cross boundary strategy that uses the default one
const connectedNgElementStrategyFactory = new CrossBoundaryNgElementStrategyFactory(
  defaultElementBoundaryStrategyFactory,
  {
    isRoot: true, // Set to `true` for ONLY top-level custom element
  },
);

const MyAwesomeCustomElement = createCustomElement(MyAwesomeComponent, {
  injector: injector,
  strategyFactory: connectedNgElementStrategyFactory,
});

customElements.define('my-awesome', MyAwesomeCustomElement);

As long as your Custom Elements are created using CrossBoundaryNgElementStrategy they will properly inherit each other Injectors.

Template Injector

Most of the times when your component projects content in template it does so in specific place in the view that may have it's own tree of Injectors.

And by default the strategy will only inherit component level Injectors even if the projection was done on a deeper level.

In that case projected Custom Element will see top level Injector of the parent Custom Element and not the one it projected into.

To fix this issue you may tell the library where exactly you are having content projection in your view by using ElementBoundaryDirective.

So in your component's template mark content projection with the directive:

<some-ng-component>
  <div nebElementBoundary>
    <ng-content></ng-content>
  </div>
</some-ng-component>

NOTE: This directive is exported from ElementBoundaryModule.

Once you do that - any projected Custom Element will resolve Injector at the level that ElementBoundaryDirective is at, meaning that - Custom Element will be able to inject some-ng-component.

Base Strategy

As you might have already understood - this library does not re-implement the default NgElementStrategy but only augments the base strategy by setting up proper Injector tree between Custom Elements.

That means that the strategy CrossBoundaryNgElementStrategy requires you to pass another strategy.

That other strategy is not same as NgElementStrategy as it needs access to the ComponentRef to be able to get Custom Element's Injector.

For that purpose library defines new version of NgElementStrategy called - ElementBoundaryNgElementStrategy.

It also defines new version of NgElementStrategyFactory called - ElementBoundaryNgElementStrategyFactory.

Default Strategy

In most cases you would want to reuse the same NgElementStrategy that @angular/elements uses by default.

For this purpose library has secondary entry-point that contains all the functionality required - ngx-element-boundary/element-strategy/default.

In general you should use DefaultElementBoundaryNgElementStrategyFactory as the base ElementBoundaryNgElementStrategyFactory.

What it does is the following:

  • Uses strategy DefaultNgElementStrategyFactoryStrategy to create default NgElementStrategyFactory
  • Uses DefaultElementBoundaryNgElementStrategy to bridge between default NgElementStrategy and ElementBoundaryNgElementStrategy

By default it uses built-in way to extract default NgElementStrategyFactory. And because Angular Team decided to not expose it as part of public API there are some private API accesses involved and they may brake at any time.

Should that brake - you may still recover by providing updated functions to access private APIs all without the need to wait for new release.

There are 2 places where private API access is performed:

  • To extract NgElementStrategy from the Custom Element: Performed in the DefaultNgElementStrategyFactory via the NgElementStrategyExtractor. You may provide custom extractor in DefaultNgElementStrategyFactoryOptions.
  • To extract ComponentRef from the NgElementStrategy: Performed in the DefaultElementBoundaryNgElementStrategy via the NgElementStrategyComponentRefExtractor. You may provide custom extractor in DefaultElementBoundaryNgElementStrategyOptions.

Creation of the default NgElementStrategy is done in DefaultNgElementStrategyFactory by creating a dummy Custom Element using createCustomElement() function from the @angular/elements with default option for the strategy.

If you have a better way of creating the default NgElementStrategy you may implement it as DefaultNgElementStrategyFactoryStrategy and then provide it in DefaultElementBoundaryNgElementStrategyFactoryOptions.factoryStrategy.

NOTE: Default strategy extraction was inspired by https://github.com/remackgeek/elements-zone-strategy


This project was generated with Angular CLI version 10.0.1.

Development server

Run ng serve for a dev server. Navigate to http://localhost:4200/. The app will automatically reload if you change any of the source files.

Code scaffolding

Run ng generate component component-name to generate a new component. You can also use ng generate directive|pipe|service|class|guard|interface|enum|module.

Build

Run ng build to build the project. The build artifacts will be stored in the dist/ directory. Use the --prod flag for a production build.

Running unit tests

Run ng test to execute the unit tests via Karma.

Running end-to-end tests

Run ng e2e to execute the end-to-end tests via Protractor.

Further help

To get more help on the Angular CLI use ng help or go check out the Angular CLI README.

About

Strategy for @angular/elements that allows to inherit Injectors between any Angular Custom Elements just like default Angular Components do

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages