Binding Angular2 components inside a jQuery plugin template

I am working on using kendo inside an angular 2 project.

Setting the widget correctly is not a problem:

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

When this starts, the plugin modifies the DOM (and, as I know, without modifying the shadow DOM supported by angular2). My problem is that if I want to use the component anywhere in the plugin, as in the template, angular does not know about its existence and will not bind it.

Example:

 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>` } }]; 

Class 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'); } } 

Is there a β€œright” way to bind these components inside the markup created by widgets? I managed to do this by listening to the binding event from the widget, and then navigating through the elements that it created, and using the DynamicComponentLoader, but it doesn't feel right.

+4
source share
2 answers

I found some details that I need in this thread: https://github.com/angular/angular/issues/6223

I hacked this service to handle the binding of my components:

 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(); }) }); }); } } 

Params:

  • componentType: the class of the component you want to bind. He uses reflection to pull the selector he needs.
  • contextParser: a callback that accepts an existing child html and creates a context object (all you need to initialize the state of the component)
  • componentInitializer is a callback that initializes the created component with the context you are analyzing.

Usage example:

  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 ); 
+1
source

Well, your solution works just fine until the component changes its state and redistributes some things. Since I have not yet found the opportunity to get a ViewContainerRef for an element created outside of Angular (jquery, vanilla js, or even server-side) the first idea was to call detectChanges () by setting the interval. And after several iterations, finally, I came up with a solution that works for me.

Until 2017, you need to replace ComponentResolver with ComponentResolverFactory and do almost the same:

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

After that, you can emulate the attached instance of the component to the change detection cycle by subscribing to the EventEmitters of your NgZone:

  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()); 

Of course, do not forget to unsubscribe from the destruction:

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

UPD after re-stacking

Forget all the words above. It works, but just follow this answer

0
source

All Articles