在 Jquery 插件模板中绑定 Angular2 组件

Posted

技术标签:

【中文标题】在 Jquery 插件模板中绑定 Angular2 组件【英文标题】:Binding Angular2 components inside of a Jquery plugin template 【发布时间】:2016-12-02 01:53:30 【问题描述】:

我正在 Angular 2 项目中使用剑道。

正确设置小部件没有问题:

ngOnInit() 
    let options = inputsToOptionObject(KendoUIScheduler, this);
    options.dataBound = this.bound;
    this.scheduler = $(this.element.nativeElement)
        .kendoScheduler(options)
        .data('kendoScheduler');


当它运行时,插件会修改 DOM(据我所知,不会修改由 angular2 维护的影子 DOM)。我的问题是,如果我想在插件内部的任何地方使用组件,比如在模板中,Angular 不知道它的存在并且不会绑定它。

例子:

public views:kendo.ui.SchedulerView[] = [
    type: 'month',
    title: 'test',
    dayTemplate: (x:any) => 
        let date = x.date.getDate();
        let count = this.data[date];
        return `<monthly-scheduler-day [date]="test" [count]=$count"></monthly-scheduler-day>`
    
];

每月调度日课程:

@Component(
    selector: 'monthly-scheduler-day',
    template: `
            <div>date</div>
            <div class="badge" (click)=dayClick($event)>Available</div>
    `
)
export class MonthlySchedulerDayComponent implements OnInit
    @Input() date: number;
    @Input() count: number;
    constructor() 
        console.log('constructed');
    
    ngOnInit()            
        console.log('created');
    

    dayClick(event)
        console.log('clicked a day');
    


是否有一种“正确”的方式将这些组件绑定到小部件创建的标记中?我已经设法通过侦听来自小部件的绑定事件然后循环遍历它创建的元素并使用 DynamicComponentLoader 来做到这一点,但感觉不对。

【问题讨论】:

【参考方案1】:

在组件需要更改其状态并重新渲染某些内容之前,您的解决方案可以正常工作。 因为我还没有找到任何能力为在 Angular 之外生成的元素(jquery、vanilla js 甚至服务器端)获取 ViewContainerRef 第一个想法是通过设置间隔来调用detectChanges()。经过几次迭代,我终于找到了一个适合我的解决方案。

到目前为止,在 2017 年,您必须将 ComponentResolver 替换为 ComponentResolverFactory 并执行几乎相同的操作:

    let componentFactory = this.factoryResolver.resolveComponentFactory(componentType),
        componentRef = componentFactory.create(this.injector, null, selectorOrNode);

    componentRef.changeDetectorRef.detectChanges();

之后,您可以通过订阅其 NgZone 的 EventEmitters 来模拟将组件实例附加到更改检测周期:

    let enumerateProperties = obj => Object.keys(obj).map(key => obj[key]),
        properties = enumerateProperties(injector.get(NgZone))
                         .filter(p => p instanceof EventEmitter);

    let subscriptions = Observable.merge(...properties)
                                  .subscribe(_ => changeDetectorRef.detectChanges());

当然不要忘记取消订阅:

    componentRef.onDestroy(_ => 
        subscriptions.forEach(x => x.unsubscribe());
        componentRef.changeDetectorRef.detach();
    );

再次***后的UPD

忘记上面的所有单词。它有效,但只需遵循此answer

【讨论】:

【参考方案2】:

我在这个帖子中找到了一些我需要的细节:https://github.com/angular/angular/issues/6223

我启动了这项服务来处理绑定我的组件:

import  Injectable, ComponentMetadata, ViewContainerRef, ComponentResolver, ComponentRef, Injector  from '@angular/core';

declare var $:JQueryStatic;

@Injectable()
export class JQueryBinder 
    constructor(
        private resolver: ComponentResolver,
        private injector: Injector
    )

    public bindAll(
        componentType: any, 
        contextParser:(html:string)=>, 
        componentInitializer:(c: ComponentRef<any>, context: )=>void): 
            void 
        
        let selector = Reflect.getMetadata('annotations', componentType).find((a:any) => 
            return a instanceof ComponentMetadata
        ).selector;

        this.resolver.resolveComponent(componentType).then((factory)=> 
            $(selector).each((i,e) => 
                let context = contextParser($(e).html());
                let c = factory.create(this.injector, null, e);
                componentInitializer(c, context);
                c.changeDetectorRef.detectChanges();
                c.onDestroy(()=>
                    c.changeDetectorRef.detach();
                )
            );
        );        
    

参数:

componentType:要绑定的组件类。它使用反射来拉取它需要的选择器 contextParser:接受现有子 html 并构造上下文对象的回调(初始化组件状态所需的任何东西) componentInitializer - 使用您解析的上下文初始化创建的组件的回调

示例用法:

    let parser = (html: string) => 
        return 
            date: parseInt(html)
        ;
    ;

    let initer =  (c: ComponentRef<GridCellComponent>, context:  date: number )=>
        let d = context.date;

        c.instance.count = this.data[d];
        c.instance.date = d;
    

    this.binder.bindAll(GridCellComponent, parser, initer );

【讨论】:

以上是关于在 Jquery 插件模板中绑定 Angular2 组件的主要内容,如果未能解决你的问题,请参考以下文章

如何在Angular2中使用jQuery及其插件的方法

angular2采用自定义指令(Directive)方式加载jquery插件

Angular2:导入引导插件

出位的template.js 基于jquery的模板渲染插件,简单好用

在Angular 2中的Jquery Datatable中绑定按钮事件

Angular2动态组件和TP jQuery库