Skip to content

Commit f7e577b

Browse files
authored
Merge pull request #1157 from puschie286/angular-template-ref-support
feat: (Angular) template ref support
2 parents 564a724 + 1774e05 commit f7e577b

21 files changed

Lines changed: 230 additions & 123 deletions

packages/dockview-angular/src/__tests__/angular-renderer.spec.ts

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
EnvironmentInjector,
88
inject,
99
Injector,
10+
TemplateRef,
11+
ViewChild,
1012
} from '@angular/core';
1113
import { AngularRenderer } from '../lib/utils/angular-renderer';
1214

@@ -37,14 +39,31 @@ class TestUpdateComponent {
3739
}
3840
}
3941

42+
@Component({
43+
selector: 'test-template-holder-component',
44+
template: `
45+
<ng-template #template>
46+
<test-update-component />
47+
</ng-template>
48+
`,
49+
})
50+
class TemplateHolderComponent {
51+
@ViewChild('template', { static: true })
52+
public template?: TemplateRef<any>;
53+
}
54+
4055
describe('AngularRenderer', () => {
4156
let injector: Injector;
4257
let environmentInjector: EnvironmentInjector;
4358
let application: ApplicationRef;
4459

4560
beforeEach(async () => {
4661
await TestBed.configureTestingModule({
47-
declarations: [TestComponent],
62+
declarations: [
63+
TestComponent,
64+
TestUpdateComponent,
65+
TemplateHolderComponent,
66+
],
4867
}).compileComponents();
4968

5069
injector = TestBed.inject(Injector);
@@ -127,7 +146,7 @@ describe('AngularRenderer', () => {
127146
jest.spyOn(console, 'error').mockImplementation();
128147

129148
const renderer = new AngularRenderer({
130-
component: null as any,
149+
component: null as never,
131150
injector,
132151
environmentInjector,
133152
});
@@ -168,6 +187,31 @@ describe('AngularRenderer', () => {
168187
}).not.toThrow();
169188
});
170189

190+
it('should render view from template', () => {
191+
// Create component with template
192+
const templateRenderer = new AngularRenderer({
193+
component: TemplateHolderComponent,
194+
injector,
195+
environmentInjector,
196+
});
197+
templateRenderer.init({});
198+
const template = (
199+
templateRenderer.component.instance as TemplateHolderComponent
200+
).template;
201+
202+
expect(template).toBeDefined();
203+
204+
// Create view from template
205+
const renderer = new AngularRenderer({
206+
component: template,
207+
injector: templateRenderer.component.injector, // use container injector to ensure we have a view
208+
});
209+
renderer.init({});
210+
application.tick();
211+
212+
expect(renderer.element.innerHTML).toContain('Counter: 0');
213+
});
214+
171215
it('should render when component is marked for change detection', () => {
172216
const renderer = new AngularRenderer<TestUpdateComponent>({
173217
component: TestUpdateComponent,
@@ -179,7 +223,7 @@ describe('AngularRenderer', () => {
179223
application.tick(); // trigger change detection
180224

181225
expect(renderer.element.innerHTML).toContain('Counter: 0');
182-
renderer.component!.instance.updateCounter();
226+
renderer.component.instance.updateCounter();
183227
application.tick();
184228
expect(renderer.element.innerHTML).toContain('Counter: 1');
185229
});
@@ -199,7 +243,7 @@ describe('AngularRenderer', () => {
199243
});
200244
application.tick();
201245

202-
const instance = renderer.component!.instance;
246+
const instance = renderer.component.instance;
203247
expect(instance.params).toEqual({ title: 'Hello' });
204248
expect(instance.api).toEqual({ mockApi: true });
205249
expect(instance.containerApi).toEqual({ mockContainerApi: true });

packages/dockview-angular/src/__tests__/component-factory.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { TestBed, ComponentFixture } from '@angular/core/testing';
1+
import { TestBed } from '@angular/core/testing';
22
import { Component, Injector, EnvironmentInjector } from '@angular/core';
33
import { AngularFrameworkComponentFactory } from '../lib/utils/component-factory';
44
import { CreateComponentOptions } from 'dockview-core';
@@ -218,8 +218,8 @@ describe('AngularFrameworkComponentFactory', () => {
218218
const renderer = factory.createTabComponent(options);
219219

220220
expect(renderer).toBeDefined();
221-
expect(renderer!.element).toBeTruthy();
222-
expect(renderer!.element.tagName).toBe('TEST-TAB-COMPONENT');
221+
expect(renderer.element).toBeTruthy();
222+
expect(renderer.element.tagName).toBe('TEST-TAB-COMPONENT');
223223
});
224224

225225
it('should use default tab component when specific component not found', () => {
@@ -231,8 +231,8 @@ describe('AngularFrameworkComponentFactory', () => {
231231
const renderer = factory.createTabComponent(options);
232232

233233
expect(renderer).toBeDefined();
234-
expect(renderer!.element).toBeTruthy();
235-
expect(renderer!.element.tagName).toBe('TEST-TAB-COMPONENT');
234+
expect(renderer.element).toBeTruthy();
235+
expect(renderer.element.tagName).toBe('TEST-TAB-COMPONENT');
236236
});
237237

238238
it('should return undefined when no component and no default', () => {
@@ -283,8 +283,8 @@ describe('AngularFrameworkComponentFactory', () => {
283283
factory.createHeaderActionsComponent('header-test');
284284

285285
expect(renderer).toBeDefined();
286-
expect(renderer!.element).toBeTruthy();
287-
expect(renderer!.element.tagName).toBe(
286+
expect(renderer.element).toBeTruthy();
287+
expect(renderer.element.tagName).toBe(
288288
'TEST-HEADER-ACTIONS-COMPONENT'
289289
);
290290
});
@@ -350,7 +350,7 @@ describe('AngularFrameworkComponentFactory', () => {
350350
const renderer = factory.createTabComponent(options);
351351

352352
expect(renderer).toBeDefined();
353-
expect(renderer!.element).toBeTruthy();
353+
expect(renderer.element).toBeTruthy();
354354
});
355355
});
356356

packages/dockview-angular/src/__tests__/dockview-angular.component.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ describe('DockviewAngularComponent', () => {
3434
});
3535

3636
it('should throw error if components input is not provided', () => {
37-
component.components = undefined as any;
37+
component.components = undefined as never;
3838

3939
expect(() => {
4040
component.ngOnInit();
@@ -71,7 +71,7 @@ describe('DockviewAngularComponent', () => {
7171
it('should dispose api on ngOnDestroy', () => {
7272
component.ngOnInit();
7373
const api = component.getDockviewApi();
74-
const disposeSpy = jest.spyOn(api!, 'dispose');
74+
const disposeSpy = jest.spyOn(api, 'dispose');
7575

7676
component.ngOnDestroy();
7777

@@ -81,7 +81,7 @@ describe('DockviewAngularComponent', () => {
8181
it('should handle input changes', () => {
8282
component.ngOnInit();
8383
const api = component.getDockviewApi();
84-
const updateOptionsSpy = jest.spyOn(api!, 'updateOptions');
84+
const updateOptionsSpy = jest.spyOn(api, 'updateOptions');
8585

8686
component.className = 'test-class';
8787
component.ngOnChanges({

packages/dockview-angular/src/__tests__/dockview-context-menu.spec.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ describe('DockviewAngularComponent – context menu', () => {
7272

7373
const renderer = factory({
7474
id: 'test-id',
75-
component: TestContextMenuItemComponent as Type<any>,
75+
component: TestContextMenuItemComponent as Type<never>,
7676
} as CreateContextMenuItemComponentOptions);
7777

7878
expect(renderer).toBeInstanceOf(AngularRenderer);
@@ -100,13 +100,13 @@ describe('DockviewAngularComponent – context menu', () => {
100100

101101
const renderer: AngularRenderer = factory({
102102
id: 'test-id',
103-
component: TestContextMenuItemComponent as Type<any>,
103+
component: TestContextMenuItemComponent as Type<never>,
104104
} as CreateContextMenuItemComponentOptions);
105105

106106
const props: IContextMenuItemComponentProps = {
107-
panel: {} as any,
108-
group: {} as any,
109-
api: {} as any,
107+
panel: {} as never,
108+
group: {} as never,
109+
api: {} as never,
110110
close: jest.fn(),
111111
};
112112

@@ -123,7 +123,8 @@ describe('DockviewAngularComponent – context menu', () => {
123123
const renderer: AngularRenderer<TestContextMenuItemWithInputsComponent> =
124124
factory({
125125
id: 'test-id',
126-
component: TestContextMenuItemWithInputsComponent as Type<any>,
126+
component:
127+
TestContextMenuItemWithInputsComponent as Type<never>,
127128
} as CreateContextMenuItemComponentOptions);
128129

129130
const panel = {} as IDockviewPanel;
@@ -134,14 +135,14 @@ describe('DockviewAngularComponent – context menu', () => {
134135
const props: IContextMenuItemComponentProps = {
135136
panel,
136137
group,
137-
api: {} as any,
138+
api: {} as never,
138139
close: closeFn,
139140
componentProps: extraProps,
140141
};
141142

142143
renderer.init(props);
143144

144-
const instance = renderer.component!.instance;
145+
const instance = renderer.component.instance;
145146
expect(instance.panel).toBe(panel);
146147
expect(instance.group).toBe(group);
147148
expect(instance.close).toBe(closeFn);

packages/dockview-angular/src/__tests__/gridview-angular.component.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ describe('GridviewAngularComponent', () => {
3434
});
3535

3636
it('should throw error if components input is not provided', () => {
37-
component.components = undefined as any;
37+
component.components = undefined as never;
3838

3939
expect(() => {
4040
component.ngOnInit();
@@ -71,7 +71,7 @@ describe('GridviewAngularComponent', () => {
7171
it('should dispose api on ngOnDestroy', () => {
7272
component.ngOnInit();
7373
const api = component.getGridviewApi();
74-
const disposeSpy = jest.spyOn(api!, 'dispose');
74+
const disposeSpy = jest.spyOn(api, 'dispose');
7575

7676
component.ngOnDestroy();
7777

@@ -81,7 +81,7 @@ describe('GridviewAngularComponent', () => {
8181
it('should handle input changes', () => {
8282
component.ngOnInit();
8383
const api = component.getGridviewApi();
84-
const updateOptionsSpy = jest.spyOn(api!, 'updateOptions');
84+
const updateOptionsSpy = jest.spyOn(api, 'updateOptions');
8585

8686
component.className = 'test-class';
8787
component.ngOnChanges({
@@ -101,7 +101,7 @@ describe('GridviewAngularComponent', () => {
101101
it('should handle orientation changes', () => {
102102
component.ngOnInit();
103103
const api = component.getGridviewApi();
104-
const updateOptionsSpy = jest.spyOn(api!, 'updateOptions');
104+
const updateOptionsSpy = jest.spyOn(api, 'updateOptions');
105105

106106
component.orientation = 'vertical';
107107
component.ngOnChanges({
@@ -121,7 +121,7 @@ describe('GridviewAngularComponent', () => {
121121
it('should handle proportional layout changes', () => {
122122
component.ngOnInit();
123123
const api = component.getGridviewApi();
124-
const updateOptionsSpy = jest.spyOn(api!, 'updateOptions');
124+
const updateOptionsSpy = jest.spyOn(api, 'updateOptions');
125125

126126
component.proportionalLayout = true;
127127
component.ngOnChanges({
@@ -141,7 +141,7 @@ describe('GridviewAngularComponent', () => {
141141
it('should handle hideBorders changes', () => {
142142
component.ngOnInit();
143143
const api = component.getGridviewApi();
144-
const updateOptionsSpy = jest.spyOn(api!, 'updateOptions');
144+
const updateOptionsSpy = jest.spyOn(api, 'updateOptions');
145145

146146
component.hideBorders = true;
147147
component.ngOnChanges({
@@ -161,7 +161,7 @@ describe('GridviewAngularComponent', () => {
161161
it('should not call updateOptions on first change', () => {
162162
component.ngOnInit();
163163
const api = component.getGridviewApi();
164-
const updateOptionsSpy = jest.spyOn(api!, 'updateOptions');
164+
const updateOptionsSpy = jest.spyOn(api, 'updateOptions');
165165

166166
component.ngOnChanges({
167167
className: {
@@ -186,7 +186,7 @@ describe('GridviewAngularComponent', () => {
186186
it('should handle multiple property changes at once', () => {
187187
component.ngOnInit();
188188
const api = component.getGridviewApi();
189-
const updateOptionsSpy = jest.spyOn(api!, 'updateOptions');
189+
const updateOptionsSpy = jest.spyOn(api, 'updateOptions');
190190

191191
component.className = 'test-class';
192192
component.hideBorders = true;

packages/dockview-angular/src/__tests__/lifecycle-utils.spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { TestBed } from '@angular/core/testing';
21
import { Observable, Subject, of } from 'rxjs';
3-
import { delay, take } from 'rxjs/operators';
2+
import { delay } from 'rxjs/operators';
43
import {
54
AngularDisposable,
65
AngularLifecycleManager,

packages/dockview-angular/src/__tests__/paneview-angular.component.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ describe('PaneviewAngularComponent', () => {
3434
});
3535

3636
it('should throw error if components input is not provided', () => {
37-
component.components = undefined as any;
37+
component.components = undefined as never;
3838

3939
expect(() => {
4040
component.ngOnInit();
@@ -71,7 +71,7 @@ describe('PaneviewAngularComponent', () => {
7171
it('should dispose api on ngOnDestroy', () => {
7272
component.ngOnInit();
7373
const api = component.getPaneviewApi();
74-
const disposeSpy = jest.spyOn(api!, 'dispose');
74+
const disposeSpy = jest.spyOn(api, 'dispose');
7575

7676
component.ngOnDestroy();
7777

@@ -81,7 +81,7 @@ describe('PaneviewAngularComponent', () => {
8181
it('should handle input changes', () => {
8282
component.ngOnInit();
8383
const api = component.getPaneviewApi();
84-
const updateOptionsSpy = jest.spyOn(api!, 'updateOptions');
84+
const updateOptionsSpy = jest.spyOn(api, 'updateOptions');
8585

8686
component.className = 'test-class';
8787
component.ngOnChanges({
@@ -101,7 +101,7 @@ describe('PaneviewAngularComponent', () => {
101101
it('should not call updateOptions on first change', () => {
102102
component.ngOnInit();
103103
const api = component.getPaneviewApi();
104-
const updateOptionsSpy = jest.spyOn(api!, 'updateOptions');
104+
const updateOptionsSpy = jest.spyOn(api, 'updateOptions');
105105

106106
component.ngOnChanges({
107107
className: {
@@ -126,7 +126,7 @@ describe('PaneviewAngularComponent', () => {
126126
it('should handle multiple property changes at once', () => {
127127
component.ngOnInit();
128128
const api = component.getPaneviewApi();
129-
const updateOptionsSpy = jest.spyOn(api!, 'updateOptions');
129+
const updateOptionsSpy = jest.spyOn(api, 'updateOptions');
130130

131131
component.className = 'test-class';
132132
component.disableAutoResizing = true;
@@ -180,7 +180,7 @@ describe('PaneviewAngularComponent', () => {
180180
it('should handle auto resizing configuration', () => {
181181
component.ngOnInit();
182182
const api = component.getPaneviewApi();
183-
const updateOptionsSpy = jest.spyOn(api!, 'updateOptions');
183+
const updateOptionsSpy = jest.spyOn(api, 'updateOptions');
184184

185185
component.disableAutoResizing = true;
186186
component.ngOnChanges({

0 commit comments

Comments
 (0)