diff --git a/packages/dockview-angular/src/__tests__/angular-renderer.spec.ts b/packages/dockview-angular/src/__tests__/angular-renderer.spec.ts
index 9eb30407dc..d9657500d9 100644
--- a/packages/dockview-angular/src/__tests__/angular-renderer.spec.ts
+++ b/packages/dockview-angular/src/__tests__/angular-renderer.spec.ts
@@ -4,9 +4,10 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
- EnvironmentInjector,
inject,
Injector,
+ TemplateRef,
+ ViewChild,
} from '@angular/core';
import { AngularRenderer } from '../lib/utils/angular-renderer';
@@ -36,18 +37,33 @@ class TestUpdateComponent {
}
}
+@Component({
+ selector: 'test-template-holder-component',
+ template: `
+
+
+
+ `,
+})
+class TemplateHolderComponent {
+ @ViewChild('template', { static: true })
+ public template?: TemplateRef;
+}
+
describe('AngularRenderer', () => {
let injector: Injector;
- let environmentInjector: EnvironmentInjector;
let application: ApplicationRef;
beforeEach(async () => {
await TestBed.configureTestingModule({
- declarations: [TestComponent],
+ declarations: [
+ TestComponent,
+ TestUpdateComponent,
+ TemplateHolderComponent,
+ ],
}).compileComponents();
injector = TestBed.inject(Injector);
- environmentInjector = TestBed.inject(EnvironmentInjector);
application = TestBed.inject(ApplicationRef);
});
@@ -63,7 +79,6 @@ describe('AngularRenderer', () => {
const renderer = new AngularRenderer({
component: TestComponent,
injector,
- environmentInjector,
});
renderer.init({ title: 'Updated Title', value: 'test-value' });
@@ -80,7 +95,6 @@ describe('AngularRenderer', () => {
const renderer = new AngularRenderer({
component: TestComponent,
injector,
- environmentInjector,
});
renderer.init({ title: 'Initial Title' });
@@ -97,7 +111,6 @@ describe('AngularRenderer', () => {
const renderer = new AngularRenderer({
component: TestComponent,
injector,
- environmentInjector,
});
renderer.init({ title: 'Test Title' });
@@ -118,7 +131,6 @@ describe('AngularRenderer', () => {
const renderer = new AngularRenderer({
component: null as any,
injector,
- environmentInjector,
});
expect(() => {
@@ -130,7 +142,6 @@ describe('AngularRenderer', () => {
const renderer = new AngularRenderer({
component: TestComponent,
injector,
- environmentInjector,
});
renderer.init({ title: 'Test Title' });
@@ -145,7 +156,6 @@ describe('AngularRenderer', () => {
const renderer = new AngularRenderer({
component: TestComponent,
injector,
- environmentInjector,
});
renderer.init({ title: 'Test Title' });
@@ -161,7 +171,6 @@ describe('AngularRenderer', () => {
const renderer = new AngularRenderer({
component: TestUpdateComponent,
injector,
- environmentInjector,
});
renderer.init({});
@@ -172,4 +181,28 @@ describe('AngularRenderer', () => {
application.tick();
expect(renderer.element.innerHTML).toContain('Counter: 1');
});
+
+ it('should render view from template', () => {
+ // Create component with template
+ const templateRenderer = new AngularRenderer({
+ component: TemplateHolderComponent,
+ injector,
+ });
+ templateRenderer.init({});
+ const template = (
+ templateRenderer.component.instance as TemplateHolderComponent
+ ).template;
+
+ expect(template).toBeDefined();
+
+ // Create view from template
+ const renderer = new AngularRenderer({
+ component: template,
+ injector: templateRenderer.component.injector, // use container injector to ensure we have a view
+ });
+ renderer.init({});
+ application.tick();
+
+ expect(renderer.element.innerHTML).toContain('Counter: 0');
+ });
});
diff --git a/packages/dockview-angular/src/__tests__/component-factory.spec.ts b/packages/dockview-angular/src/__tests__/component-factory.spec.ts
index c7bfb2f60f..b7f34acb7a 100644
--- a/packages/dockview-angular/src/__tests__/component-factory.spec.ts
+++ b/packages/dockview-angular/src/__tests__/component-factory.spec.ts
@@ -2,6 +2,7 @@ import { TestBed, ComponentFixture } from '@angular/core/testing';
import { Component, Injector, EnvironmentInjector } from '@angular/core';
import { AngularFrameworkComponentFactory } from '../lib/utils/component-factory';
import { CreateComponentOptions } from 'dockview-core';
+import { ComponentRegistryService } from '../lib/utils/component-registry.service';
@Component({
selector: 'test-dockview-component',
@@ -53,6 +54,7 @@ describe('AngularFrameworkComponentFactory', () => {
let injector: Injector;
let environmentInjector: EnvironmentInjector;
let factory: AngularFrameworkComponentFactory;
+ let resolver: ComponentRegistryService;
const components = {
'dockview-test': TestDockviewComponent,
@@ -84,9 +86,11 @@ describe('AngularFrameworkComponentFactory', () => {
injector = TestBed.inject(Injector);
environmentInjector = TestBed.inject(EnvironmentInjector);
+ resolver = TestBed.inject(ComponentRegistryService);
+ resolver.registerComponents(components);
factory = new AngularFrameworkComponentFactory(
- components,
+ resolver,
injector,
environmentInjector,
tabComponents,
@@ -237,7 +241,7 @@ describe('AngularFrameworkComponentFactory', () => {
it('should return undefined when no component and no default', () => {
const factoryWithoutDefault = new AngularFrameworkComponentFactory(
- components,
+ resolver,
injector,
environmentInjector,
{}
@@ -266,7 +270,7 @@ describe('AngularFrameworkComponentFactory', () => {
it('should throw error when no watermark component provided', () => {
const factoryWithoutWatermark =
new AngularFrameworkComponentFactory(
- components,
+ resolver,
injector,
environmentInjector
);
@@ -299,7 +303,7 @@ describe('AngularFrameworkComponentFactory', () => {
it('should return undefined when no header actions components provided', () => {
const factoryWithoutHeaderActions =
new AngularFrameworkComponentFactory(
- components,
+ resolver,
injector,
environmentInjector
);
diff --git a/packages/dockview-angular/src/__tests__/component-registry.spec.ts b/packages/dockview-angular/src/__tests__/component-registry.spec.ts
new file mode 100644
index 0000000000..e4f71f7610
--- /dev/null
+++ b/packages/dockview-angular/src/__tests__/component-registry.spec.ts
@@ -0,0 +1,121 @@
+import { TestBed } from '@angular/core/testing';
+import { Type } from '@angular/core';
+import {
+ ComponentRegistryService,
+ ComponentResolver,
+} from '../lib/utils/component-registry.service';
+
+describe('ComponentRegistryService', () => {
+ let service: ComponentRegistryService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [ComponentRegistryService],
+ });
+ service = TestBed.inject(ComponentRegistryService);
+ });
+
+ describe('registerComponent', () => {
+ it('should register a component with a valid name and reference', () => {
+ const mockComponent = {} as Type;
+ service.registerComponent('testComponent', mockComponent);
+
+ expect(service.resolveComponent('testComponent')).toBe(
+ mockComponent
+ );
+ });
+
+ it('should throw an error if component name or reference is not provided', () => {
+ expect(() =>
+ service.registerComponent('', {} as Type)
+ ).toThrow('Component and reference must be provided');
+ });
+ });
+
+ describe('registerComponents', () => {
+ it('should register multiple components from a record', () => {
+ const components = {
+ componentA: {} as Type,
+ componentB: {} as Type,
+ };
+
+ service.registerComponents(components);
+
+ expect(service.resolveComponent('componentA')).toBe(
+ components.componentA
+ );
+ expect(service.resolveComponent('componentB')).toBe(
+ components.componentB
+ );
+ });
+ });
+
+ describe('resolveComponent', () => {
+ it('should return a registered component reference', () => {
+ const mockComponent = {} as Type;
+ service.registerComponent('testComponent', mockComponent);
+
+ const resolved = service.resolveComponent('testComponent');
+ expect(resolved).toBe(mockComponent);
+ });
+
+ it('should throw an error if component name is not provided', () => {
+ expect(() => service.resolveComponent('')).toThrow(
+ 'Component must be provided'
+ );
+ });
+
+ it('should resolve a component dynamically through a resolver', () => {
+ const dynamicComponent = {} as Type;
+ const resolver: ComponentResolver = (component) =>
+ component === 'dynamicComponent' ? dynamicComponent : undefined;
+
+ service.registerResolver(resolver);
+
+ expect(service.resolveComponent('dynamicComponent')).toBe(
+ dynamicComponent
+ );
+ });
+
+ it('should fallback to static registration if no resolver matches', () => {
+ const staticComponent = {} as Type;
+ service.registerComponent('staticComponent', staticComponent);
+
+ const resolver: ComponentResolver = () => undefined;
+ service.registerResolver(resolver);
+
+ expect(service.resolveComponent('staticComponent')).toBe(
+ staticComponent
+ );
+ });
+ });
+
+ describe('registerResolver', () => {
+ it('should register a new resolver for dynamic component resolution', () => {
+ const dynamicComponent = {} as Type;
+ const resolver: ComponentResolver = (component) =>
+ component === 'dynamicComponent' ? dynamicComponent : undefined;
+
+ service.registerResolver(resolver);
+
+ expect(service.resolveComponent('dynamicComponent')).toBe(
+ dynamicComponent
+ );
+ });
+ });
+
+ describe('unregisterResolver', () => {
+ it('should unregister a resolver', () => {
+ const dynamicComponent = {} as Type;
+ const resolver: ComponentResolver = (component) =>
+ component === 'dynamicComponent' ? dynamicComponent : undefined;
+
+ service.registerResolver(resolver);
+ service.unregisterResolver(resolver);
+
+ expect(
+ service.resolveComponent('dynamicComponent')
+ ).toBeUndefined();
+ });
+ });
+});
diff --git a/packages/dockview-angular/src/__tests__/dockview-angular.component.spec.ts b/packages/dockview-angular/src/__tests__/dockview-angular.component.spec.ts
index 093dc40193..374fb98f79 100644
--- a/packages/dockview-angular/src/__tests__/dockview-angular.component.spec.ts
+++ b/packages/dockview-angular/src/__tests__/dockview-angular.component.spec.ts
@@ -33,14 +33,6 @@ describe('DockviewAngularComponent', () => {
expect(component).toBeTruthy();
});
- it('should throw error if components input is not provided', () => {
- component.components = undefined as any;
-
- expect(() => {
- component.ngOnInit();
- }).toThrow('DockviewAngularComponent: components input is required');
- });
-
it('should initialize dockview api on ngOnInit', () => {
component.ngOnInit();
diff --git a/packages/dockview-angular/src/__tests__/gridview-angular.component.spec.ts b/packages/dockview-angular/src/__tests__/gridview-angular.component.spec.ts
index 6923e35d36..3db0233df5 100644
--- a/packages/dockview-angular/src/__tests__/gridview-angular.component.spec.ts
+++ b/packages/dockview-angular/src/__tests__/gridview-angular.component.spec.ts
@@ -33,14 +33,6 @@ describe('GridviewAngularComponent', () => {
expect(component).toBeTruthy();
});
- it('should throw error if components input is not provided', () => {
- component.components = undefined as any;
-
- expect(() => {
- component.ngOnInit();
- }).toThrow('GridviewAngularComponent: components input is required');
- });
-
it('should initialize gridview api on ngOnInit', () => {
component.ngOnInit();
diff --git a/packages/dockview-angular/src/__tests__/paneview-angular.component.spec.ts b/packages/dockview-angular/src/__tests__/paneview-angular.component.spec.ts
index aa56b840aa..67e3618800 100644
--- a/packages/dockview-angular/src/__tests__/paneview-angular.component.spec.ts
+++ b/packages/dockview-angular/src/__tests__/paneview-angular.component.spec.ts
@@ -33,14 +33,6 @@ describe('PaneviewAngularComponent', () => {
expect(component).toBeTruthy();
});
- it('should throw error if components input is not provided', () => {
- component.components = undefined as any;
-
- expect(() => {
- component.ngOnInit();
- }).toThrow('PaneviewAngularComponent: components input is required');
- });
-
it('should initialize paneview api on ngOnInit', () => {
component.ngOnInit();
diff --git a/packages/dockview-angular/src/__tests__/splitview-angular.component.spec.ts b/packages/dockview-angular/src/__tests__/splitview-angular.component.spec.ts
index 3b566b0395..81b2d2a922 100644
--- a/packages/dockview-angular/src/__tests__/splitview-angular.component.spec.ts
+++ b/packages/dockview-angular/src/__tests__/splitview-angular.component.spec.ts
@@ -33,14 +33,6 @@ describe('SplitviewAngularComponent', () => {
expect(component).toBeTruthy();
});
- it('should throw error if components input is not provided', () => {
- component.components = undefined as any;
-
- expect(() => {
- component.ngOnInit();
- }).toThrow('SplitviewAngularComponent: components input is required');
- });
-
it('should initialize splitview api on ngOnInit', () => {
component.ngOnInit();
diff --git a/packages/dockview-angular/src/lib/dockview/dockview-angular.component.ts b/packages/dockview-angular/src/lib/dockview/dockview-angular.component.ts
index 3db53da583..c44fd1eb51 100644
--- a/packages/dockview-angular/src/lib/dockview/dockview-angular.component.ts
+++ b/packages/dockview-angular/src/lib/dockview/dockview-angular.component.ts
@@ -28,6 +28,8 @@ import {
} from 'dockview-core';
import { AngularFrameworkComponentFactory } from '../utils/component-factory';
import { AngularLifecycleManager } from '../utils/lifecycle-utils';
+import { ComponentRegistryService } from '../utils/component-registry.service';
+import { ComponentReference } from '../types';
export interface DockviewAngularOptions extends DockviewOptions {
components: Record>;
@@ -60,10 +62,14 @@ export interface DockviewAngularOptions extends DockviewOptions {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DockviewAngularComponent implements OnInit, OnDestroy, OnChanges {
+ private readonly componentRegistry: ComponentRegistryService = inject(
+ ComponentRegistryService
+ );
+
@ViewChild('dockviewContainer', { static: true })
private containerRef!: ElementRef;
- @Input() components!: Record>;
+ @Input() components?: Record;
@Input() tabComponents?: Record>;
@Input() watermarkComponent?: Type;
@Input() defaultTabComponent?: Type;
@@ -130,10 +136,8 @@ export class DockviewAngularComponent implements OnInit, OnDestroy, OnChanges {
}
private initializeDockview(): void {
- if (!this.components) {
- throw new Error(
- 'DockviewAngularComponent: components input is required'
- );
+ if (this.components) {
+ this.componentRegistry.registerComponents(this.components);
}
const coreOptions = this.extractCoreOptions();
@@ -178,7 +182,7 @@ export class DockviewAngularComponent implements OnInit, OnDestroy, OnChanges {
}
const componentFactory = new AngularFrameworkComponentFactory(
- this.components,
+ this.componentRegistry,
this.injector,
this.environmentInjector,
this.tabComponents,
diff --git a/packages/dockview-angular/src/lib/gridview/angular-gridview-panel.ts b/packages/dockview-angular/src/lib/gridview/angular-gridview-panel.ts
index f3d5b37dcf..8ccd9bc073 100644
--- a/packages/dockview-angular/src/lib/gridview/angular-gridview-panel.ts
+++ b/packages/dockview-angular/src/lib/gridview/angular-gridview-panel.ts
@@ -1,12 +1,13 @@
-import { Type, Injector, EnvironmentInjector } from '@angular/core';
+import { Injector, EnvironmentInjector } from '@angular/core';
import { GridviewPanel, IFrameworkPart } from 'dockview-core';
import { AngularRenderer } from '../utils/angular-renderer';
+import { ComponentReference } from '../types';
export class AngularGridviewPanel extends GridviewPanel {
constructor(
id: string,
component: string,
- private readonly angularComponent: Type,
+ private readonly angularComponent: ComponentReference,
private readonly injector: Injector,
private readonly environmentInjector?: EnvironmentInjector
) {
diff --git a/packages/dockview-angular/src/lib/gridview/gridview-angular.component.ts b/packages/dockview-angular/src/lib/gridview/gridview-angular.component.ts
index 5ba3381740..e29f6498ef 100644
--- a/packages/dockview-angular/src/lib/gridview/gridview-angular.component.ts
+++ b/packages/dockview-angular/src/lib/gridview/gridview-angular.component.ts
@@ -26,6 +26,8 @@ import {
import { AngularFrameworkComponentFactory } from '../utils/component-factory';
import { AngularLifecycleManager } from '../utils/lifecycle-utils';
import { GridviewAngularReadyEvent } from './types';
+import { ComponentRegistryService } from '../utils/component-registry.service';
+import { ComponentReference } from '../types';
export interface GridviewAngularOptions extends GridviewOptions {
components: Record>;
@@ -52,10 +54,14 @@ export interface GridviewAngularOptions extends GridviewOptions {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GridviewAngularComponent implements OnInit, OnDestroy, OnChanges {
+ private readonly componentRegistry: ComponentRegistryService = inject(
+ ComponentRegistryService
+ );
+
@ViewChild('gridviewContainer', { static: true })
private containerRef!: ElementRef;
- @Input() components!: Record>;
+ @Input() components?: Record;
// Core gridview options as inputs
@Input() className?: string;
@@ -107,10 +113,8 @@ export class GridviewAngularComponent implements OnInit, OnDestroy, OnChanges {
}
private initializeGridview(): void {
- if (!this.components) {
- throw new Error(
- 'GridviewAngularComponent: components input is required'
- );
+ if (this.components) {
+ this.componentRegistry.registerComponents(this.components);
}
const coreOptions = this.extractCoreOptions();
@@ -140,7 +144,7 @@ export class GridviewAngularComponent implements OnInit, OnDestroy, OnChanges {
private createFrameworkOptions(): GridviewFrameworkOptions {
const componentFactory = new AngularFrameworkComponentFactory(
- this.components,
+ this.componentRegistry,
this.injector,
this.environmentInjector
);
diff --git a/packages/dockview-angular/src/lib/paneview/angular-pane-part.ts b/packages/dockview-angular/src/lib/paneview/angular-pane-part.ts
index 535917a137..c85f211ef8 100644
--- a/packages/dockview-angular/src/lib/paneview/angular-pane-part.ts
+++ b/packages/dockview-angular/src/lib/paneview/angular-pane-part.ts
@@ -1,16 +1,17 @@
-import { Type, Injector, EnvironmentInjector } from '@angular/core';
+import { Injector, EnvironmentInjector } from '@angular/core';
import {
IPanePart,
PanelUpdateEvent,
PanePanelComponentInitParameter,
} from 'dockview-core';
import { AngularRenderer } from '../utils/angular-renderer';
+import { ComponentReference } from '../types';
export class AngularPanePart implements IPanePart {
private renderer: AngularRenderer;
constructor(
- private readonly angularComponent: Type,
+ private readonly angularComponent: ComponentReference,
private readonly injector: Injector,
private readonly environmentInjector?: EnvironmentInjector
) {
diff --git a/packages/dockview-angular/src/lib/paneview/paneview-angular.component.ts b/packages/dockview-angular/src/lib/paneview/paneview-angular.component.ts
index b756ce5d36..ebbd1127e0 100644
--- a/packages/dockview-angular/src/lib/paneview/paneview-angular.component.ts
+++ b/packages/dockview-angular/src/lib/paneview/paneview-angular.component.ts
@@ -28,6 +28,8 @@ import { AngularFrameworkComponentFactory } from '../utils/component-factory';
import { AngularLifecycleManager } from '../utils/lifecycle-utils';
import { PaneviewAngularReadyEvent } from './types';
import { AngularPanePart } from './angular-pane-part';
+import { ComponentRegistryService } from '../utils/component-registry.service';
+import { ComponentReference } from '../types';
export interface PaneviewAngularOptions extends PaneviewOptions {
components: Record>;
@@ -55,10 +57,14 @@ export interface PaneviewAngularOptions extends PaneviewOptions {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PaneviewAngularComponent implements OnInit, OnDestroy, OnChanges {
+ private readonly componentRegistry: ComponentRegistryService = inject(
+ ComponentRegistryService
+ );
+
@ViewChild('paneviewContainer', { static: true })
private containerRef!: ElementRef;
- @Input() components!: Record>;
+ @Input() components?: Record;
@Input() headerComponents?: Record>;
// Core paneview options as inputs
@@ -111,10 +117,8 @@ export class PaneviewAngularComponent implements OnInit, OnDestroy, OnChanges {
}
private initializePaneview(): void {
- if (!this.components) {
- throw new Error(
- 'PaneviewAngularComponent: components input is required'
- );
+ if (this.components) {
+ this.componentRegistry.registerComponents(this.components);
}
const coreOptions = this.extractCoreOptions();
@@ -147,7 +151,7 @@ export class PaneviewAngularComponent implements OnInit, OnDestroy, OnChanges {
private createFrameworkOptions(): PaneviewFrameworkOptions {
const componentFactory = new AngularFrameworkComponentFactory(
- this.components,
+ this.componentRegistry,
this.injector,
this.environmentInjector,
this.headerComponents
diff --git a/packages/dockview-angular/src/lib/splitview/angular-splitview-panel.ts b/packages/dockview-angular/src/lib/splitview/angular-splitview-panel.ts
index eedd63df69..dce1773c33 100644
--- a/packages/dockview-angular/src/lib/splitview/angular-splitview-panel.ts
+++ b/packages/dockview-angular/src/lib/splitview/angular-splitview-panel.ts
@@ -1,12 +1,13 @@
-import { Type, Injector, EnvironmentInjector } from '@angular/core';
+import { Injector, EnvironmentInjector } from '@angular/core';
import { SplitviewPanel, IFrameworkPart } from 'dockview-core';
import { AngularRenderer } from '../utils/angular-renderer';
+import { ComponentReference } from '../types';
export class AngularSplitviewPanel extends SplitviewPanel {
constructor(
id: string,
component: string,
- private readonly angularComponent: Type,
+ private readonly angularComponent: ComponentReference,
private readonly injector: Injector,
private readonly environmentInjector?: EnvironmentInjector
) {
diff --git a/packages/dockview-angular/src/lib/splitview/splitview-angular.component.ts b/packages/dockview-angular/src/lib/splitview/splitview-angular.component.ts
index e1577c0bec..24691b5535 100644
--- a/packages/dockview-angular/src/lib/splitview/splitview-angular.component.ts
+++ b/packages/dockview-angular/src/lib/splitview/splitview-angular.component.ts
@@ -26,6 +26,8 @@ import {
import { AngularFrameworkComponentFactory } from '../utils/component-factory';
import { AngularLifecycleManager } from '../utils/lifecycle-utils';
import { SplitviewAngularReadyEvent } from './types';
+import { ComponentRegistryService } from '../utils/component-registry.service';
+import { ComponentReference } from '../types';
export interface SplitviewAngularOptions extends SplitviewOptions {
components: Record>;
@@ -52,10 +54,14 @@ export interface SplitviewAngularOptions extends SplitviewOptions {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SplitviewAngularComponent implements OnInit, OnDestroy, OnChanges {
+ private readonly componentRegistry: ComponentRegistryService = inject(
+ ComponentRegistryService
+ );
+
@ViewChild('splitviewContainer', { static: true })
private containerRef!: ElementRef;
- @Input() components!: Record>;
+ @Input() components?: Record;
// Core splitview options as inputs
@Input() className?: string;
@@ -107,10 +113,8 @@ export class SplitviewAngularComponent implements OnInit, OnDestroy, OnChanges {
}
private initializeSplitview(): void {
- if (!this.components) {
- throw new Error(
- 'SplitviewAngularComponent: components input is required'
- );
+ if (this.components) {
+ this.componentRegistry.registerComponents(this.components);
}
const coreOptions = this.extractCoreOptions();
@@ -140,7 +144,7 @@ export class SplitviewAngularComponent implements OnInit, OnDestroy, OnChanges {
private createFrameworkOptions(): SplitviewFrameworkOptions {
const componentFactory = new AngularFrameworkComponentFactory(
- this.components,
+ this.componentRegistry,
this.injector,
this.environmentInjector
);
diff --git a/packages/dockview-angular/src/lib/types.ts b/packages/dockview-angular/src/lib/types.ts
new file mode 100644
index 0000000000..a386c45546
--- /dev/null
+++ b/packages/dockview-angular/src/lib/types.ts
@@ -0,0 +1,3 @@
+import { TemplateRef, Type } from '@angular/core';
+
+export type ComponentReference = Type | TemplateRef;
diff --git a/packages/dockview-angular/src/lib/utils/angular-renderer.ts b/packages/dockview-angular/src/lib/utils/angular-renderer.ts
index 979e8168a3..e722dade0c 100644
--- a/packages/dockview-angular/src/lib/utils/angular-renderer.ts
+++ b/packages/dockview-angular/src/lib/utils/angular-renderer.ts
@@ -6,17 +6,21 @@ import {
createComponent,
EnvironmentInjector,
ApplicationRef,
+ TemplateRef,
+ ViewContainerRef,
} from '@angular/core';
import { IContentRenderer, IFrameworkPart, Parameters } from 'dockview-core';
+import { ComponentReference } from '../types';
export interface AngularRendererOptions {
- component: Type;
+ component: ComponentReference;
injector: Injector;
environmentInjector?: EnvironmentInjector;
}
export class AngularRenderer implements IContentRenderer, IFrameworkPart {
private componentRef: ComponentRef | null = null;
+ private viewRef: EmbeddedViewRef | null = null;
private _element: HTMLElement | null = null;
private appRef: ApplicationRef;
@@ -34,10 +38,13 @@ export class AngularRenderer implements IContentRenderer, IFrameworkPart {
get component(): ComponentRef | null {
return this.componentRef;
}
+ get view(): EmbeddedViewRef | null {
+ return this.viewRef;
+ }
init(parameters: Parameters): void {
// If already initialized, just update the parameters
- if (this.componentRef) {
+ if (this._element) {
this.update(parameters);
} else {
this.render(parameters);
@@ -45,6 +52,7 @@ export class AngularRenderer implements IContentRenderer, IFrameworkPart {
}
update(params: Parameters): void {
+ // Only component can have parameters
if (!this.componentRef) {
return;
}
@@ -56,43 +64,69 @@ export class AngularRenderer implements IContentRenderer, IFrameworkPart {
}
}
- // trigger change detection
+ // Trigger change detection
this.componentRef.changeDetectorRef.markForCheck();
}
private render(parameters: Parameters): void {
try {
- // Create the component using modern Angular API
- this.componentRef = createComponent(this.options.component, {
- environmentInjector:
- this.options.environmentInjector ||
- (this.options.injector as EnvironmentInjector),
- elementInjector: this.options.injector,
- });
-
- // Set initial parameters
- this.update(parameters);
-
- // Get the DOM element
- const hostView = this.componentRef.hostView as EmbeddedViewRef;
- this._element = hostView.rootNodes[0] as HTMLElement;
-
- // attach to change detection
- this.appRef.attachView(hostView);
-
- // trigger change detection
- this.componentRef.changeDetectorRef.markForCheck();
+ if (this.options.component instanceof TemplateRef) {
+ this.setupView(this.options.component);
+ } else {
+ this.setupComponent(this.options.component, parameters);
+ }
} catch (error) {
console.error('Error creating Angular component:', error);
throw error;
}
}
+ private setupComponent(component: Type, parameters: Parameters): void {
+ // Create the component using modern Angular API
+ this.componentRef = createComponent(component, {
+ environmentInjector:
+ this.options.environmentInjector ||
+ (this.options.injector as EnvironmentInjector),
+ elementInjector: this.options.injector,
+ });
+
+ // Set initial parameters
+ this.update(parameters);
+
+ // Get the DOM element
+ const hostView = this.componentRef.hostView as EmbeddedViewRef;
+ this._element = hostView.rootNodes[0] as HTMLElement;
+
+ // Attach to change detection
+ this.appRef.attachView(hostView);
+
+ // Trigger change detection
+ this.componentRef.changeDetectorRef.markForCheck();
+ }
+
+ private setupView(template: TemplateRef): void {
+ // Get factory for template instances
+ const vcr = this.options.injector.get(ViewContainerRef);
+
+ // Create embedded view from template
+ this.viewRef = vcr.createEmbeddedView(template);
+ this._element = this.viewRef.rootNodes[0] as HTMLElement;
+
+ // Already attached to change detection (of injector, usually dockview)
+
+ // Trigger change detection
+ this.viewRef.markForCheck();
+ }
+
dispose(): void {
if (this.componentRef) {
this.componentRef.destroy();
this.componentRef = null;
}
+ if (this.viewRef) {
+ this.viewRef.destroy();
+ this.viewRef = null;
+ }
this._element = null;
}
}
diff --git a/packages/dockview-angular/src/lib/utils/component-factory.ts b/packages/dockview-angular/src/lib/utils/component-factory.ts
index c7c039d4b2..76791f31d0 100644
--- a/packages/dockview-angular/src/lib/utils/component-factory.ts
+++ b/packages/dockview-angular/src/lib/utils/component-factory.ts
@@ -13,10 +13,11 @@ import { AngularRenderer } from './angular-renderer';
import { AngularGridviewPanel } from '../gridview/angular-gridview-panel';
import { AngularSplitviewPanel } from '../splitview/angular-splitview-panel';
import { AngularPanePart } from '../paneview/angular-pane-part';
+import { ComponentRegistryService } from './component-registry.service';
export class AngularFrameworkComponentFactory {
constructor(
- private components: Record>,
+ private componentResolver: ComponentRegistryService,
private injector: Injector,
private environmentInjector?: EnvironmentInjector,
private tabComponents?: Record>,
@@ -27,7 +28,7 @@ export class AngularFrameworkComponentFactory {
// For DockviewComponent
createDockviewComponent(options: CreateComponentOptions): IContentRenderer {
- const component = this.components[options.name];
+ const component = this.componentResolver.resolveComponent(options.name);
if (!component) {
throw new Error(
`Component '${options.name}' not found in component registry`
@@ -46,7 +47,7 @@ export class AngularFrameworkComponentFactory {
// For GridviewComponent
createGridviewComponent(options: CreateComponentOptions): GridviewPanel {
- const component = this.components[options.name];
+ const component = this.componentResolver.resolveComponent(options.name);
if (!component) {
throw new Error(
`Component '${options.name}' not found in component registry`
@@ -64,7 +65,7 @@ export class AngularFrameworkComponentFactory {
// For SplitviewComponent
createSplitviewComponent(options: CreateComponentOptions): SplitviewPanel {
- const component = this.components[options.name];
+ const component = this.componentResolver.resolveComponent(options.name);
if (!component) {
throw new Error(
`Component '${options.name}' not found in component registry`
@@ -82,7 +83,7 @@ export class AngularFrameworkComponentFactory {
// For PaneviewComponent
createPaneviewComponent(options: CreateComponentOptions): IPanePart {
- const component = this.components[options.name];
+ const component = this.componentResolver.resolveComponent(options.name);
if (!component) {
throw new Error(
`Component '${options.name}' not found in component registry`
diff --git a/packages/dockview-angular/src/lib/utils/component-registry.service.ts b/packages/dockview-angular/src/lib/utils/component-registry.service.ts
new file mode 100644
index 0000000000..fb640c91ba
--- /dev/null
+++ b/packages/dockview-angular/src/lib/utils/component-registry.service.ts
@@ -0,0 +1,53 @@
+import { Injectable } from '@angular/core';
+import { ComponentReference } from '../types';
+
+export type ComponentResolver = (component: string) => ComponentReference | undefined;
+
+@Injectable({ providedIn: 'root' })
+export class ComponentRegistryService {
+ private readonly components: Map = new Map();
+ private readonly resolver: ComponentResolver[] = [];
+
+ public registerResolver(resolver: ComponentResolver) {
+ this.resolver.push(resolver);
+ }
+
+ public unregisterResolver(resolver: ComponentResolver) {
+ this.resolver.splice(this.resolver.indexOf(resolver), 1);
+ }
+
+ public registerComponents(components: Record) {
+ for (const [component, reference] of Object.entries(components)) {
+ this.registerComponent(component, reference);
+ }
+ }
+
+ public registerComponent(component: string, reference: ComponentReference) {
+ if (!component || !reference) {
+ throw new Error('Component and reference must be provided');
+ }
+
+ this.components.set(component, reference);
+ }
+
+ public resolveComponent(component: string): ComponentReference | undefined {
+ if (!component) {
+ throw new Error('Component must be provided');
+ }
+
+ return this.getComponentReference(component);
+ }
+
+ private getComponentReference(component: string): ComponentReference | undefined {
+ // first, try to get dynamic reference
+ for (const resolver of this.resolver) {
+ const reference = resolver(component);
+ if (reference) {
+ return reference;
+ }
+ }
+
+ // last, try to get static reference
+ return this.components.get(component);
+ }
+}
diff --git a/packages/dockview-angular/src/public-api.ts b/packages/dockview-angular/src/public-api.ts
index 9b2cd8f656..ca8c5f7701 100644
--- a/packages/dockview-angular/src/public-api.ts
+++ b/packages/dockview-angular/src/public-api.ts
@@ -40,7 +40,10 @@ export {
SplitviewAngularReadyEvent,
} from './lib/splitview/types';
+export * from './lib/types';
+
// Utilities
export * from './lib/utils/angular-renderer';
export * from './lib/utils/component-factory';
export * from './lib/utils/lifecycle-utils';
+export * from './lib/utils/component-registry.service';