Strategy for
@angular/elementsthat allows to inheritInjectors between all Angular Custom Elements just like default Angular Component do
| 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.
$ npm install ngx-element-boundaryMain 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
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
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.
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.
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.
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
DefaultNgElementStrategyFactoryStrategyto create defaultNgElementStrategyFactory - Uses
DefaultElementBoundaryNgElementStrategyto bridge between defaultNgElementStrategyandElementBoundaryNgElementStrategy
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
NgElementStrategyfrom the Custom Element: Performed in theDefaultNgElementStrategyFactoryvia theNgElementStrategyExtractor. You may provide custom extractor inDefaultNgElementStrategyFactoryOptions. - To extract
ComponentReffrom theNgElementStrategy: Performed in theDefaultElementBoundaryNgElementStrategyvia theNgElementStrategyComponentRefExtractor. You may provide custom extractor inDefaultElementBoundaryNgElementStrategyOptions.
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.
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.
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.
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.
Run ng test to execute the unit tests via Karma.
Run ng e2e to execute the end-to-end tests via Protractor.
To get more help on the Angular CLI use ng help or go check out the Angular CLI README.