Angular 2 动态组件注入和访问动态组件属性
Posted
技术标签:
【中文标题】Angular 2 动态组件注入和访问动态组件属性【英文标题】:Angular 2 dynamic component injection and access to dynamic component attributes 【发布时间】:2017-05-20 01:46:38 【问题描述】:我有以下代码:
search.component.ts
import CoreComponent from './../../core/components/core.component';
import SearchDropDownComponent from './search.dropdown.component';
import Component,Input,EventEmitter, Output, ViewChild from "@angular/core";
@Component(
selector:'search',
templateUrl:'app/templates/search.component.html',
styleUrls:['app/templates/css/search.component.css']
)
export class SearchComponent
@Input() core : CoreComponent;
@Output() onSearch : EventEmitter<any> = new EventEmitter();
@ViewChild(SearchDropDownComponent) dropdown: SearchDropDownComponent;
private searchText:String="";
search(event:any) : void
if (+event.keyCode==13 || event.type=="click")
this.onSearch.emit(event);
return;
search.component.html
<div class="input-group" id="search">
<input class="form-control kblue-background-pale" [(ngModel)]="searchText" (keypress)=search($event) placeholder="Search" list="search-dropdown" type="text">
<search-dropdown [core]="this.core" [typed]="this.searchText"></search-dropdown>
<div class="input-group-btn">
<div class="btn-group" role="group">
<button (click)="search($event)" type="button" class="btn kblue-background-pale">
<span class="glyphicon glyphicon-search kblue" aria-hidden="true">
</span>
</button>
</div>
</div>
</div>
search.dropdown.component.ts
import CoreBase from './../../core/components/core.base';
import ComponentFactoryService from './../../services/services/component.fac';
import SearchResultComponent from './search.result.component';
import CoreComponent from './../../core/components/core.component';
import Component,
Input,AfterViewInit ,
ContentChildren,ContentChild,
Output,Inject,forwardRef,OnChanges,ElementRef,ViewChild,ViewContainerRef,ViewChildren,QueryList from "@angular/core";
@Component(
selector:'search-dropdown',
templateUrl:'app/templates/search.dropdown.component.html',
styleUrls:['app/templates/css/search.dropdown.component.css']
)
export class SearchDropDownComponent implements AfterViewInit , OnChanges
@Input() core : CoreComponent;
@ViewChild('searchresult', read: ViewContainerRef) p: ViewContainerRef;
@ViewChildren(SearchResultComponent) t: QueryList<any>;
@ViewChild('parent', read: ViewContainerRef) children: ViewContainerRef;
private MLENGTH=2;
private RLENGTH=0;
private searchLength=0;
constructor(private factory:ComponentFactoryService)
@Input()
set typed(typed:string)
if (typed.length>this.searchLength)
if (typed.length>this.MLENGTH)
this.core.webservice(this.result,"communities",search:typed);
this.searchLength=typed.length;
else
if (typed.length==this.RLENGTH)
this.p.clear();
this.searchLength=0;
console.log(this.p.length);
console.log(this.children);
// for(var index = 0; index < this.p.length; index++)
//
// console.log(this.t);
//
ngAfterViewInit()
ngOnChanges()
console.log(this.p.element.nativeElement.childNodes);
console.log(this.children);
public result=(data:any)=>
data.forEach((item:any)=>
this.factory.getComponent(this.p,SearchResultComponent,item);
);
search.dropdown.component.html:
<datalist id="search-dropdown" #searchresult></datalist>
search.result.component.ts:
import CoreBase from './../../core/components/core.base';
import Component,OnInit,TemplateRef from "@angular/core";
@Component(
selector:'search-result',
templateUrl:'app/templates/search.result.component.html',
styleUrls:['app/templates/css/search.result.component.css']
)
export class SearchResultComponent extends CoreBase implements OnInit
private suggestions:String;
constructor()
super();
ngOnInit()
this.suggestions=this.parameters["search"];
search.result.component.html:
<option>suggestions</option>
component.fac.ts(组件工厂服务):
import CoreBase from './../../core/components/core.base';
import
Injectable,
ReflectiveInjector,
ViewContainerRef,
ComponentFactoryResolver
from '@angular/core';
@Injectable()
export class ComponentFactoryService
constructor(private componentFactoryResolver: ComponentFactoryResolver)
public getComponent(refDOM:ViewContainerRef, component:any,parameters:any)
let factory = this.componentFactoryResolver.resolveComponentFactory(component);
let injector = ReflectiveInjector.fromResolvedProviders([], refDOM.parentInjector);
let comp = factory.create(injector);
(<CoreBase>comp.instance).parameters=parameters;
comp.changeDetectorRef.detectChanges();
refDOM.insert(comp.hostView,0);
return comp.instance;
我想在 Angular 2 中开发一个搜索下拉建议小部件,当用户在输入框占位符中键入 3 个字符时,会向后端发出请求并返回 json 响应。每个搜索建议元素都在 search.dropdown.component 中动态加载,特别是在 search.dropdown.component.html 的 #searchresult 中。
组件 search.result.component 是动态注入的,但会在外部渲染:
我想在数据列表中注入动态组件,然后能够使用 ViewChildren api 访问属性建议 search.result.component。
问题是搜索结果组件呈现在数据列表之外。如果我在 html 中放置一个 div 或一个模板标签来渲染内部的动态元素,则视图会正确渲染,但 ViewChildren api 不检索动态注入的组件。
如何使用 div 标签内呈现的 ViewChildren api 组件进行访问?
谢谢
【问题讨论】:
【参考方案1】:所以你正在寻找的是类似于角度路由器本身的东西,不是吗?看看这个来源:https://github.com/angular/angular/blob/master/modules/%40angular/router/src/directives/router_outlet.ts:
activate(...): void
...
const component: any = <any>snapshot._routeConfig.component;
const factory = resolver.resolveComponentFactory(component);
...
this.activated = this.location.createComponent(factory, this.location.length, inj, []);
...
很快你就有了一个ViewContainerRef,它将当前组件引用为一个容器和一个ComponentFactoryResolver。它们都是由构造函数注入的。
componentFactory(上面的const factory
)是用ComponentFactoryResolver.resolveComponentFactory(desiredComponent)
方法创建的。
然后使用方法ViewContainerRef.createComponent(componentFactory, index)
(此处为文档:https://angular.io/docs/ts/latest/api/core/index/ViewContainerRef-class.html#!#createComponent-anchor)将desiredComponent(上面的this.activated
)添加到容器中。
所以在你的代码中,替换:
refDOM.insert(comp.hostView,0);
return comp.instance;
作者:
refDOM.createComponent(factory, refDOM.length, injector);
【讨论】:
这是我一直在寻找的解决方案。我没有正确使用 refDOM.insert 中的位置。非常感谢。【参考方案2】:对此有一个棘手的解决方案。 现在,在 Angular rc5 之后,没有办法随心所欲地实现嵌套标签,但如果你改变:
search.dropdown.component.html:
<datalist id="search-dropdown">
<template #searchresult />
</datalist>
你应该在 html 代码中实现一些东西:
<data-list id="search-dropdown">
<!-- template -->
<search-result ... />
<search-result ... />
<search-result ... />
...
</data-list>
Template标签不是angular渲染的,改为comment(其他标签f.ex.div都会显示为空div。
【讨论】:
【参考方案3】:我不明白您为什么使用@ViewChild...您的search.dropdown.component.html
可能类似于:
<datalist id="search-dropdown" #searchresult>
<div *ngFor="let result of results">
<search-result1 *ngIf="result.type==1" [suggestions]="result" ...></search-result1>
<search-result2 *ngIf="result.type==2" [suggestions]="result" ...></search-result2>
...
</div>
</datalist>
然后将您的建议注释为组件中的输入以显示那里的内容:
export class SearchResultComponent extends CoreBase implements OnInit
@Input() private suggestions:string;
...
并在您的 search.dropdown.component.ts
中添加一个带有建议的字段:
export class SearchDropDownComponent implements AfterViewInit , OnChanges
private results: string[] = [];
onChange()
updateResultsHere();
...
【讨论】:
您好,我希望每个搜索结果都由一个组件管理,因为每个搜索结果可能有不同的属性,我想通过搜索结果组件提供的方法访问它们。例如,每个搜索结果建议可能具有不同的相关性。问题是组件在DOM中注入不正确,ViewChildren api没有找到动态注入的组件。 谢谢,我了解您的解决方案。我会测试它。然而,是否可以在不指定父组件中嵌入组件的选择器的情况下在 DOM 中动态注入组件?即使用 componentFactoryResolver.resolveComponentFactory 并将动态生成的组件插入到由 refDOM:ViewContainerRef 定义的 DOM 引用中?现在我可以执行动态注入,但是组件没有注入到正确的位置,之后我无法查询它。非常感谢。 "是否可以在不指定父组件中嵌入组件的选择器的情况下在 DOM 中动态注入组件" -> 确定!你必须使用ViewContainerRef.createComponent(componentFactory, index)
。我发布了另一个答案,我使用 Angular Router 示例开发它。以上是关于Angular 2 动态组件注入和访问动态组件属性的主要内容,如果未能解决你的问题,请参考以下文章
通过自定义注入器将服务注入动态生成的组件时,Angular ChangeDetection 不起作用