dynamically instantiate an ng-template and create HTML in angular 5 - javascript

I am trying to build a typeahead.js component for use in Angular 5.
I have a working plunker, as far as I can get it working.
When the component, type-head is declared, an ng-template is supplied to the component.
<type-head>
<ng-template #hint let-hint="hint">name: {{hint.name}}</ng-template>
</type-head>
Each hint should display name: sam|frodo|pippin etc.
The type-head component is declared as follows:
#Component({
selector: 'type-head',
template: `
<input #input class="form-control" />
<ng-content #content></ng-content>
`
})
When the typeahead.js component needs to display a hint, it executes a callback, the suggest method which should return HTML content.
At the moment my implementation is as follows:
/**
* TODO: REPLACE THIS FUNCTION WITH A DYNAMIC TEMPLATE
*/
suggest(value:any) {
return $("<div>name: " + value.name + "</div>");
}
I want to replace this implementation with an implementation that uses the ng-template. I can render the template using *ngTemplateOutlet, but I don't know how to do it dynamically so I can return the HTML.
My question is:
How do I load up the #hint ng-template, bind the value to it, and return the rendered HTML to typeahead.js in my suggest function.

You can use TemplateRef::createEmbeddedView method to create embedded view and then obtain DOM node to pass to typeahead as a template:
parent.html
<type-head>
<ng-template #hint let-hint="hint">
<div>name: {{hint.name}}</div>
</ng-template>
</type-head>
component.ts
#ContentChild(TemplateRef) templateRef: TemplateRef<any>;
suggest(value:any) {
const embeddedView = this.templateRef.createEmbeddedView({ hint: value });
embeddedView.detectChanges();
this.embeddedViews.push(embeddedView);
return embeddedView.rootNodes[1];
}
Plunker Example

Related

How to render a component through the id is stored in variable?

I have two components. 1. App component, 2. child component.
For better understanding, the code and output is here in stackblitz: https://stackblitz.com/edit/angular-j9cwzl
app.component.ts:
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
template: `
<div id="child"></div>
<div id="{{childComponent}}"></div>
`
})
export class AppComponent {
childComponent='child';
}
child.component.ts
import { Component } from '#angular/core';
#Component({
selector: '[id=child]',
template: '<div>I am child</div>'
})
export class ChildComponent { }
When I write the id of the child component directly in the template of the App component it renders nicely. but when I write the id of the child component through a variable, it just creates a div element with an id="child".
My question is Why does this happen? How can I render the child with id through a variable in the script file dynamically?
I have a suggestion why your interpolation {{ childComponent }} is not working. In my view, Angular works something in the following order:
At first, it sees all your HTML and then tries to find attributes or selectors which can be evaluated as component or directive
Then the second step is when we found a component or directive, then {{ }} interpolation is applied
So maybe it is reason why your interpolation is not evaluated in your selector. Because evaluation of selectors are completed and then evaluation of directives started.
If you want to make a decision what component should be rendered, then you can use *ngIf statement:
<ng-container *ngIf="childComponentl else anotherComponent">
<div id="child"></div>
</ng-container>
<ng-template #anotherComponent>
test
</ng-template>
TypeScript:
childComponent='"child1"';
UPDATE:
If you want to avoid hard code, then you can load component dynamically:
Dynamically add components to the DOM with Angular
Angular 2.1.0 create child component on the fly, dynamically
Insert a dynamic component as child of a container in the DOM

Angular - How do directives "see" template & ViewContainer?

