Angular 2 passing html to ng-content with bindings - javascript

I'm writing angular components for the foundation css framework. I am working on the tabs component, and want to be able to pass some HTML to the <ng-content> of this.
The problem is, I also need to pass html which a user can put bindings on, like this:
PARENT TEMPLATE
<tabs [data]='example'>
<div> Age <br> {{item.age}} </div>`
</tabs>
TABS COMPONENT
<ul class="tabs" #tabs>
<li *ngFor="let item of data | async" (click)="tabClick($event)">
<a>{{item.name}}</a>
</li>
</ul>
<div>
<ng-content></ng-content>
</div>
TABS TYPESCRIPT
#Component({
selector: 'tabs',
templateUrl: './tabs.component.html'
})
export class TabsComponent {
#Input('data') data:any;
#ViewChild('tabs') tabs: ElementRef;
}
Where item is a reference to an object in the example array.
However, I get this error:
Cannot read property 'name' of undefined
as item is being evaluated before it is inserted into the <ng-content> directive.
Is there a way to get around this limitation, or am I going about this the wrong way?

update Angular 5
ngOutletContext was renamed to ngTemplateOutletContext
See also https://github.com/angular/angular/blob/master/CHANGELOG.md#500-beta5-2017-08-29
original
ngTemplateOutlet or ngForTemplate can be used for that use case:
<tabs [data]='example'>
<ng-template let-item>
<div> Age <br> {{item.age}} </div>`
</ng-template>
</tabs>
#Component({
...
template: `
<ul class="tabs" #tabs>
<li *ngFor="let item of data | async" (click)="tabClick($event)">
<a>{{item.name}}</a>
</li>
</ul>
<div>
<ng-template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{$implicit: (data | async)}"></ng-template>
</div>
`
})
class TabsComponent {
#ContentChild(TemplateRef) templateRef:TemplateRef;
}
See also Angular 2 bind transcluded content to loop variable

You should be using this way instead,
<tabs [data]='example'>
<div> Age <br> {{item.age}} </div>`
</tabs>
Component typescript
#Component({
selector: 'tabs',
templateUrl: './tabs.component.html'
})
export class TabsComponent {
#Input() data:any;
item:any{};
}
In your content projection define a selector as
<div class="tabs-body">
<ng-content select=".tabs-body"> </ng-content>
</div>
As your passing with bindings
<tabs [data]='example'>
<div> Age <br> {{item.age}} </div>`
</tabs>
DEMO

You need to pass the item object to the ng-content component.
<ng-content [item]="selectedTab></ng-content>
I am not certain on what lies behind the tab click event but you can assign that item object to selectedTab which will be passed to the component.
The component that will control the tab view can have the following:
#Input() item: Item;
And this will pass that object when you click. I might be attacking this from the wrong angle but maybe it will help you in some way.

Related

VUE- How do I put the values inside of components imported?

I remember I have seen once how to put the values in the html text area after importing components in VUE.
I'm not sure there is a way to do that or I just remember things in a wrong way.
my code is as below.
<template>
<div class="container">
<div class="row">
<Heading></Heading>
</div>
<hr>
<div class="row">
<div class="col-xs-12 col-sm-6">
<ul class="list-group">
<comp v-for='(value,index) in listing' :key='index'>{{value}}</comp>
</ul>
</div>
<serverstat></serverstat>
</div>
<hr>
<div class="row">
<footing></footing>
</div>
</div>
</template>
<script>
import Heading from './assets/Heading.vue';
import comp from './assets/comp.vue';
import serverstat from './assets/serverstatus.vue';
import footing from'./assets/footing.vue';
export default {
data() {
return {
listing: ['max','toms','judy','michael','dumdum']
}
},
components: {
Heading,comp,serverstat,footing
},
};
</script>
<style>
</style>
-comp-
<template>
<li class="list-group-item">
</li>
</template>
<script>
export default {
}
</script>
<style>
</style>
After I render this,
it doesn't show {{value}}. It only shows blank .
How do I insert the {{value}} within the html element?
Thank you in advance.
Since you are entering a value inside of a component, you can render it by using a slot in your component like this:
<template>
<li class="list-group-item">
<slot />
</li>
</template>
<comp v-for='(value,index) in listing' :key='index'>
<slot>{{ value }} </slot>
</comp>
Then in comp component use slot as
<slot/>
Not including the approach for props as you don't want to use that. Use the link above to learn more about slots.
When you use v-for it calls all the value from an array and :key='index' defines each object row from an array. If your object listing consists of firstname, lastname as your object then the value you want to print will be {{value.firstname}}. You are missing object name in value.
Can you try this once :
<comp v-for='(value,index) in listing' :key='index'>{{value.index}}</comp>

Angular 8: detect if a ng-content has content in it (or exists)

