4

Ich arbeite an der Verwendung eines Kendo in einem eckigen 2-Projekt.Binding Angular2-Komponenten innerhalb einer Jquery-Plugin-Vorlage

das Widget richtig eingestellt zu bekommen ist kein Problem:

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

} 

Wenn das läuft, das Plugin modifiziert das DOM (und zu meiner knowleged, ohne modifiying den Schatten DOM von angular2 gehalten). Mein Problem ist, dass, wenn ich eine Komponente irgendwo innerhalb des Plugins verwenden möchte, wie in einer Vorlage, Angular seine Existenz nicht kennt und es nicht bindet.

Beispiel:

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

Der monatlichen-Scheduler-Tages-Kurs:

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

} 

Gibt es einen "richtigen" Weg, um diese Komponenten im Innern des Markup durch das Widget erstellt zu binden? Ich habe es geschafft, indem ich auf das Bindungsereignis vom Widget gelauscht habe und dann die Elemente, die es erstellt hat, mit dem DynamicComponentLoader durchlaufen habe, aber es fühlt sich falsch an.

Antwort

0

ich einige Details fand ich in diesem Thread benötigt: https://github.com/angular/angular/issues/6223

ich diesen Dienst peitschte meine Komponenten zu umgehen Bindung:

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:

  • component: Die Komponentenklasse, die Sie binden möchten. Es verwendet Reflektion, um die Wähler zu ziehen braucht
  • contextParser: Rückruf, der die vorhandene Kind html und konstruiert ein Kontextobjekt (alles, was Sie die Komponente Zustand initialisieren müssen)
  • componentInitializer nimmt - Rückruf, der die erzeugte Komponente mit dem Kontext initialisiert Sie analysiert

Beispiel Nutzung:

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

Nun Ihre Lösung funktioniert gut, bis die Komponente ihren Zustand ändern muss und ein paar Sachen rerender. Weil ich noch keine Möglichkeit gefunden habe, ViewContainerRef für ein Element außerhalb von Angular (jquery, vanilla js oder sogar serverseitig) zu erhalten, war die erste Idee, detectChanges() durch Einrichten eines Intervalls aufzurufen. Und nach mehreren Wiederholungen kam ich schließlich zu einer Lösung, die für mich funktioniert.

Bisher im Jahr 2017 haben Sie ComponentResolver mit ComponentResolverFactory und tun fast die gleichen Dinge zu ersetzen:

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

    componentRef.changeDetectorRef.detectChanges(); 

Danach können Sie durch den Abschluss EventEmitters seiner NgZone Komponenteninstanz auf die Änderung Erfassungszyklus Befestigung emulieren kann:

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

natürlich vergessen Sie nicht, auf vernichten abzumelden:

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

UPD nach stackoverflowing einmal mehr

Vergessen Sie die alle obigen Worte. Es funktioniert, aber folgen Sie einfach diesem answer