I have a simple component which injects numbers after a delay via custom directive named *appDelay
I already know that * is a hint for Angular to de-sugar the syntax into something like
<ng-template ...>
...actual markup
</ng-template>
I also know that we can Inject components/templates to the viewContainer via :
this.viewContainerRef.createEmbeddedView/Component(this.templateRef);
The directive code is :
#Directive({
selector: '[appDelay]'
})
export class DelayDirective {
constructor(
private templateRef: TemplateRef<any>, private viewContainerRef: ViewContainerRef
) { }
#Input() set appDelay(time: number): void {
setTimeout(()=>{
this.viewContainerRef.createEmbeddedView(this.templateRef);
}, time);
}
}
The docs states :
To access a ViewContainerRef of an Element, you can either place a
Directive injected with ViewContainerRef on the Element, or you obtain
it via a ViewChild query.
Question:
In a general pseudo form : What are the template "string values" for templateRef and viewContainerRef ?
IMHO the de-sugared template would be something like:
<ng-template ...>
<card *appDelay="500 * item">
{{item}}
</card>
</ng-template>
So the ViewContainerRef would be a reference to <ng-template ...>
And the templateRef will be a reference to the <card >...</card>
— Is that correct ?
(Also , is it possible to console.log() those HTML templates and see the actual markup ?
https://plnkr.co/edit/80AGn8bR4CiyH0ceP8ws?p=preview
ViewContainerRef just point to element that will be host for inserting views. Those views will be added as siblings to this host element.
For structural directive comment like <!----> will be host element.
Desugar for
<div *appDelay="500">
Hooray
</div>
will be
<ng-template [appDelay]="500">
<div>
Hooray
</div>
</ng-template>
it also can be described like so:
<ng-template view-container-ref></ng-template>
<!-- ViewRef -->
<div>
Hooray
</div>
<!-- /ViewRef -->
Since ng-template is not stamped to the DOM it will be rendered as <!---->.
Angular will create ViewContainerRef with reference to this comment tag.
vcRef.element.nativeElement
Each ViewContainer can have only one anchor element and each anchor element can only have a single ViewContainer. ViewContainer is a container that helps you to manipulate Views(ViewRef, EmbeddedViewRef)
Also TemplateRef instance will be created
class TemplateRef_ {
constructor(private _parentView: ViewData, private _def: NodeDef) { }
get elementRef(): ElementRef {
return new ElementRef(asElementData(this._parentView, this._def.index).renderElement);
}
and its elementRef(anchor or location) will point to the same comment element.
The main feature of TemplateRef is having template property
this.templateRef._def.element.template
This property doesn't contains html string but describes view
this.templateRef._def.element.template.factory + ''
will print
"function View_AppComponent_1(_l) {
return jit_viewDef1(0,[(_l()(),jit_elementDef2(0,null,null,1,'div',[],null,null,
null,null,null)),(_l()(),jit_textDef3(null,['\n Hooray\n']))],null,null);
}"
so this is our template. As you can see it describes view with div root element and child text node with text \n Hooray\n
Angular uses such ViewDefinitions which are located in ngfactories to build the DOM tree
See also
Angular2: How is ngfor expanded
https://github.com/angular/angular/blob/4.2.0-rc.1/packages/core/src/linker/view_ref.ts#L41-L79
Don't forget to watch https://www.youtube.com/watch?v=EMjTp12VbQ8

On a TemplateRef, is possible to add 'let-x' attributes dynamically?

The context of the problem is: I'm trying to perform transclusion and passing context variables to inner components.
For example, one snippet code of my initial DOM:
<line-container>
<template> <!-- I have as constraint add 'let-item' to access to the variable -->
<componentA item="item"></component>
<componentB item="item"></component>
</template>
</line-container>
This is a component which retrieves its inner content through 'template' tag (#ContentChild), and then using a ngFor passing the template and routing.
#Component({
selector: 'line-container',
templateUrl: `
<div *ngFor="let item of items">
<template [ngTemplateOutlet]="templateRef"
[ngOutletContext]="{item: item}"></template>
</div>`
})
export class LineContainerComponent implements AfterContentInit {
public items: any[];
#ContentChild(TemplateRef) templateRef: TemplateRef<any>;
constructor(private renderer: Renderer) { }
ngAfterContentInit() {
// this.renderer.setElementAttr...
}
}
As you notice above, I'm missing to add the 'let-item' into the template tag. Unfortunately, this is a constraint I can't do, hence I'm looking the way to add that attribute dynamically.
I thought using:
this.renderer.setElementAttribute(this.templateRef.elementRef.nativeElement,
'let-item', 'item');
But I noticed always the value of the nativeElement is a comment: and it's not able to find the ElementHTML to attach that property. Any idea of how to declare this template variable into this template tag?

Custom template(transclusion without ng-content) for list component in Angular2