I have a component whose template allows for 2 content areas: Text and "read more" text. If the consumer of the component adds the area for the "read more" text, I want to show the "read more" link the end-user would click to show the text. If they don't include/need any "read more" text I don't want to show the link.
How do I detect the presence of the template area, and act accordingly with an ngIf?
For example, the html might be:
<app-promohero-message-unit title="Title for messaging module">
<div description>
Include a short, informative description here.
</div>
<div readmoretext>
If you need to add more detail, include another sentence or two it in this section.
</div>
</app-promohero-message-unit>
Obviously, they might not need readmoretext, so if they've omitted it I should not show the readmore link.
The component code is, so far:
import { Component, Input } from '#angular/core';
#Component({
selector: 'app-promohero-message-unit',
template: `
<div>
<h3 class="text-white">{{ title }}</h3>
<p class="text-white">
<ng-content select="[description]"></ng-content>
</p>
<p class="text-white" *ngIf="readMore">
<ng-content select="[readmoretext]"></ng-content>
</p>
</div>
<p>
<a class="text-white" (click)="showReadMore()" *ngIf="something"><u>Read more</u></a>
</p>
`
})
export class PromoheroMessageUnitComponent {
#Input()
title: string;
readMore = false;
showReadMore() {
this.readMore = true;
}
}
In Angular 8 you dont have to use the ngAfterViewInit life cycle hook. You can use the ngOnInit as long as you set the "static" value of the viewchild to true.
import { Component, OnInit, ViewChild, TemplateRef, ElementRef } from '#angular/core';
#Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit {
#ViewChild('content', { read: ElementRef, static: true }) content: ElementRef;
constructor() { }
ngOnInit() {
console.log(!!this.content.nativeElement.innerHTML); // return true if there is a content
}
}
Note that you must wrap the ng-content directive with html tag (such as div, span etc) and to set the templateRef on this outer tag.
<div #content>
<ng-content></ng-content>
</div>
I putted it on stackblitz: https://stackblitz.com/edit/angular-8-communicating-between-components-mzneaa?file=app/app.component.html
You can get a reference to the ng-content (Template Variable) and then access that variable in your component to check the length on the content of that ng-content using ViewChild
Then you can use the ngAfterViewInit life cycle hook to check for ng-content length
Your code will be like this:
import { Component, Input, ViewChild, ElementRef } from '#angular/core';
#Component({
selector: 'app-promohero-message-unit',
template: `
<div>
<h3 class="text-white">{{ title }}</h3>
<p class="text-white">
<ng-content select="[description]"></ng-content>
</p>
<p class="text-white" *ngIf="readMore">
<ng-content #readMoreContent select="[readmoretext]"></ng-content>
</p>
</div>
<p>
<a class="text-white" (click)="showReadMore()" *ngIf="something"><u>Read more</u></a>
</p>
`
})
export class PromoheroMessageUnitComponent {
#Input()
title: string;
#ViewChild('readMoreContent') readMoreContent: ElementRef;
readMore = false;
ngAfterViewInit() {
if (this.readMoreContent.nativeElement.childNodes.length.value == 0){
this.readMore = false
}
}
showReadMore() {
this.readMore = true;
}
}
You can use the ContentChild decorator for this, but will need to use an ng-template with a defined id as your content:
<app-promohero-message-unit title="Title for messaging module">
<div description>
Include a short, informative description here.
</div>
<ng-template #readmoretext>
If you need to add more detail, include another sentence or two it in this section.
</ng-template>
</app-promohero-message-unit>
Then in your component, you can use the ContentChild annotation like this:
export class PromoheroMessageUnitComponent {
#ContentChild('readmoretext')
readMoreContent: TemplateRef<any>;
// ...snip
}
Then finally in the HTML for your component:
<!-- snip -->
<p class="text-white" *ngIf="readMoreContent">
<ng-container *ngTemplateOutlet="readMoreContent"></ng-container>
</p>

angular2-tree-component is not expanded by default

