:host ::ng-deep .ant-modal-body定位失败问题
问题描述
这个问题是在修改nzModal的内置样式过程中遇到的,欲修改内容为 nzModal组件中,content的pading、margin以及nzModal中子组件的样式等。
这些修改并不能直接通过官方提供的api进行修改,但也不是通过标题中的:host ::ng-deep .ant-modal-body进行修改的。虽然:host ::ng-deep .ant-modal-body无法生效,但需要先理解这组选择器的含义。
选择器:host ::ng-deep .ant-modal-body由三部分构成::host的官方表述(Angular - 组件样式)是:"每个组件都会关联一个与其组件选择器相匹配的元素。这个元素称为宿主元素,模板会渲染到其中。:host
伪类选择器可用于创建针对宿主元素自身的样式,而不是针对宿主内部的那些元素。"我个人理解是,:host就是本组件的根选择器。一般来说,:host后面还会加上本组件中的其他DOM元素,表示样式限定在本组件样式中。
::ng-deep也是伪选择器,不指代实际的DOM元素,表示选择器的深入关系。在应用中,一般是A ::ng-deep B的形式,表示从父级选择器A开始,逐层递进,样式的最终目标作用点是选择器B。A和B之间可能间隔多层DOM元素。
.ant-modal-body是nzModal中的内部封装的一个class。利用浏览器的开发者工具能观察到,如下所示的红色部分即为.ant-modal-body类。这个类跟普通的DOM元素一样,只要能在css文件中正确选择出来,就能非常方便的修改这个页面。其他封装好的zorro组件也能通过这个方式查看类名。
因此,:host ::ng-deep .ant-modal-body选择器就表示在当前组件中,选择类名为.ant-modal-body的DOM元素。
看起来确实能够解决问题,但实际上并未生效。这说明:host中并没有.ant-modal-body类。
第二次尝试 ::ng-deep .ant-modal-body
既然:host中并没有.ant-modal-body,那么去掉:host,直接使用::ng-deep .ant-modal-body. 结果表明已经生效.问题看似已经解决,但是实际上,其他组件中的modal也被渲染了,即污染了别的组件.这是由于::ng-deep左边未赋选择器,则表示在解析后的整个html文件中,寻找类名为.ant-modal-body的目标DOM,但是其他组件中也包含了这个nzModal,因此也有这个类.ant-modal-body . 这显然不符合需求.
因此,需要进一步细化.ant-modal-body的匹配程度.如果能够找到这个组件中.ant-modal-body的父级DOM,并为其特殊命名,最终形成这样的格式"::ng-deep 父级选择器 .ant-modal-body ",应该能解决问题.
第三次尝试 ::ng-deep 父级选择器 .ant-modal-body
这次将整个nz-Modal标签设置类名:class="myClass",设置样式如下:::ng-deep .myClass .ant-modal-body.最后发现,仍然不生效.思路正确,但结果不对,应该是class设置错误.
第四次尝试 ::ng-deep 父级选择器 .ant-modal-body
千辛万苦查阅资料后,大佬(https://github.com/NG-ZORRO/ng-zorro-antd/issues/5955)给出了一个解决方案:设置class方式为:nzClassName="myClass",而不是class="myClass".最后,使用::ng-deep .myClass .ant-modal-body即可成功.
原因分析:为什么host不包含ant-modal-body?
失败 | 原因 |
:host ::ng-deep .ant-modal-body | host不包含ant-modal-body |
::ng-deep .ant-modal-body | 污染其他组件 |
::ng-deep .myClass .ant-modal-body(其中myClass为普通class) | 普通class不包含ant-modal-body |
::ng-deep .myClass .ant-modal-body(其中myClass为ngClass) | 成功 |
上表总结了四次尝试,有几个关键问题值得探究:
为什么host不包含ant-modal-body?
其实host的作用范围是<app-root>(参看css - Modal Dialog of different widths are not applied with :host and :ng-deep - Stack Overflow),这一点可以在浏览器中查看渲染后的html页面,modal与app-root实际上是并列关系,而不是包含关系(下图展示了app-root和modal的位置关系),这与代码中,modal的位置无关.第三次尝试的失败也是这一原因,因为普通class仍属于app-root,那也就不包含ant-modal-body了.
本次BUG使用的示例代码
为演示""ng-deep的渗透作用,定义了两个component:APPComponent和AnotherComponent.如下:
app.component.ts
export class AppComponent {
isVisible = false;
constructor(private modalService: NzModalService) { }
showModal(): void {
this.isVisible = true;
}
handleOk(): void {
this.isVisible = false;
}
handleCancel(): void {
this.isVisible = false;
}
showConfirm(): void {
this.modalService.confirm({
nzTitle: 'Confirm',
nzContent: 'Bla bla ...',
nzOkText: 'OK',
nzCancelText: 'Cancel'
});
}
}
app.component.html
<div class="div-button">
<button nz-button nzType="primary" (click)="showModal()">AnotherModal</button>
<nz-modal [(nzVisible)]="isVisible" nzTitle="Modal" nzOkText="Ok" nzCancelText="Cancel" (nzOnOk)="handleOk()"
(nzOnCancel)="handleCancel()">
<ng-container *nzModalContent>
<p>Bla bla ...</p>
<p>Bla bla ...</p>
<p>Bla bla ...</p>
</ng-container>
</nz-modal>
</div>
<br />
app.component.less
::ng-deep .myClass .ant-modal-body {
padding: 0;
height: 500px;
width: 100%;
background-color: red;
}
:host {
font-size: 28px;
}
another.component.ts
export class AnotherComponent {
isVisible = false;
constructor(private modalService: NzModalService) { }
showModal(): void {
this.isVisible = true;
}
handleOk(): void {
this.isVisible = false;
}
handleCancel(): void {
this.isVisible = false;
}
showConfirm(): void {
this.modalService.confirm({
nzTitle: 'Confirm',
nzContent: 'Bla bla ...',
nzOkText: 'OK',
nzCancelText: 'Cancel'
});
}
}
another.component.html
<div class="div-button">
<button nz-button nzType="primary" (click)="showModal()">AnotherModal</button>
<nz-modal [(nzVisible)]="isVisible" nzTitle="Modal" nzOkText="Ok" nzCancelText="Cancel" (nzOnOk)="handleOk()"
(nzOnCancel)="handleCancel()">
<ng-container *nzModalContent>
<p>Bla bla ...</p>
<p>Bla bla ...</p>
<p>Bla bla ...</p>
</ng-container>
</nz-modal>
</div>
<br />
another.component.less为空
ERROR Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'. Expression location: UserManageComponent component.
翻译过来,就表示angular在检查了相关属性后,又变更了这个属性值或变量值.
这个报错对功能暂时没有影响,报错来源:同一个页面先后两次调用同一个modal,分别用来新增和编辑.当先点击编辑,再点击新增时,就会报错.这主要是由于,编辑时,传递了当前表单初始值,angular完成校验工作后,又打开新增页面,这时就会报错.
网上有一些解决办法: