「这是我参与2022首次更文挑战的第36天,活动详情查看:[2022首次更文挑战] (juejin.cn/post/705288… "juejin.cn/post/705288…
angular 用了一年了,结果还不怎么会用它的插槽,暂且就用select。 templateOut等搞搞明白了再来。
ng-content 标签
这个插槽用法简单,和vue的slot 差不多。
在组件里的预留位置放置<ng-content select="[name=sider]"></ng-content>
然后,使用组件的时候。 假设组件标签是app-co
<app-co >
<div name="sider"></div>
</app-co>
和 vue相同,如果不设置 select 那就是个默认插槽<ng-content ></ng-content>,
使用的时候,直接放在组件标签即可。
select 属性
这个select属性类似具名插槽,但是select的值,是css选择器,和document.querySelector()的参数是一样的,
select="head" 表示选择第一个head标签
select=".head" 表示选择第一个class="head"的标签
select="#head" 表示选择第一个id="head"的标签
select="[name=head]" 表示选择第一个name="head"的标签
select="[slot=head]" 表示选择第一个slot="head"的标签
最后那个写法是不是可以和vue以假乱真,^_^。
但是要注意都得写在组件标签内.
ContentChildren
说到插槽,就要说一个典型的组件tabset。点击tab栏切换,好像挺简单的。但是有个地方要注意,用于点击的tab,肯定是在外层组件的,但是却是根据内部组件的数据来渲染的。因此,我们需要在外部组件就获取子组件的数据,并且还要监听子组件的变化。
期望的tab组件如下
<app-tabs>
<app-tab *ngFor="let bar of bars" [barTitle] = "bar.title">
{{bar.content}}
</app-tab>
</app-tabs>
很明显, 这个tabset组件由外层tabs 和内层tab组成。并且在我改变了遍历的bars数组之后,要能改变对应的tab栏和当前显示的内容。这就要使用 contentChildren 了。
用于从内容 DOM 中获取元素或指令的 `QueryList`。每当添加、删除或移动子元素时,此查询列表都会更新,并且其可观察对象 changes 都会发出新值。
在调用 `ngAfterContentInit` 回调之前设置的内容查询。
我们先定义好内部组件tab.就是一个插槽,然后接受一个tab标题,一个点击事件的回调函数。 我这里直接使用display来控制,当前展示的tab页。如果需要滚动动画可以考虑,Element.scrollIntoView()这个方法。
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'app-tab',
template: `
<main [style.display]="display">
<ng-content>
</ng-content>
</main>
`,
styles: [
``
]
})
export class TabComponent implements OnInit {
constructor() { }
@Input()barTitle: '' ;
display = 'none' ;
@Input()clickcb = () => {} ;
ngOnInit(): void {
}
}
然后需要在外部tabs组件里获取子组件的数据,并渲染。 获取的使用方法就是装饰器contentChildren,结果是QueryList,这并不是一个数组,需要调用它的toArray方法转为数组。总体来说这个组件写的比较失败,
import { AfterContentInit, AfterViewInit, ChangeDetectorRef, Component, ContentChildren, Input, OnInit, QueryList, ViewChild, ViewChildren, ViewRef } from '@angular/core';
import { TabComponent } from './tab.component';
import { delay, filter, first, startWith, takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-tabs',
template: `
<div class="top">
<div class="l">
<!-- 这里只是要个标题而已 遍历一个轻量的数组会不会好些-->
<span (click)="toggle(index)" *ngFor="let v of bars; let index = index" [ngClass]="{active:curIndex === index}" class="tab-title">
{{v}}
</span>
</div>
<div class="r">
<ng-content select="[slot=nzTabBarExtraContent]"></ng-content>
</div>
</div>
<main style="padding:5px 0;" >
<ng-content>
</ng-content>
</main>
`,
styles: [
`
.top{
display: flex;
justify-content: space-between;
}
.top .l{
display: flex;
gap:10px ;
}
.tab-title{
padding: 10px 15px 8px 15px;
position: relative;
border-bottom: 2px solid transparent;
cursor: pointer ;
}
.tab-title:hover{
color: #409eff;
}
.tab-title.active{
color: #409eff;
border-bottom-color:#409eff ;
}
`
]
})
export class TabsComponent implements OnInit, AfterViewInit, AfterContentInit {
bars = [];
curIndex = 0;
@Input()
set defualtIndex(v){
/* 这是为了主动设置 活跃的tab 应该有更好的写法 */
this.curIndex = v ;
this.tabArray.length && this.toggle(v) ;
}
tabArray: TabComponent[] = [];
constructor( private cd: ChangeDetectorRef) { }
@ContentChildren(TabComponent) tabs: QueryList<TabComponent>;
ngOnInit(): void {
}
toggle(index: number): void{
this.curIndex = index ;
this.tabArray.forEach((tab) => {
tab.display = 'none' ;
});
this.tabArray[index].display = 'block' ;
this.tabArray[index].clickcb() ;
}
ngAfterViewInit(): void {
}
ngAfterContentChecked(): void {
/* 这个方法会调用多次 并且初始化时会在 init方法后调用 */
}
/*
变更检测期间
更新所有子组件/指令的绑定属性
调用所有子组件/指令的三个生命周期钩子:ngOnInit,OnChanges,ngDoCheck
更新当前组件的 DOM
为子组件执行变更检测(译者注:在子组件上重复上面三个步骤,依次递归下去)
为所有子组件/指令调用当前组件的 ngAfterViewInit 生命周期钩子
属性值突变的罪魁祸首是子组件或指令,*/
ngAfterContentInit(): void {
/* 断点调试发现是子组件先触发这个init 然后是父组件 符合函数调用特征 */
this.updateChidren(null)
this.tabs.changes.pipe(startWith(this.tabs)).subscribe((data)=> {
this.updateChidren(data) ;
})
}
updateChidren(data){
this.tabArray = this.tabs.toArray();
this.bars = [] ;
this.tabArray .forEach((tab, i) => {
this.bars.push( tab.barTitle) ;
tab.display = 'none' ;
});
/* 这里刚好在变更检测之后才去更改 就会导致ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked */
this.tabArray[this.curIndex].display = 'block' ;
// this.cd.detectChanges() ;// 增加一个变更检测 加了之后疯狂报错应该这个时候有些数据又没了
// this.tabs.notifyOnChanges(); // 通知变更吗 结果栈溢出了,无限触发检查? 应该是不能和上面的混用, 不行
console.log(this.tabArray, this.bars, data) ;
}
}