-1

i am building autocomplete in NativeScript and Angular (this question is valid for pure Angular also).

Markup looks like this:

<StackLayout>
<TextField #keywords [hint]="options?.hint" [(ngModel)]="occupation"
    (textChange)="keywordsInputChange$.next(keywords.text)">
</TextField>
<ng-container *ngTemplateOutlet="parentTemplate"></ng-container>

<ScrollView orientation="vertical" height="200" *ngIf="dataItems?.length > 0" class="m-r-16 m-l-16 search-view">
    <ListView [items]="dataItems" (itemTap)="onItemTap($event)">
        <ng-template let-item="item">
            ...
        </ng-template>
    </ListView>
</ScrollView>

and it will be used from outside:

<px-lookup (selected)="onOccupationSelected($event)" [options]="occupationLookupOptions">
    <ng-template let-item>
         <StackLayout class="search-item" backgroundColor="red">
               <Label [text]="item.text"></Label>
         </StackLayout>
    </ng-template>
</px-lookup>

As you see i would like to pass custom template to my lookup which will be used by ListView. I am am retrieving that template like this

@ContentChild(TemplateRef, { static: false }) parentTemplate: TemplateRef<any>;

I can render it in lookup without any problem by defining

<ng-container *ngTemplateOutlet="parentTemplate; context: { $implicit: item }"></ng-container>

However when i am trying to put it to LietView which also requires template i cannot make it to work. I am geting errors like 'Error: No suitable views found in list template! Nesting level: 0' either items are rendered just as [Object object]

I have tried these options:

<ListView [items]="dataItems" (itemTap)="onItemTap($event)">
        1. Option ===> <ng-container *ngTemplateOutlet="parentTemplate"></ng-container> -->

        2. Option ===><ng-content></ng-content>

        3. Option ===> <ng-template let-item="item"> (top template that is required by ListView)
            <ng-container *ngTemplateOutlet="parentTemplate; context: { $implicit: item }"></ng-container> (Tried to render another template with current one by passing item down the pipe)
        </ng-template>
    </ListView>

Does somebody knows the way how I can attchieve that?

Thanks

  • You might want to raise [an issue on Github repo](https://github.com/NativeScript/nativescript-angular/issues). – Manoj Apr 02 '20 at 09:10

1 Answers1

0

The key is to create a directive for storing a ref to the template, by leveraging dependency injection of TemplateRef.

First the directive:

// my-template.directive.ts

@Directive({ selector: '[myTemplate]' })
export class MyTemplateDirective  {
  @Input('myTemplate') type: string;

  constructor(public template: TemplateRef<any>) {}
}

(Please note that, although the type property is not used in this simple case, with multiple nested templates it would be necessary)

After, in the autocomplete component, you look for a content child with that directive, like this:

// autocomplete.component.ts

@ContentChild(MyTemplateDirective, { static: false }) set setTemplate(value: MyTemplateDirective) {
   // for multiple templates you could implement logic using value.type

   // for example:
   // this.parentTemplates[value.type] = value.template

   this.parentTemplate = value.template;
};

and in the template

// autocomplete.component.html
// Basically your Option 3

<ListView [items]="dataItems" (itemTap)="onItemTap($event)">   
  <ng-template let-item="item">
     <ng-container *ngTemplateOutlet="parentTemplate; context: { $implicit: item }"></ng-container>
  </ng-template>
</ListView>

then you would use it like this:

<px-lookup (selected)="onOccupationSelected($event)" [options]="occupationLookupOptions">
    <ng-template let-item myTemplate="pxLookupElem">
         <StackLayout class="search-item" backgroundColor="red">
               <Label [text]="item.text"></Label>
         </StackLayout>
    </ng-template>
</px-lookup>
Francisco
  • 157
  • 7
  • What does your comment means "(Please note that, although the type is not used in this simple case, with multiple nested templates it would be necessary)" - i should specify type instead of 'any' in directive? Is it possible to have generic directive for storing any template? Or types should be provided if i want to use multiple nested templates - but if just one - then it is fine like that? – Vytautas Pranskunas Apr 01 '20 at 12:39
  • Also where is pxLookupElem is comming in myTemplate="pxLookupElem"? – Vytautas Pranskunas Apr 01 '20 at 12:46
  • the directive allows for a string property to be passed through `myTemplate` and which is called `type` internally. This is just a string that identifies which template you are referring to. Since this autocomplete component only allows for one type of template (which I called `"pxLookupElem"`), it is not necessary. However, if you would like to allow multiple templates, you could implement logic in the setter of `@ContentChild()`, and then expose each of them using a separate `*ngTemplateOutlet`. – Francisco Apr 01 '20 at 14:38
  • I added some comments in my answer to make it clearer. Hope it helps. – Francisco Apr 01 '20 at 14:47
  • Thanks but you did not mentioned where "pxLookupElem" comes from. I assume that pxLookupElem is variable added on template like - if thats true so then it does not work i det same error :( – Vytautas Pranskunas Apr 01 '20 at 20:35
  • `"pxLookupElem"` does not come from anywhere it is just a string. – Francisco Apr 02 '20 at 10:44