UNPKG

@ngneat/overview

Version:

The Template for Success in Template Management

385 lines (372 loc) 15.4 kB
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