@ngneat/overview
Version:
The Template for Success in Template Management
385 lines (372 loc) • 15.4 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, input, inject, TemplateRef, Directive, ViewContainerRef, effect, createComponent, InjectionToken, Injector, ApplicationRef, EnvironmentInjector, signal, Component } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { filter, map } from 'rxjs/operators';
class TeleportService {
constructor() {
this.outlets = new BehaviorSubject('');
this.ports = new Map();
}
outlet$(name) {
return this.outlets.pipe(filter((current) => current === name), map((name) => this.ports.get(name)));
}
newOutlet(name) {
this.outlets.next(name);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: TeleportService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: TeleportService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: TeleportService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}] });
class TeleportDirective {
constructor() {
this.teleportTo = input();
this.subscription = null;
this.tpl = inject(TemplateRef);
this.service = inject(TeleportService);
}
ngOnChanges(changes) {
// Note: Do not use `effect` to observe changes to `teleportTo`, as
// this would result in running `createEmbeddedView` within that effect.
// `createEmbeddedView` may create a component whose constructor might
// trigger another effect or write to some signals, given that we are
// operating within a reactive context.
if (changes.teleportTo && typeof this.teleportTo() === 'string') {
this.dispose();
this.subscription = this.service.outlet$(this.teleportTo()).subscribe((outlet) => {
if (outlet) {
this.viewRef = outlet.createEmbeddedView(this.tpl);
}
});
}
}
ngOnDestroy() {
this.dispose();
}
dispose() {
this.subscription?.unsubscribe();
this.subscription = null;
this.viewRef?.destroy();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: TeleportDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.0.4", type: TeleportDirective, isStandalone: true, selector: "[teleportTo]", inputs: { teleportTo: { classPropertyName: "teleportTo", publicName: "teleportTo", isSignal: true, isRequired: false, transformFunction: null } }, usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: TeleportDirective, decorators: [{
type: Directive,
args: [{
selector: '[teleportTo]',
standalone: true,
}]
}] });
class TeleportOutletDirective {
constructor() {
// We could've also used the `ngAcceptInputType`, but it's being deprecated in newer Angular versions.
this.teleportOutlet = input();
this.vcr = inject(ViewContainerRef);
this.service = inject(TeleportService);
effect(() => {
const teleportOutlet = this.teleportOutlet();
if (typeof teleportOutlet === 'string') {
this.service.ports.set(teleportOutlet, this.vcr);
this.service.newOutlet(teleportOutlet);
}
});
}
ngOnDestroy() {
this.service.ports.delete(this.teleportOutlet());
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: TeleportOutletDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.0.4", type: TeleportOutletDirective, isStandalone: true, selector: "[teleportOutlet]", inputs: { teleportOutlet: { classPropertyName: "teleportOutlet", publicName: "teleportOutlet", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: TeleportOutletDirective, decorators: [{
type: Directive,
args: [{
selector: '[teleportOutlet]',
standalone: true,
}]
}], ctorParameters: () => [] });
class CompRef {
constructor(options) {
this.options = options;
if (options.vcr) {
this.ref = options.vcr.createComponent(options.component, {
index: options.vcr.length,
injector: options.injector || options.vcr.injector,
});
}
else {
this.ref = createComponent(options.component, {
elementInjector: options.injector,
environmentInjector: options.environmentInjector,
});
options.appRef.attachView(this.ref.hostView);
}
}
setInput(input, value) {
this.ref.setInput(input, value);
return this;
}
setInputs(inputs) {
Object.keys(inputs).forEach((input) => {
this.ref.setInput(input, inputs[input]);
});
return this;
}
detectChanges() {
this.ref.hostView.detectChanges();
return this;
}
updateContext(context) {
this.options.contextSignal?.set(context);
return this;
}
appendTo(container) {
container.appendChild(this.getElement());
return this;
}
removeFrom(container) {
container.removeChild(this.getElement());
return this;
}
getRawContent() {
return this.getElement().outerHTML;
}
getElement() {
return this.ref.location.nativeElement;
}
destroy() {
this.ref.destroy();
!this.options.vcr && this.options.appRef.detachView(this.ref.hostView);
this.ref = null;
}
}
function isTemplateRef(value) {
return value instanceof TemplateRef;
}
function isComponent(value) {
return typeof value === 'function';
}
function isString(value) {
return typeof value === 'string';
}
function getViewRef(value) {
return value instanceof CompRef ? value.ref.hostView : value.ref;
}
class TplRef {
constructor(args) {
this.args = args;
if (this.args.vcr) {
this.ref = this.args.vcr.createEmbeddedView(this.args.tpl, this.args.context || {}, { injector: args.injector });
this.ref.detectChanges();
}
else {
this.ref = this.args.tpl.createEmbeddedView(this.args.context || {}, args.injector);
this.ref.detectChanges();
this.args.appRef.attachView(this.ref);
}
}
detectChanges() {
this.ref.detectChanges();
return this;
}
getElement() {
const rootNodes = this.ref.rootNodes;
if (rootNodes.length === 1 && rootNodes[0] === Node.ELEMENT_NODE) {
this.element = rootNodes[0];
}
else {
this.element = document.createElement('div');
this.element.append(...rootNodes);
}
return this.element;
}
destroy() {
if (this.ref.rootNodes[0] !== 1) {
this.element?.parentNode.removeChild(this.element);
this.element = null;
}
if (!this.args.vcr) {
this.args.appRef.detachView(this.ref);
}
this.ref.destroy();
this.ref = null;
}
updateContext(context) {
Object.assign(this.ref.context, context);
return this;
}
}
class StringRef {
constructor(value) {
this.value = value;
}
getElement() {
return this.value;
}
detectChanges() {
return this;
}
updateContext() {
return this;
}
destroy() { }
}
const VIEW_CONTEXT = new InjectionToken('Component context');
class ViewService {
constructor() {
this.injector = inject(Injector);
this.appRef = inject(ApplicationRef);
this.environmentInjector = inject(EnvironmentInjector);
}
createComponent(component, options = {}) {
let injector = options.injector ?? this.injector;
let contextSignal;
if (options.context) {
contextSignal = signal(options.context);
injector = Injector.create({
providers: [
{
provide: VIEW_CONTEXT,
useValue: contextSignal.asReadonly(),
},
],
parent: injector,
});
}
return new CompRef({
component,
vcr: options.vcr,
injector,
appRef: this.appRef,
environmentInjector: options.environmentInjector || this.environmentInjector,
contextSignal,
});
}
createTemplate(tpl, options = {}) {
return new TplRef({
vcr: options.vcr,
appRef: this.appRef,
tpl,
context: options.context,
injector: options.injector,
});
}
createView(content, viewOptions = {}) {
if (isTemplateRef(content)) {
return this.createTemplate(content, viewOptions);
}
else if (isComponent(content)) {
return this.createComponent(content, viewOptions);
}
else if (isString(content)) {
return new StringRef(content);
}
else {
throw 'Type of content is not supported';
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: ViewService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: ViewService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: ViewService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
function injectViewContext() {
return inject(VIEW_CONTEXT);
}
class DynamicViewComponent {
constructor() {
this.content = input();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: DynamicViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.0.4", type: DynamicViewComponent, isStandalone: true, selector: "dynamic-view", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: ` <div [innerHTML]="content()"></div> `, isInline: true }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: DynamicViewComponent, decorators: [{
type: Component,
args: [{
selector: 'dynamic-view',
standalone: true,
template: ` <div [innerHTML]="content()"></div> `,
}]
}] });
class DynamicViewDirective {
constructor() {
this.view = input(undefined, { alias: 'dynamicView' });
this.injector = input(undefined, { alias: 'dynamicViewInjector' });
this.context = input(undefined, { alias: 'dynamicViewContext' });
this.inputs = input(undefined, { alias: 'dynamicViewInputs' });
this.defaultTpl = inject(TemplateRef);
this.vcr = inject(ViewContainerRef);
this.viewService = inject(ViewService);
}
ngOnInit() {
this.resolveContentType();
}
ngOnChanges(changes) {
const viewChanged = changes.view && !changes.view.isFirstChange();
const contextChanged = changes.context && !changes.context.isFirstChange();
const inputsChanged = changes.inputs && !changes.inputs.isFirstChange();
if (viewChanged) {
this.resolveContentType();
}
else if (contextChanged) {
this.viewRef.updateContext(this.context());
}
else if (isComponent(this.view()) && inputsChanged) {
this.viewRef.setInputs(this.inputs() || {});
}
}
resolveContentType() {
this.viewRef?.destroy();
const view = this.view();
const injector = this.injector();
const context = this.context();
if (isString(view)) {
const viewRef = (this.viewRef = this.viewService.createComponent(DynamicViewComponent, {
vcr: this.vcr,
injector,
}));
viewRef.setInput('content', view).detectChanges();
}
else if (isComponent(view)) {
this.viewRef = this.viewService.createComponent(view, {
vcr: this.vcr,
injector: injector ?? this.vcr.injector,
context,
});
const inputs = this.inputs();
if (inputs) {
this.viewRef.setInputs(inputs);
}
}
else {
this.viewRef = this.viewService.createView(view || this.defaultTpl, {
vcr: this.vcr,
injector: injector ?? this.vcr.injector,
context,
});
}
}
ngOnDestroy() {
this.viewRef?.destroy();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: DynamicViewDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.0.4", type: DynamicViewDirective, isStandalone: true, selector: "[dynamicView]", inputs: { view: { classPropertyName: "view", publicName: "dynamicView", isSignal: true, isRequired: false, transformFunction: null }, injector: { classPropertyName: "injector", publicName: "dynamicViewInjector", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "dynamicViewContext", isSignal: true, isRequired: false, transformFunction: null }, inputs: { classPropertyName: "inputs", publicName: "dynamicViewInputs", isSignal: true, isRequired: false, transformFunction: null } }, usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: DynamicViewDirective, decorators: [{
type: Directive,
args: [{
selector: '[dynamicView]',
standalone: true,
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { CompRef, DynamicViewDirective, StringRef, TeleportDirective, TeleportOutletDirective, TplRef, ViewService, getViewRef, injectViewContext, isComponent, isString, isTemplateRef };
//# sourceMappingURL=ngneat-overview.mjs.map