I am using angular2-tree-component and want to show already expanded tree.
Now my tree is not expanded after loading page:
My HTML looks like this:
<div class="sidebartree-ul" [ngStyle]="{'background-color': 'white'}">
<tree-root class="panel-body " [nodes]="Items" [options]="customTemplateStringOptions"
#tree >
<ng-template #loadingTemplate>
Loading..
</ng-template>
<ng-template #treeNodeTemplate let-node>
<tree-node-expander [node]="node"></tree-node-expander>
<span *ngIf="node?.data?.expanded === true " title="{{node.data.subTitle}}">
<b> {{ node.data.name }} {{ childrenCount(node) }} </b></span>
<span *ngIf="!node?.data?.expanded === true" title="{{node.data.subTitle}}">
<b>{{ node.data.name }} {{ childrenCount(node) }} </b></span>
</ng-template>
<ng-template #loadingTemplate>Loading, please hold....</ng-template>
</tree-root>
</div>
I see an example from github and I've set isExpandedField to expanded, however, children fields are not expanded:
customTemplateStringOptions: any = {
isExpandedField: 'expanded',
idField: 'uuid',
nodeHeight: 23
}
public Items: any[];
And my data for TreeView looks like this:
If I add <tree-node-expander [node]="node"></tree-node-expander> and click at it, then it expands, but it is now what I want:
<tree-root class="panel-body " [nodes]="Items"
[options]="customTemplateStringOptions" #tree >
...
<ng-template #treeNodeTemplate let-node>
<tree-node-expander [node]="node"></tree-node-expander>
...
</ng-template>
<ng-template #loadingTemplate>Loading, please hold....</ng-template>
</tree-root>
Does anybody know what I am doing wrong to expand all children immediately?
I ran into the same problem. I used the below hack which i learned it from using ngx-toastr in ngOnInit. I worked for me. Not sure why ngAfterViewInit is not working for me.
ngAfterViewInit() {
setTimeout(() => this.tree.treeModel.expandAll(), 500)
}
I had to adjust the waiting time to make it work. by animation, you can make it for expansion.
I am still new to angular to provide in depth explanation. Let's see if this works for you and someone provide explanation.
The angular lifecycle isn't reliable for expanding the tree. The more reliable way is to use the (initialized) event on the tree root.
Docs: https://angular2-tree.readme.io/docs/events#initialized
In your component html file:
<tree-root (initialized)="onTreeLoad()">
In your component class:
onTreeLoad() {
this.tree.treeModel.expandAll();
}
You just need to call the expandAll method. You can check the demo, here
https://angular2-tree.readme.io/docs
The Expand All button calls the tree.treeModel.expandAll() method.

Angular 4 is not rendering my ID in my child component

I have 3 components, they are nested.
But the ID on the last child is not rendering using id = {{myId}}.
I tried [id]=myID as well.
Something like this:
GrandFather:
<app-middle-section #middleSection
myTitle="Title"
myID="my_id_creation" >
</app-middle-section>
Father:
<div class="section rounded">
<div class="title">{{myTitle}}</div>
<app-list #glist
myID= {{myID}}
</app-list>
</div>
Child
<div class="">
<div id="{{myID}}" class=""></div>
</div>
EDIT:
If I use console log on the last component it prints my "my_id_creation".
For the Father and Child I'm using the:
#Input() myID: string;
Are you getting an error? In any case, your father and child components need to define those id's as input, something along the lines of this:
#Component({
selector: 'father-component',
template: `
<div class="section rounded">
<div class="title">{{myTitle}}</div>
<app-list #glist
[id]="id"
</app-list>
</div>
`
})
export class FatherComponent {
#Input() id: number;
}

Nested Templates in Angular 2 [duplicate]

This question already has an answer here:
Passing Components in Angular2
(1 answer)
Closed 6 years ago.
I have a component, <DropDown></DropDown> and I want to let the user pass in a template for the list items in the DropDown.
Assuming they want to make a custom list item that has an image and text they would do something like this:
<DropDown [data]="myData">
<template>
<span> <img src="..."> Some Text <span>
</template>
</DropDown>
Inside the HTML of my DropDown component I have:
<div>
<ul>
<DropDownList [data]="data">
</DropDownList>
</ul>
</div>
In the DropDownList component I have the following HTML:
<li *ngFor="let item of data
(click)="handleOnSelect(item)">
[class.selected]="selectedItems.includes(item)">
<template [ngWrapper]="itemWrapper>
</template>
</li>
(I am using the template wrapper method from this post:
Binding events when using a ngForTemplate in Angular 2)
This method works if I have the li elements within the DropDown component's HTML. However, I want to have wrap the li's into a DropDownList component and pass the template the user gave from DropDown into DropDownList.
Is it possible to do this?
You could try the following solution:
#Component({
selector: 'DropDownList',
template: `
<li *ngFor="let item of items" (click)="handleOnSelect(item)">
<template [ngTemplateOutlet]="itemWrapper" [ngOutletContext]="{ $implicit: item }">
</template>
</li>`
})
export class DropDownListComponent {
#Input() itemWrapper: TemplateRef<any>;
#Input() items: any;
handleOnSelect(item) {
console.log('clicked');
}
}
#Component({
selector: 'DropDown',
template: `
<div>
<ul>
<DropDownList [items]="items" [itemWrapper]="itemWrapper">
</DropDownList>
</ul>
</div>`
})
export class DropDownComponent {
#Input() items: string[];
#ContentChild(TemplateRef) itemWrapper: TemplateRef<any>;
}
#Component({
selector: 'my-app',
template: `
<DropDown [items]="items">
<template let-item>
<h1>item: {{item}}</h1>
</template>
</DropDown>
`
})
export class App {
items = ['this','is','a','test'];
}
Plunker Example
The ngTemplateOutlet(^2.0.0-rc.2) directive has the same functionality as your custom directive NgWrapper
See also related questions:
Creating a dynamic repeater with ng-content transclusion with Angular2
Switch html templates dynamically during runtime on user action in angular 2

Categories

Resources