1
votes

I have following code:

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 (Component factory service):

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

} 

I want to develop a search dropdown suggestion widget in Angular 2, when the user types 3 characters in the input box placeholder a request is done to a backend and a json response is returned. Each search suggestion element is dynamically loaded in the search.dropdown.component, specifically in the #searchresult of search.dropdown.component.html.

The components, search.result.component, are injected dynamically, but are rendered outside:

enter image description here

enter image description here

I want to inject the dynamic components inside the datalist and be able to access the attribute suggestion search.result.component afterwards with the ViewChildren api.

The problem is that the search-result components are rendered outside the datalist. If I put a div or a template tag in the html to render the dynamic elements inside, the view is rendered properly but the ViewChildren api doesn't retrieve the dynamic injected components.

How can I access with ViewChildren api components rendered inside a div tag?

Thank you

3

3 Answers

1
votes

So what you are looking is something similar to the angular router itself, isn't ? Take a look at this sources: 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, []);
    ...
  }

Shortly you have a ViewContainerRef which references the current component as a container and a ComponentFactoryResolver. They are both injected by constructor.

The componentFactory (const factory in above) is created with method ComponentFactoryResolver.resolveComponentFactory(desiredComponent).

Then the desiredComponent (this.activated in above) is added to the container with method ViewContainerRef.createComponent(componentFactory, index) (here the doc: https://angular.io/docs/ts/latest/api/core/index/ViewContainerRef-class.html#!#createComponent-anchor).

So in your code, replace :

refDOM.insert(comp.hostView,0);
return comp.instance;

By :

refDOM.createComponent(factory, refDOM.length, injector);
1
votes

There is tricky solution for that. Right now after Angular rc5 there is no way to achive nested tag as you want but if you change:

search.dropdown.component.html:

<datalist id="search-dropdown">
   <template #searchresult />
</datalist>

you should achive something lie that in html code:

<data-list id="search-dropdown">
   <!-- template -->
   <search-result ... />
   <search-result ... />
   <search-result ... />
   ...
</data-list>

Template tag is not rendered by angular and it is changed to comment (any other tag f.ex. div will be displayed as empty div.

0
votes

I don't understand why you are using @ViewChild... Your search.dropdown.component.html may be something like :

<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>

then anotate your sugestions as an input in your component to dislay there content :

export class SearchResultComponent extends CoreBase implements OnInit
{
   @Input() private suggestions:string;
   ...

and add a field with suggestions in your search.dropdown.component.ts :

export class SearchDropDownComponent  implements AfterViewInit , OnChanges
{

   private results: string[] = [];

   onChange() {
     updateResultsHere();
   }
   ...