I have a list component which shows only names. list component should be able to take custom template which will be given by user.
List Component
import {Component } from 'angular2/core';
#Component({
selector: 'my-list',
template: `<p>This is List</p>
<ul>
<li *ngFor="#i of data"><div class='listItem'>{{i.name}}</div></li>
</ul>`
})
export class MyList implements OnInit{
data: Array<any> = [{name: 'John', age: 26},{name: 'Kevin', age: 26}, {name:'Simmons', age:26}];
}
My Requirement
<my-list>
<div>{{i.name}}-{{i.age}}</div> //user should be able to provide custom template like this
</my-list>
I tried this with ng-content but it throws error. In angular 1 same thing used to work with transcluded content. do we have any alternative of manual transclusion in angular 2 and if not then how could we implement this feature in angular2.
Here is Plunker
You need to use ngForTemplate, I've created PrimeNG DataList and many other DataComponents using this technique and it works great. Demo;
http://www.primefaces.org/primeng/#/datalist
Code;
https://github.com/primefaces/primeng/blob/master/src/app/components/datalist/datalist.ts
In your component, define a templateRef with contentchild;
#ContentChild(TemplateRef) itemTemplate: TemplateRef;
Your template becomes;
template: `<p>This is List</p>
<ul>
<template ngFor [ngForOf]="data" [ngForTemplate]="itemTemplate"></template>
</ul>`
So that your users can define content like;
<my-list>
<template #anything>
<div>{{anything.i.name}}-{{anything.i.age}}</div>
</template>
</my-list>
There was a question regarding this in the past (see Use content of component template in angular 2) and this doesn't seem to be supported.
There are two things here:
When you provide an input template for the component, your i is variable is evaluated against the current component and not my-list one. If you want to use its properties you must do something like that:
<my-list #myList>
<div>{{myList.i.name}}-{{myList.i.age}}</div> //user should be able to provide custom template like this
</my-list>
The other problem is the ability to use ng-content within a loop and it's not supported. I think that we could add an issue for this...
Here is a the plunkr I used for my tests: https://plnkr.co/edit/a06vVP?p=preview.
You can find a short interesting guide which shows you how to build such a list-component with custom-template
here.

Angular 2: How to pass down a template through a component property?

How to pass down a template through a component property in angular 2?
I've only made the first steps:
#Component({
selector: 'tab',
template: `<div>#!HERE GOES THE HEADER TEMPLATE!#
<ng-content></ng-content>
</div>`
})
export class Tab {
#Input() title: string;
#Input() headerTemplate:string;
...
That could be used something like this:
<tab [title]="'Some Title'" [header-template]="'<p>{{title}}</p>'">Some Content</tab>
That should render:
<div><p>Some Title</p>Some Content</div>
At this point I'm stuck.
Though this question is very old, there are much better solutions available. There is no need to pass a string as a template - that is very limiting. You can create an element and get its 'TemplateRef' and send that to a component which takes TemplateRef as input. A component can take any number of TemplateRefs as input actually, and you can inject those templates in any number of places.
Note that the 'template' element is deprecated, and the 'ng-template' element should be used in all cases.
So, in a parent component -
<ng-template #thisTemplate>You Can put anything here, including other components</ng-template>
<tab [template]="thisTemplate"></tab>
Then in the tabs component from the OP
import { Component, Input, TemplateRef, ViewChild, ViewContainerRef } from '#angular/core'
.... somehwere in its template ....
<div #viewcontainer></div>
Down in the component :
#ViewChild('viewcontainer',{read:ViewContainerRef}) viewcontainer : ViewContainerRef;
#Input() template : TemplateRef<any>;
ngAfterViewInit() {
this.viewcontainer.createEmbeddedView(this.template);
}
An method similar to diopside’s but using a container element for projection in the template rather than programmatically:
parent.component.html
<app-child [template]=“templateRef”></app-child>
<ng-template #templateRef>
<p>hello, from defined in parent</p>
</ng-template>
child.component.ts
<ng-container *ngTemplateOutlet=“templateRef”></ng-container>
After some research, I’ve checked that someone already has an elegant solution for this question.
At https://github.com/valor-software/ngx-bootstrap/blob/development/src/tabs the tab component can receive a template to tab heading. Great work!
Checking the code the solution relies on two directives, a tab specific one: TabHeading and a generic one:NgTransclude)
As presented there, the component can be created passing a template:
<tab>
<template tab-heading>Tab 2</template>
Tab 2 content
</tab>
If someone can give a better explanation of that implementation, please do.

Categories