Skip to content

Commit 29b087c

Browse files
Devextreme-angular: Fix popup resizing after show by popupService (DevExpress#28934)
- changed order of adding content component for correct repaint timing - get rid of AplicationRef.tick() - insert DOM by NG renderer - extend test
1 parent fab13e4 commit 29b087c

File tree

7 files changed

+756
-751
lines changed

7 files changed

+756
-751
lines changed

packages/devextreme-angular/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"codelyzer": "6.0.2",
4343
"core-js": "2.6.12",
4444
"cross-env": "7.0.3",
45+
"css-loader": "6.10.0",
4546
"del": "2.2.2",
4647
"eslint": "8.56.0",
4748
"gulp": "4.0.2",
@@ -60,6 +61,7 @@
6061
"reflect-metadata": "0.1.13",
6162
"rxjs": "7.8.1",
6263
"stream-browserify": "3.0.0",
64+
"style-loader": "3.3.4",
6365
"tslib": "2.6.3",
6466
"typescript": "5.4.5",
6567
"webpack": "5.96.1",

packages/devextreme-angular/src/ui/popup/service/insertion.directive.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import {
2-
AfterViewInit,
3-
Component, ComponentRef,
2+
OnInit,
3+
Component,
4+
ComponentRef,
45
ElementRef,
56
Inject,
67
NgZone,
78
PLATFORM_ID,
8-
TransferState, Type,
9+
TransferState,
10+
Type,
911
ViewChild,
12+
ViewContainerRef,
1013
} from '@angular/core';
1114
import {
1215
DxTemplateHost,
@@ -15,21 +18,19 @@ import {
1518
WatcherHelper,
1619
} from 'devextreme-angular/core';
1720
import { DxPopupComponent, DxPopupTypes } from '../component';
18-
import { DxServicePopupInsertionDirective } from './insertion.directive';
1921

2022
@Component({
2123
standalone: true,
22-
imports: [DxServicePopupInsertionDirective],
2324
providers: [
2425
DxTemplateHost,
2526
WatcherHelper,
2627
NestedOptionHost,
2728
IterableDifferHelper,
2829
],
29-
template: '<ng-template popup-content-insertion></ng-template>',
30+
template: `<ng-container #dxPopupContentContainer></ng-container>`,
3031
})
31-
export class PopupServiceComponent<T> extends DxPopupComponent implements AfterViewInit {
32-
@ViewChild(DxServicePopupInsertionDirective) contentInsertion: DxServicePopupInsertionDirective;
32+
export class PopupServiceComponent<T> extends DxPopupComponent implements OnInit {
33+
@ViewChild('dxPopupContentContainer', { read: ViewContainerRef, static: true }) contentContainer!: ViewContainerRef;
3334

3435
contentRef: ComponentRef<T>;
3536

@@ -48,13 +49,13 @@ export class PopupServiceComponent<T> extends DxPopupComponent implements AfterV
4849
super(elementRef, ngZone, templateHost, _watcherHelper, _idh, optionHost, transferState, platformId);
4950
}
5051

51-
ngAfterViewInit() {
52-
super.ngAfterViewInit();
52+
ngOnInit() {
53+
super.ngOnInit();
5354

54-
if(this.popupOptions) {
55-
this.instance.option(this.popupOptions)
55+
if (this.popupOptions) {
56+
this.instance.option(this.popupOptions);
5657
}
5758

58-
this.contentRef = this.contentInsertion?.viewContainerRef.createComponent(this.contentComponent);
59+
this.contentRef = this.contentContainer.createComponent(this.contentComponent);
5960
}
6061
}

packages/devextreme-angular/src/ui/popup/service/service.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import {
44
ComponentFactoryResolver,
55
Injector,
66
EmbeddedViewRef,
7-
ComponentRef, Type,
7+
ComponentRef,
8+
Type,
9+
RendererFactory2,
810
} from '@angular/core';
911
import { DxPopupComponent, DxPopupTypes } from '../component';
1012
import { PopupServiceComponent } from './service.component';
@@ -19,6 +21,7 @@ export class DxPopupService {
1921
private readonly injector: Injector,
2022
private readonly applicationRef: ApplicationRef,
2123
private readonly componentFactoryResolver: ComponentFactoryResolver,
24+
private readonly rendererFactory: RendererFactory2,
2225
) {}
2326

2427
open<T>(contentComponent: Type<T>, popupOptions?: DxPopupTypes.Properties): DxPopupServiceComponent<T> {
@@ -39,16 +42,17 @@ export class DxPopupService {
3942
componentRef.destroy();
4043
});
4144

45+
componentRef.changeDetectorRef.detectChanges();
46+
4247
this.applicationRef.attachView(componentRef.hostView);
4348

4449
const domElem = (componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement;
50+
const renderer = this.rendererFactory.createRenderer(null, null);
4551

46-
document.body.appendChild(domElem);
52+
renderer.appendChild(document.body, domElem);
4753

4854
cmpInstance.visible = true;
4955

50-
this.applicationRef.tick();
51-
5256
return componentRef.instance;
5357
}
5458
}
Lines changed: 66 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,86 @@
1-
import { TestBed } from '@angular/core/testing';
2-
import { DxPopupService } from 'devextreme-angular/ui/popup';
3-
import { DxButtonModule } from 'devextreme-angular';
4-
import {Component, Input } from '@angular/core';
1+
import { TestBed, fakeAsync, tick } from '@angular/core/testing';
2+
import { Component, Input } from '@angular/core';
3+
import { DxPopupService, type DxPopupTypes } from 'devextreme-angular/ui/popup';
4+
import { DxButtonModule } from 'devextreme-angular/ui/button';
55

6-
@Component({
7-
selector: 'stub-component',
8-
template: '<div class="stub"></div>'
9-
})
10-
class StubComponent {}
6+
import 'devextreme/dist/css/dx.light.css';
117

128
@Component({
13-
standalone: true,
14-
imports: [DxButtonModule],
15-
selector: '',
16-
template: `<div class="popup-content">
9+
standalone: true,
10+
imports: [DxButtonModule],
11+
selector: '',
12+
template: `<div class="popup-content" style="height:300px">
1713
<dx-button text="Test Button" (onClick)="onClick()"></dx-button>
18-
</div>`
14+
</div>`,
1915
})
2016
class PopupContentComponent {
21-
@Input() onClick = () => {};
17+
@Input() onClick = () => {};
2218
}
2319

2420
describe('Using DxPopupService', () => {
25-
let popupService: DxPopupService;
21+
let popupService: DxPopupService;
22+
23+
beforeEach(() => {
24+
popupService = TestBed.inject(DxPopupService);
25+
});
26+
27+
it('DxPopupService opens DxPopup with component passed as argument', fakeAsync(() => {
28+
const TITLE_MIN_WIDTH = 150;
29+
const POPUP_CONTENT_MAX_HEIGHT = 240;
30+
const popupOptions: DxPopupTypes.Properties = {
31+
showTitle: true,
32+
showCloseButton: true,
33+
title: 'TEST-POPUP-TITLE',
34+
height: 400,
35+
width: 600,
36+
toolbarItems: [{
37+
location: 'after',
38+
toolbar: 'bottom',
39+
widget: 'dxButton',
40+
options: {
41+
text: 'Disable',
42+
},
43+
}],
44+
onShown() {
45+
isPopupHidden = false;
46+
},
47+
};
2648

27-
beforeEach(() => {
28-
TestBed.configureTestingModule({
29-
declarations: [StubComponent],
30-
imports: [],
31-
providers: [],
32-
});
49+
let isPopupHidden = true;
3350

34-
popupService = TestBed.inject(DxPopupService);
51+
const popupRef = popupService.open(PopupContentComponent, popupOptions);
52+
53+
popupRef.onHidden.subscribe(() => {
54+
isPopupHidden = true;
3555
});
3656

37-
it('DxPopupService opens DxPopup with component passed as argument', (done) => {
38-
const fixture = TestBed.createComponent(StubComponent);
39-
const popupRef = popupService.open(PopupContentComponent, { showTitle: true, showCloseButton: true });
57+
expect(popupRef.contentRef.instance).toBeTruthy();
4058

41-
popupRef.onHidden.subscribe(() => {
42-
done();
43-
});
59+
popupRef.contentRef.instance.onClick = () => {
60+
popupRef.visible = false;
61+
};
4462

45-
popupRef.contentRef.instance.onClick = () => {
46-
popupRef.visible = false;
47-
};
63+
const popupEl: HTMLElement = document.querySelector('.dx-overlay-wrapper.dx-popup-wrapper .dx-overlay-content.dx-popup-normal');
64+
const popupCloseEl = popupEl.querySelector('.dx-toolbar .dx-button .dx-icon-close');
65+
const popupContentButtonEl: HTMLButtonElement = popupEl.querySelector('.popup-content .dx-button');
4866

49-
fixture.detectChanges();
67+
const popupTitleBarEl: HTMLElement = popupEl.querySelector('.dx-toolbar .dx-toolbar-before .dx-toolbar-label');
68+
const popupContentEl: HTMLElement = popupEl.querySelector('.dx-popup-content');
5069

51-
const popupEl = document.querySelector('.dx-overlay-wrapper.dx-popup-wrapper .dx-overlay-content.dx-popup-normal');
52-
const popupCloseEl = popupEl.querySelector('.dx-toolbar .dx-button .dx-icon-close');
53-
const popupContentComponentEl: HTMLButtonElement = popupEl.querySelector('.popup-content .dx-button');
70+
tick(500);
5471

55-
expect(popupEl).toBeTruthy();
56-
expect(popupCloseEl).toBeTruthy();
57-
expect(popupRef.contentRef).toBeTruthy();
58-
expect(popupContentComponentEl.textContent).toEqual('Test Button');
72+
expect(isPopupHidden).toBeFalsy();
73+
expect(popupEl).toBeTruthy();
74+
expect(popupCloseEl).toBeTruthy();
75+
expect(Number.parseInt(popupTitleBarEl.style.maxWidth, 10)).toBeGreaterThan(TITLE_MIN_WIDTH);
76+
expect(Number.parseInt(popupContentEl.style.height, 10)).toBeLessThan(POPUP_CONTENT_MAX_HEIGHT);
77+
expect(popupContentButtonEl.textContent).toEqual('Test Button');
5978

60-
popupContentComponentEl.click();
61-
});
79+
popupContentButtonEl.click();
80+
81+
tick(500);
82+
83+
expect(isPopupHidden).toBeTruthy();
84+
expect(popupRef.contentRef.hostView.destroyed).toBeTruthy();
85+
}));
6286
});

packages/devextreme-angular/webpack.test.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ module.exports = {
1313
resolve: {
1414
fullySpecified: false,
1515
},
16-
}
16+
},
17+
{
18+
test: /\.css$/,
19+
use: ['style-loader', 'css-loader']
20+
}
1721
],
1822
}]
1923
}

0 commit comments

Comments
 (0)