中文名:手风琴
更清晰的格式,欢迎访问语雀 www.yuque.com/uov16w/tq9o…
组件及属性
- ibm-accordion
- Input
- align: start|end = end
- skeleton: boolean = false;
- Input
- ibm-accordion-item
- Input
- title: string| TemplateRef;
- context: Object | null = null;
- id =
accordion-item-${AccordionItem.accordionItemCount}
- skeleton = false;
- Output
- selected
- Input
Tips
ContentChildren 指令
使用 ng-content 指令可以实现 Content Projection 内容投影,类似Vue的slot,插槽。如果存在多个 ng-content,可以通过 select 指定要投射的位置。
@Component({
selector: 'exe-parent',
template: `
<p>Parent Component</p>
<ng-content></ng-content>
`
})
@Component({
selector: 'my-app',
template: `
<h4>Welcome to Angular World</h4>
<exe-parent>
<exe-child></exe-child>
</exe-parent>
`,
})
ContentChild 是属性装饰器,用来从通过 Content Projection 方式设置的试图中获取匹配的元素。
ContentChildren 属性装饰器,和 ContentChild相比,就是获取匹配的多个元素,返回的结果是一个 QueryList 集合。
那 ContentChildren 和 ViewChildren 有啥区别呢?
- 在父组件的开始结束标签中间放入的 元素,成为 ContentChildren.
- 在组件自己的模板中定义的内容,是组件的一部分,称为 ViewChildren.
ContentChild 和 ViewChild 区别?
- ContentChild 用于获取通过内容投影方式设置到试图中的元素。
- ViewChild 用于从模板中获取匹配的元素
- 在父组件的 ngAfterContentInit 生命周期中,才能获取通过 ContentChild 查询的元素
- 在父组件的 ngAfterViewInit 生命周期中,才能获取通过 ViewChild 查询的元素。
相关文章
- blog.angular-university.io/angular-ng-…
- segmentfault.com/a/119000000… Angular2 ContentChild & ContentChildren
ngTemplateOutlet/ngTemplateOutletContext 使用
codesandbox.io/s/strange-b… 实时尝试
@Component({
selector: '',
template: `
<!--
这里即实现了动态展示模板,并且 ng-container 仅仅是站位标签,
实际dom渲染后并不会有ng-container这一层显示。
<ng-container *ngTemplateOutlet="estimateTemplate" *ngTemplateOutletContext="ctx"></ng-container>
-->
<ng-container *ngTemplateOutlet="estimateTemplate; context: ctx"></ng-container>
<!--
注意,这里的let-xxx,这个xxx是模板中能够使用的变量
estimate 则是 ts 中定义的ctx的属性值。这个对应关系需要注意
-->
<ng-template #estimateTemplate let-lessonsCounter="estimate">
<div> Approximately {{lessonsCounter}} lessons ... </div>
</ng-template>
`})
export class AppoComponent {
total = 10;
ctx = { estimate: this.total }
}
/*
* 或者下面的方式也可以
* 上面是简写,这里是完整写法。
*/
@Component({
selector: "app-root",
template: `
<!--
这里的 $implicit 看着有点怪,不过算是固定写法,
可以看的到,还可以传入其他参数,如示例的 idx。
-->
<ng-container
[ngTemplateOutlet]="estimateTemplate"
[ngTemplateOutletContext]="{ $implicit: estimate, idx: 1 }"
></ng-container>
<ng-template #estimateTemplate let-estimate let-position="idx">
<div>Approximately {{ estimate }} lessons ...</div>
<div>positoin = {{position}}</div>
</ng-template>
`,
styleUrls: ["./app.component.css"]
})
export class AppComponent {
total = 10;
estimate = 20;
}
参考
HostBinding/HostListener
HostBinding 和 HostListener 长得很像,先看一个 HostListener 的 example:
import { Directive, ElementRef, Renderer, HostListener } from '@angular/core';
@Directive({
selector:'[appChbgcolor]'
})
export class ChangeBgColorDirective {
constructor(private el: ElementRef, private renderer: Renderer) {
}
// 这里监听了 mouseover 事件
// 当鼠标移动到该 host 组件时,执行其中的逻辑
@HostListener('mouseover') onMouseOver() {
this.ChangeBgColor('red')
}
@HostListener('click') onClick() {
// click event
}
@HostListener('mouseleave') onMouseLeave() {
// mouseleave event
}
ChangeBgColor(color: string) {
this.renderer.setElementStyle(this.el.nativeElement, 'color', color);
}
}
// 使用该 directive
`
<!-- 我们所说的 host 元素是 appChbgcolor 所在的元素,也就是 div-->
<div appChbgcolor>
<h3>{{title}}</h3>
</div>
`
所以该 example 可以看出 HostListener 指令的作用是处理来自host(托管)元素的事件。
再来看一个 HostBinding 的 example:
// 这里使用 border 变量绑定了 host 元素的 border 属性
@HostBinding('style.border') border: string;
@HostListener('mouseover') onMouseOver() {
// 这里修改 border 变量的值,来达到修改 host 元素 border 属性的目的
this.border = '5px solid green'
}
所以,可以看出 HostBinding 的作用就是修改 host 元素的属性。
参考
import {
Component,
Input,
ContentChildren,
QueryList,
AfterContentInit
} from "@angular/core";
import { AccordionItem } from "./accordion-item.component";
/**
* [See demo](../../?path=/story/components-accordion--basic)
*
* <example-url>../../iframe.html?id=components-accordion--basic</example-url>
*/
@Component({
selector: "ibm-accordion",
template: `
<ul class="bx--accordion"
[class.bx--accordion--end]="align == 'end'"
[class.bx--accordion--start]="align == 'start'">
<ng-content></ng-content>
</ul>
`
})
export class Accordion implements AfterContentInit {
@Input() align: "start" | "end" = "end";
// 获取全部的 accordion-item 组件,便于后面统一设置 skeleton 属性
@ContentChildren(AccordionItem) children: QueryList<AccordionItem>;
protected _skeleton = false;
@Input()
// 这里使用 _skeleton, set 方式,方便当传入的 skeleton 属性变化时,做其他逻辑,
// 如本组件的更新子组件的 skeleton 属性
set skeleton(value: any) {
this._skeleton = value;
this.updateChildren();
}
get skeleton(): any {
return this._skeleton;
}
ngAfterContentInit() {
this.updateChildren();
}
protected updateChildren() {
if (this.children) {
this.children.toArray().forEach(child => child.skeleton = this.skeleton);
}
}
}
import {
Component,
Input,
HostBinding,
Output,
TemplateRef,
EventEmitter
} from "@angular/core";
@Component({
selector: "ibm-accordion-item",
template: `
<button
type="button"
[attr.aria-expanded]="expanded"
[attr.aria-controls]="id"
(click)="toggleExpanded()"
class="bx--accordion__heading">
<svg ibmIcon="chevron--right" size="16" class="bx--accordion__arrow"></svg>
<p *ngIf="!isTemplate(title)"
class="bx--accordion__title"
[ngClass]="{
'bx--skeleton__text': skeleton
}">
{{!skeleton ? title : null}}
</p>
<!-- 这个写法已经在 tips 中说明 -->
<ng-template
*ngIf="isTemplate(title)"
[ngTemplateOutlet]="title"
[ngTemplateOutletContext]="context">
</ng-template>
</button>
<div [id]="id" class="bx--accordion__content">
<ng-content *ngIf="!skeleton; else skeletonTemplate"></ng-content>
<ng-template #skeletonTemplate>
<p class="bx--skeleton__text" style="width: 90%"></p>
<p class="bx--skeleton__text" style="width: 80%"></p>
<p class="bx--skeleton__text" style="width: 95%"></p>
</ng-template>
</div>
`
})
export class AccordionItem {
// 这个静态属性很有意思,用于记录自己是第几个
// 如果一个页面引入了多组 Accordion 组件,每个下有几个 AccordionItem, 那这个值是怎么样的呢,待测试。
static accordionItemCount = 0;
// 表示同时支持 string 和 template 传入,方便自定义
@Input() title: string | TemplateRef<any>;
// 此处的 context 表示当 title 为 templateRef 时,设置的上下文
@Input() context: Object | null = null;
// 这个id通过这种方式实现很简单,不需要引入比如 uuid 之类的库
@Input() id = `accordion-item-${AccordionItem.accordionItemCount}`;
@Input() skeleton = false;
@Output() selected = new EventEmitter();
// 通过上面 HostBinding 的介绍,这里的写法也很清晰
// 给 host 元素设置该类名
@HostBinding("class.bx--accordion__item") itemClass = true;
// 给 host 元素设置类名,默认是 false,通过下面的逻辑来变更
// 同时这里的 expanded 是 @Input,所以是支持作为 props 传入到组件中
@HostBinding("class.bx--accordion__item--active") @Input() expanded = false;
// host 元素的 style display 设置为 list-item
@HostBinding("style.display") itemType = "list-item";
// host 元素的 role 属性设置为 heading
@HostBinding("attr.role") role = "heading";
// host 元素的 aria-level 属性设置 为 3,同时支持作为 props 传入
@HostBinding("attr.aria-level") @Input() ariaLevel = 3;
constructor() {
// 每次实例化该组件时,默认加 1
AccordionItem.accordionItemCount++;
}
public toggleExpanded() {
if (!this.skeleton) {
this.expanded = !this.expanded;
this.selected.emit({id: this.id, expanded: this.expanded});
}
}
public isTemplate(value) {
// 这个判断能够知道 title props 是传入的字符串还是 template,很有参考意义
return value instanceof TemplateRef;
}
}
到这里我们对 accordion 组件也有了一定的了解了。
朋友你好,如果你对我的文章感兴趣,欢迎关注我的 Github (github.com/llccing),或者掘金 (juejin.cn/user/322782…),或者语雀 (www.yuque.com/uov16w),感谢你的支持!