Ручка @Input и @Output для динамически созданного компонента в Angular 2
Как обрабатывать/предоставлять свойства @Input
и @Output
для динамически созданных Компонентов в Angular 2?
Идея состоит в том, чтобы динамически создавать (в данном случае) SubComponent при вызове метода createSub. Форкс отлично, но как я могу предоставить данные для свойств @Input
в SubComponent. Также, как обрабатывать/подписываться на события @Output
, которые предоставляет SubComponent?
Пример:
(Оба компонента находятся в одном NgModule)
AppComponent
@Component({
selector: 'app-root'
})
export class AppComponent {
someData: 'asdfasf'
constructor(private resolver: ComponentFactoryResolver, private location: ViewContainerRef) { }
createSub() {
const factory = this.resolver.resolveComponentFactory(SubComponent);
const ref = this.location.createComponent(factory, this.location.length, this.location.parentInjector, []);
ref.changeDetectorRef.detectChanges();
return ref;
}
onClick() {
// do something
}
}
подкомпонент
@Component({
selector: 'app-sub'
})
export class SubComponent {
@Input('data') someData: string;
@Output('onClick') onClick = new EventEmitter();
}
Ответы
Ответ 1
Я нашел следующий код для генерации компонентов "на лету" из строки (angular2 сгенерировать компонент только из строки) и создал из него директивку compileBoundHtml, которая проходит по входным данным (не обрабатывает выходные данные, но я думаю, что такая же стратегия применима, чтобы вы могли это изменить):
@Directive({selector: '[compileBoundHtml]', exportAs: 'compileBoundHtmlDirective'})
export class CompileBoundHtmlDirective {
// input must be same as selector so it can be named as property on the DOM element it on
@Input() compileBoundHtml: string;
@Input() inputs?: {[x: string]: any};
// keep reference to temp component (created below) so it can be garbage collected
protected cmpRef: ComponentRef<any>;
constructor( private vc: ViewContainerRef,
private compiler: Compiler,
private injector: Injector,
private m: NgModuleRef<any>) {
this.cmpRef = undefined;
}
/**
* Compile new temporary component using input string as template,
* and then insert adjacently into directive viewContainerRef
*/
ngOnChanges() {
class TmpClass {
[x: string]: any;
}
// create component and module temps
const tmpCmp = Component({template: this.compileBoundHtml})(TmpClass);
// note: switch to using annotations here so coverage sees this function
@NgModule({imports: [/*your modules that have directives/components on them need to be passed here, potential for circular references unfortunately*/], declarations: [tmpCmp]})
class TmpModule {};
this.compiler.compileModuleAndAllComponentsAsync(TmpModule)
.then((factories) => {
// create and insert component (from the only compiled component factory) into the container view
const f = factories.componentFactories[0];
this.cmpRef = f.create(this.injector, [], null, this.m);
Object.assign(this.cmpRef.instance, this.inputs);
this.vc.insert(this.cmpRef.hostView);
});
}
/**
* Destroy temporary component when directive is destroyed
*/
ngOnDestroy() {
if (this.cmpRef) {
this.cmpRef.destroy();
}
}
}
Важная модификация заключается в добавлении:
Object.assign(this.cmpRef.instance, this.inputs);
В основном, он копирует значения, которые вы хотите добавить в новый компонент, в класс компонентов tmp, чтобы их можно было использовать в сгенерированных компонентах.
Он будет использоваться как:
<div [compileBoundHtml]="someContentThatHasComponentHtmlInIt" [inputs]="{anInput: anInputValue}"></div>
Надеюсь, это спасет кого-то огромное количество Googling, которое я должен был сделать.
Ответ 2
createSub() {
const factory = this.resolver.resolveComponentFactory(SubComponent);
const ref = this.location.createComponent(factory, this.location.length,
ref.instance.model = {Which you like to send}
ref.instance.outPut = (data) =>{ //will get called from from SubComponent}
this.location.parentInjector, []);
ref.changeDetectorRef.detectChanges();
return ref;
}
SubComponent{
public model;
public outPut = <any>{};
constructor(){ console.log("Your input will be seen here",this.model) }
sendDataOnClick(){
this.outPut(inputData)
}
}
Ответ 3
Если вы знаете тип компонента, который хотите добавить, я думаю, вы можете использовать другой подход.
В корневом компоненте приложения html:
<div *ngIf="functionHasCalled">
<app-sub [data]="dataInput" (onClick)="onSubComponentClick()"></app-sub>
</div>
В корневом компоненте приложения typescript:
private functionHasCalled:boolean = false;
private dataInput:string;
onClick(){
//And you can initialize the input property also if you need
this.dataInput = 'asfsdfasdf';
this.functionHasCalled = true;
}
onSubComponentClick(){
}
Ответ 4
Предоставление данных для @Input очень просто. Вы назвали свой компонентный под-компонент и у него есть свойство @Input с именем data. Предоставление этих данных можно сделать, выполнив следующие действия:
<app-sub [data]="whateverdatayouwant"></app-sub>
Ответ 5
Вы можете легко связать его при создании компонента:
createSub() {
const factory = this.resolver.resolveComponentFactory(SubComponent);
const ref = this.location.createComponent(factory, this.location.length, this.location.parentInjector, []);
ref.someData = { data: '123' }; // send data to input
ref.onClick.subscribe( // subscribe to event emitter
(event: any) => {
console.log('click');
}
)
ref.changeDetectorRef.detectChanges();
return ref;
}
Отправка данных действительно сложна, просто ref.someData = data
, где data
- данные, которые вы хотите отправить.
Получение данных с вывода также очень просто, так как EventEmitter
вы можете просто подписаться на него, а clojure, который вы передадите, будет выполняться всякий раз, когда вы emit()
получаете значение от компонента.