最近前端框架转移到蚂蚁的Ng-Alain和Ng-Zorro上,需要把我们自己的一些前端数据逻辑对接到Ng-Zorro提供的组件上,以简化前端代码。以前的做法是定义一组自己的自定义组件来包装Ng-Zorro提供的组件,但这个方法的缺点是页面上很难直接使用Ng-Zorro组件的属性、事件和其他特性,除非自定义组件把Ng-Zorro组件的属性和事件都暴露出来,Ng-Zorro组件的属性事件非常多,全部暴露出来太麻烦,不太现实。
突然想到是否能够利用Angular自定义指令控制Ng-Zorro组件,这样页面上放置的不是自定义组件,而是Ng-Zorro组件,然后附加上一个自己写的自定义指令,添加自己的一些属性和事件,页面上仍然可以使用所有的Ng-Zorro组件的属性和事件。
网上找到的Angular自定义属性指令的例子大多都是注入ElementRef和Renderer2,控制html标签的属性。没找到自定义指令控制第三方组件的例子。最先想到的是通过@Input方式传入Ng-Zorro组件的实例。如下,自定义属性指令TreeSelectDirective控制Ng-Zorro的下拉树组件NzTreeSelectComponent
自定义指令的ts文件:
@Directive({
selector: '[myTreeSelect]',
exportAs:'myTreeSelect'
})
export class TreeSelectDirective implements OnChanges,OnDestroy,OnInit,AfterViewInit {
@Input("myTreeSelect")
container:NzTreeSelectComponent=null;
@Input("myProp")
prop:string='';
@Output("ev")
e:EventEmitter<any>=new EventEmitter();
}
页面的HTML模板:
<nz-tree-select #t [myTreeSelect]='t' [myProp]='x' (ev)='onEv($event)' ...
这个方法可以在指令类TreeSelectDirective获得组件nz-tree-select的实例,并且控制nz-tree-select组件,但因为组件实例是在@Input中传入,而@Input是在生命周期的ngOnInit钩子被调用时才传入,而组件的ngOnInit钩子比指令的ngOnInit钩子先被调用,这意味着指令获得组件的实例之前,组件实例的各个输入属性已经完成初始化。这会带来一些问题,Ng-Zorro组件的有些属性初始化之后再设置就无法生效(可能是Zorro的bug),必须在Ng-zorro组件ngOnInit钩子调用前设置这些属性值才能生效,所以通过@Input传入组件实例,无法设置这些属性的值并使其生效。
另外,<nz-tree-select #t [myTreeSelect]='t' 这样的写法也太累赘,不优雅。后来发现,nz-tree-select之类的组件也支持Angular官方ngModel指令,于是查看ngModel指令如何和nz-tree-select组件交互,发现ngModel指令构造器中注入了ControlValueAccessor实例。
然后猜想,既然指令可以注入ControlValueAccessor,是不是也可以直接注入NzTreeSelectComponent,于是试验:
@Directive({
selector: '[myTreeSelect]',
exportAs:'myTreeSelect'
})
export class TreeSelectDirective implements OnChanges,OnDestroy,OnInit,AfterViewInit {
@Input("myProp")
prop:string='';
@Output("ev")
e:EventEmitter<any>=new EventEmitter();
constructor(private container:NzTreeSelectComponent){
// console.info(nzComp);
}
}
页面的HTML模板:
<nz-tree-select myTreeSelect [myProp]='x' (ev)='onEv($event)' ...
测试发现nz-tree-select成功注入指令实例,constructor被调用的时候,nz-tree-select组件的@Input输入属性还没有初始化,可以在constructor中设置nz-tree-select组件的属性。而且代码也更优雅简洁。