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 2:向动态创建的组件添加指令

Angular 2.1.0 动态创建子组件

Angular 2.1.0 动态创建子组件

通过自定义注入器将服务注入动态生成的组件时,Angular ChangeDetection 不起作用

typescript 一个示例,显示动态地将模板注入Angular2组件的方式

处理 Angular 2 中动态创建的组件的 @Input 和 @Output