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?
Related
I want to pass an HTML element in my Angular Higher Order Component. Right now I'm passing child element as a #Input decorator.
My HOC, Main Container is like this.
<div>
<p> MY EXTRA UI HERE</p>
<ng-container *ngComponentOutlet="child"></ng-container>
</div>
#Component({
selector: 'app-main-container',
templateUrl: './main-container.component.html',
styleUrls: ['./main-container.component.scss'],
})
export class MainContainerComponent {
#Input() child
}
In other components I'm using my HOC like this
My Current code:
<app-main-container [child]="child"></app-main-container>
in .ts file I'm passing my child component like this
import { SidebarComponent } from '../sidebar/sidebar.component'
#Component({
selector: 'app-staff',
templateUrl: './staff.component.html',
styleUrls: ['./staff.component.scss'],
})
export class StaffComponent {
child: any = SidebarComponent
}
Now what I want to do is, something like this in React format
<app-main-container>
<app-staff-sidebar></app-staff-sidebar>
</app-main-container>
Given the structure you defined in your question
<app-main-container>
<app-staff-sidebar></app-staff-sidebar>
</app-main-container>
we can achieve this using ng-content.
Your main-container.component.html should accept it like so:
<div class="main-container">
<ng-content></ng-content> <!-- This will be exchanged with the `<app-staff-sidebar></app-staff-sidebar>` that you passed in.
</div>
Assuming you want to insert more content that should be rendered a little differently than just by plain yielding, you can use slots represented by the select keyword. It is basically trying to find the provided pattern.
Calling the structure like so:
<app-main-container>
<app-staff-sidebar data-slot-type="right"></app-staff-sidebar>
<div data-slot-type="left"></div>
</app-main-container>
And accepting the slots like this:
<div class="main-container">
<ng-content select="[data-slot-type=left]"></ng-content>
<ng-content select="[data-slot-type=right]"></ng-content>
</div>
The select can match any given pattern. It can be a CSS class, an entire tag or anything else the DOM represents.
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
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
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
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.