最近遇到个问题,ngx-treeview这个控件不支持原生的异步数据加载,也没有点击展开时的“onExpandCollapse”事件,导致只能一次性地把全部数据加载进去,很慢。我并非前端开发人员,对typescript、angular这些都不太熟悉,所以花了两天时间才解决,以此记录。
ngx-treeview官方文档:ngx-treeview - npm (npmjs.com)
以及小例子:ngx-treeview (leovo2708.github.io)
我的方法:
自定义ng-template,所有的事件都自己写。
它大致长个样子:
每一级的下级都是在点击“展开”后才加载的。以下为实现细节:
component.html:
<div class="form-group">
<ngx-treeview [config]="config"
[items]="itemsManager"
[itemTemplate]="itemTemplate"
(selectedChange)="selectedValues = $event">
</ngx-treeview>
</div>
<ng-template #itemTemplate let-item="item" let-onCollapseExpand="onCollapseExpand" let-onCheckedChange="onCheckedChange">
<div class="form-inline row-item" style="display: flex;align-items: center;flex-flow: row wrap;">
<i style="margin-top: -.3rem;" *ngIf="item.value>=0" (click)="orgUnfold(item)" aria-hidden="true" class="fa" [class.fa-caret-right]="item.collapsed"
[class.fa-caret-down]="!item.collapsed"></i>
<i style="margin-top: -.3rem;width: 13px;" *ngIf="item.value>=0" aria-hidden="true"> </i>
<div class="form-check">
<input type="checkbox" class="form-check-input" *ngIf="item.value>=0" (ngModelChange)="choseItem(item)" [disabled]="item.disabled" [(ngModel)]="item.checked">
<!--[indeterminate]="item.indeterminate" />-->
<!--<i aria-hidden="true" [class]="item.value['icon']"></i>-->
<label class="form-check-label" (click)="choseItem(item);item.checked=!item.checked">
{{item.text}}
</label>
</div>
</div>
</ng-template>
component.ts:
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {OrgService} from '../../../_services';
import {ToastrService} from 'ngx-toastr';
import {DownlineTreeviewItem, TreeviewConfig, TreeviewItem} from 'ngx-treeview';
declare var $:any;
@Component({
selector: 'app-selectTree-custs',
templateUrl: './select-custs-treeview.component.html',
styleUrls: ['./select-custs-treeview.component.css']
})
export class SelectCustsTreeComponent implements OnInit {
config: any;
selectedValues: any;
itemsManager: TreeviewItem[];
<!-- 从父控件输入itemsManagerPrev -->
@Input() itemsManagerPrev: TreeviewItem[];
<!-- 输出给父控件的事件onSend,每次checkbox变化的时候将变化值发给父控件 -->
@Output() onSend: EventEmitter<number[]> = new EventEmitter<number[]>();
<!-- 输出给父控件的事件onSendTree,每次checkbox变化的时候将树选择器的整体数据同步给父控件 -->
@Output() onSendTree: EventEmitter<TreeviewItem[]> = new EventEmitter<TreeviewItem[]>();
constructor(private orgService:OrgService,
private toastr:ToastrService) {
}
ngOnInit() {
this.config = TreeviewConfig.create({
hasAllCheckBox: false,
hasFilter: false,
hasCollapseExpand: false,
decoupleChildFromParent: true,
maxHeight: 400
});
if (!this.itemsManagerPrev) {
this.getOrgRoot();
} else {
this.itemsManager = this.itemsManagerPrev;
}
}
<!-- 初始化第一层数据 -->
getOrgRoot() {
this.orgService.getChildOrg(0).subscribe( <!-- 这个是调用自己服务的接口 -->
result => {
let trItems = [];
result.forEach(orgInfo => {
const trItem = new TreeviewItem({
text: orgInfo.label,
value: orgInfo.value,
children: [new TreeviewItem({text: "查询中...", disabled: true, value: -10000, checked: false})],
collapsed: true,
checked: false,
disabled: false
});
trItems.push(trItem);
});
this.itemsManager = trItems;
},
err => {
this.toastr.error(err.error.msg);
}
)
}
<!-- 某一行点击展开时,触发该函数 -->
<!-- 定义了一些特殊值,-10000代表这个节点的下一级节点数据已经有了,-20000代表这个节点没有下一级了 -->
orgUnfold(item: TreeviewItem) {
if (!item.collapsed) {
item.collapsed = true;
return;
} else if (item.children[0].value != -10000) {
item.collapsed = false;
return;
}
item.collapsed = false;
this.orgService.getChildOrg(item.value).subscribe(
result => {
let trItems = [];
var itemChecked = item.checked;
result.forEach(orgInfo => {
const trItem = new TreeviewItem({
text: orgInfo.label,
value: orgInfo.value,
children: [new TreeviewItem({text: "查询中...", disabled: true, value: -10000, checked: false})],
collapsed: true,
checked: false,
disabled: false
});
trItems.push(trItem);
});
if (trItems.length > 0) {
item.children = trItems;
item.checked = itemChecked;
} else {
item.children = [new TreeviewItem({text: "无结果", disabled: true, value: -20000, checked: false})];
item.checked = itemChecked;
}
this.onSendTree.emit(this.itemsManager);
},
err => {
var itemChecked = item.checked;
this.toastr.error(err.error.msg);
item.collapsed = true;
item.children = [new TreeviewItem({text: "查询中...", disabled: true, value: -10000, checked: false})];
item.checked = itemChecked;
}
)
}
<!-- 每当节点被点击,checkbox变化的时候触发 -->
choseItem(item) {
console.log("selectedValues="+this.selectedValues);
if (item.value<0) {
return;
}
console.log("selectedOrgIds:"+item.checked);
if (!item.checked) {
this.onSend.emit([1,item.value]); <!-- 向父控件同步数据 -->
} else {
this.onSend.emit([-1,item.value]); <!-- 向父控件同步数据 -->
}
this.onSendTree.emit(this.itemsManager); <!-- 向父控件同步数据 -->
}
}
父控件调用这个选择树:
(这里的父控件是一个按钮弹窗,以下是按钮以及弹窗的代码)
<!-- 非必要代码省略 -->
<button class="btn btn-sm btn-success" (click)="selectOrg(OrgFilter)">{{orgButtonName | limitLength:6}}</button>
<!-- 非必要代码省略 -->
<ng-template #OrgFilter>
<div class="modal-header">
<h4 class="modal-title pull-left">选择组织</h4>
<button type="button" class="close pull-right" aria-label="Close" (click)="orgModal.hide()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<app-selectTree-custs (onSend) = "selectedOrgChanged($event)" (onSendTree) = "refreshTree($event)" [itemsManagerPrev] = "orgTree">
</app-selectTree-custs>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default pull-left" (click)="orgReset()">重置</button>
<button type="button" class="btn btn-primary pull-right" (click)="orgFilter()">确定</button>
</div>
</ng-template>
selectedOrgIds: number[];
orgTree: any;
orgButtonName: string = '选择客户';
...
constructor(
this.selectedOrgIds = [];
this.orgTree = null;
...
)
...
selectOrg(template:TemplateRef<any>){
this.orgModal = this.modalService.show(template,{ class: 'modal-sm' });
}
<!-- 同步treeview子控件中复选框选择的数据 -->
selectedOrgChanged(content: number[]) {
if (content[0] == -1) {
var index = 0;
while (index != -1) {
index = this.selectedOrgIds.indexOf(content[1]);
if(index != -1) {
this.selectedOrgIds.splice(index,1);
}
}
} else {
var index = this.selectedOrgIds.indexOf(content[1]);
if (index == -1) {
this.selectedOrgIds.push(content[1]);
}
}
console.log("this.selectedOrgIds:",this.selectedOrgIds);
}
<!-- 从treeview子控件同步它的全部数据 -->
refreshTree(orgTree: any) {
this.orgTree = orgTree;
}
<!-- 确定按钮 -->
orgFilter(){
var selOrgs = this.selectedOrgIds.toString();
this.teamid = this.activatedRoute.snapshot.paramMap.get('teamid');
if (this.teamid != null || this.teamid != undefined) {
this.getTeamUsers(this.teamid, this.page,this.keyword); <!-- 调服务接口的函数 -->
} else {
this.getUsers(this.page,this.keyword,selOrgs); <!-- 调服务接口的函数 -->
}
this.orgModal.hide();
if (this.selectedOrgIds.length > 0) {
this.orgButtonName = '已选' + this.selectedOrgIds.length + '个组织';
} else {
this.orgButtonName = '选择组织';
}
}
<!-- 重置按钮 -->
orgReset(){
var setCheckedFalse = function(item) {
item.checked = false;
if (item.children == null) {
return;
}
item.children.forEach(ch => {
ch.checked = false;
setCheckedFalse(ch);
});
}
this.orgTree.forEach(item => {
setCheckedFalse(item);
});
this.selectedOrgIds = [];
}