In angular2, how can I detect is there any ng-content? - javascript

plnkr demo here
#Component({
selector: 'my-demo',
template: `This is <ng-content select=".content"></ng-content>`
})
export class DemoComponent { name = 'Angular'; }
#Component({
selector: 'my-app',
template: `<h1>Hello {{name}}</h1>
<my-demo><div class="content">In Content</div></my-demo>
`
})
export class AppComponent { name = 'Angular'; }
I want to conditional ng-content, like
<ng-template *ngIf='hasContent(".content"); else noContent'>
This is <ng-content select=".content"></ng-content>
</ng-template>
<ng-template #noContent>No content</ng-template>
Is there possible in angular2 ?

Günter Zöchbauer's solution is acceptable, doesn't affect the useage, let the component detect this, I also found a easier way to do this without any json by using the :empty
<!-- must no content: https://css-tricks.com/almanac/selectors/e/empty/ -->
<!--#formatter:off-->
<div class="title"><ng-content select=".nav-title"></ng-content></div>
<!--#formatter:on-->
.title:empty {
display: none;
}
Works for any html+css.

template: `This is <span #wrapper>
<ng-content select=".content"></ng-content>
</span>`
#ViewChild('wrapper') wrapper:ElementRef;
ngAfterContentInit() {
console.log(this.wrapper.innerHTML); // or `wrapper.text`
}
See also
Get component child as string
Access transcluded content

if you want to use conditional statement with *ngIf and else the yes it's possible
#Component({
selector: 'my-demo',
template: `<div *ngIf='content; else noContent'>
This is <ng-content select=".content"></ng-content>
</div>
<ng-template #noContent>No content</ng-template>`
})
export class DemoComponent { name = 'Angular'; content = true}
Demo

Related

does angular content projection inside *ngFor share same data? How to access project content of child component from parent?

currently I trying to project a third component in a child component which is projected inside ngFor loop (inside child), but in parent whenever I change or set some property in the projected content using index of query list (ViewChildren('#thirdComponent')) in parent all the child's projected content shows same change. Is there any proper way of doing this.
Is it due to duplicating of select property binding at the place of content projection in child component.Child's projection is done inside a accordion with one or many panels opened at a time.
#Component({
selector: "my-app",
template: `
<child-comp #child>
<ng-container selected>
<some-other-comp #someOtherComp></some-other-comp>
</ng-container>
</child-comp>
`,
styleUrls: ["./app.component.css"]
})
export class AppComponent implements AfterViewInit {
h = 0;
i = 1;
j = 2;
k = 3;
#ViewChildren("someOtherComp") otherCompList: QueryList<SomeOtherComponent>;
ngAfterViewInit(): void {
this.otherCompList.toArray()[this.h].prop = this.h;
// below will result in undefined due to QueryList of size 1
// this.otherCompList.toArray()[this.i].prop = this.i;
// this.otherCompList.toArray()[this.j].prop = this.j;
// this.otherCompList.toArray()[this.k].prop = this.k;
}
}
#Component({
selector: "child-comp",
template: `
<div *ngFor="let value of [1, 2, 3]; let i = index">
<!-- if ngIf is removed than only the last projection is diplayed -->
<div *ngIf="i === 0">
<ng-content select="[selected]"> </ng-content>
</div>
</div>
`,
styleUrls: ["./app.component.css"]
})
export class ChildComponent {}
#Component({
selector: "some-other-comp",
template: `
<p>{{ prop }}</p>
`,
styleUrls: ["./app.component.css"]
})
export class SomeOtherComponent {
prop: any;
}
Stackblitz
Utilizing *ngTemplateOutlet and let-variables
We can pass along a template into our child-component, and utilize the #Input() decorator in conjunction with *ngTemplateOutlet to directly access the property from the HTML template in the parent.
Example
First, I've defined an array in my parent component which I want to use as the basis for my loop in my outer-child component.
Parent Component
#Component({
selector: 'parent',
templateUrl: 'parent.component.html',
styleUrls: ['parent.component.scss']
})
export class ParentComponent implements OnInit {
dataItems: { title: string, description: string }[] = [{
title: 'First Element',
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eveniet, nihil!'
}...] // remaining items truncated for brevity.
constructor() {
}
ngOnInit(): void {
}
}
This parent component then has a child component, which takes an input of the entire list of items
<child [items]="dataItems"></child>
Child-Component (fist level)
#Component({
selector: 'child',
templateUrl: 'child.component.html',
styleUrls: ['child.component.scss']
})
export class ChildComponent implements OnInit {
#Input() items!: any[];
constructor() {
}
ngOnInit(): void {
}
}
<ng-container *ngFor="let childItem of items">
<projected [item]="childItem">
<ng-template let-item>
<h4>{{item.title}}</h4>
<p>{{item.description}}</p>
</ng-template>
</projected>
</ng-container>
Projected component (sub-child)
#Component({
selector: 'projected',
templateUrl: 'projected.component.html',
styleUrls: ['projected.component.scss']
})
export class ProjectedComponent implements OnInit {
#Input() item: any;
#ContentChild(TemplateRef) templateOutlet!: TemplateRef<any>
constructor() {
}
ngOnInit(): void {
}
}
<ng-container *ngTemplateOutlet="templateOutlet; context: {$implicit: item}"></ng-container>
<ng-content></ng-content>
How does it work
The Parent Component isn't strictly necessary in this relationship, as we aren't projecting content directly from the parent into the ProjectedComponent, I simply chose to define a list of items here to keep a hierarchy similar to your question.
The Child Component
The child component does two things:
Defines a *ngFor loop to loop thru some collection of elements.
Defines a template for how these elements should be utilized in the ProjectedComponent's template.
In the ProjectedComponent we utilize the #ContentChild decorator to select the TemplateRef which we expect to be given via <ng-content>
This template is then put into a container using the *ngTemplateOutlet which also allows us to create a data-binding context to a local variable.
the context: {$implicit: item} tells Angular that any let-* variable defined on the template without any explicit binding should bind to the item property in our component.
Thus, we are able to reference this property in the template at the parent-component level.
Edit
Technically, the context binding is not necessary if you want to define the template directly inside of the child component, as you have a direct reference to the *ngFor template, however it becomes necessary if you want to lift the template out to the ParentComponent level to make the solution more reusable.
You are correct the reason for the bug (changing just the last element) is because when rendered you have multiple elements with the same select value.
A possible solution is to use template reference to pass the desired child component from the top level to the place where you want it to be projected.
Here is a working StackBlitz
import {
AfterViewInit,
Component,
Input,
QueryList,
ViewChildren
} from "#angular/core";
#Component({
selector: "my-app",
template: `
<child-comp #child [templateRef]="templateRef"> </child-comp>
<ng-template #templateRef>
<some-other-comp #someOtherComp></some-other-comp>
</ng-template>
`,
styleUrls: ["./app.component.css"]
})
export class AppComponent implements AfterViewInit {
h = 0;
i = 1;
j = 2;
k = 3;
#ViewChildren("someOtherComp") otherCompList: QueryList<SomeOtherComponent>;
ngAfterViewInit(): void {
this.otherCompList.toArray()[this.h].prop = this.h;
this.otherCompList.toArray()[this.i].prop = this.i;
this.otherCompList.toArray()[this.j].prop = this.j;
this.otherCompList.toArray()[this.k].prop = this.k;
}
}
#Component({
selector: "child-comp",
template: `
<div *ngFor="let value of [1, 2, 3, 4]; let i = index">
<!-- if ngIf is removed than only the last projection is diplayed -->
<ng-container *ngTemplateOutlet="templateRef"></ng-container>
</div>
`,
styleUrls: ["./app.component.css"]
})
export class ChildComponent {
#Input() templateRef;
}
#Component({
selector: "some-other-comp",
template: `
<p>{{ prop }}</p>
`,
styleUrls: ["./app.component.css"]
})
export class SomeOtherComponent {
prop: any;
}

How do I use #ViewChild with an external ng-template (Angular 11)

THE PROBLEM
So I have two Angular components, a parent and a child. The parent passes a custom template to the child component, which then hydrates the template with its own data using ngTemplateOutlet.
This works well for the most part. Unfortunately, I run into issues when trying to access the DOM elements of this parent template from the child.
If I try to access <div #container></div> from the default child template using #ViewChild('container',{static: false}), it gets the element without issue. When I do the same using the custom template passed in by app.component, I get the error "cannot read property 'nativeElement' of undefined".
What else do I have to do to access the DOM of my template?
Here's a Stackblitz
App.Component (Parent)
import { Component } from "#angular/core";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {}
<child [customTemplate]="parentTemplate"></child>
<ng-template #parentTemplate let-context="context">
<div #container>HELLO FROM CONTAINER</div>
<button (click)="context.toggleShow()">Toggle Display</button>
<div *ngIf="context.canShow">Container contains the text: {{context.getContainerText()}}</div>
</ng-template>
child.component (Child)
import {
Component,
ElementRef,
Input,
TemplateRef,
ViewChild
} from "#angular/core";
#Component({
selector: "child",
templateUrl: "./child.component.html",
styleUrls: ["./child.component.css"]
})
export class ChildComponent {
#Input() public customTemplate!: TemplateRef<HTMLElement>;
#ViewChild("container", { static: false })
public readonly containerRef!: ElementRef;
templateContext = { context: this };
canShow: boolean = false;
toggleShow() {
this.canShow = !this.canShow;
}
getContainerText() {
return this.containerRef.nativeElement.textContent;
}
}
<ng-container *ngTemplateOutlet="customTemplate || defaultTemplate; context: templateContext">
</ng-container>
<ng-template #defaultTemplate>
<div #container>GOODBYE FROM CONTAINER</div>
<button (click)="toggleShow()">Toggle Display</button>
<div *ngIf="canShow">Container contains the text: {{getContainerText()}}</div>
</ng-template>
MY QUESTION
How do I use #ViewChild to access this div from an outside template that updates with any changes in the DOM? (Note: Removing the *ngIf is NOT an option for this project)
What's causing this? Are there any lifecycle methods that I can use to remedy this issue?
MY HUNCH
I'm guessing that ViewChild is being called BEFORE the DOM updates with its new template and I need to setup a listener for DOM changes. I tried this and failed so I'd really appreciate some wisdom on how best to proceed. Thanks in advance :)
EDIT:
This solution needs to properly display <div #container></div> regardless of whether you're passing in a custom template or using the default one.
ViewChild doesn't seem to pick up a rendered template - probably because it's not part of the components template initially. It's not a timing or lifecycle issue, it's just never available as a ViewChild
An approach that does work is to pass in the template as content to the child component, and access it using ContentChildren. You subscribe to the ContentChildren QueryList for changes, which will update when the DOM element becomes rendered
You can then access the nativeElement (the div). If you wanted you could add listeners here to the DOM element, and trigger cd.detectChanges afterwards, but that would be a bit unusual. It would probably be better to handle DOM changes in the parent element, and pass the required values down to the child using regular #Input on the child
#Component({
selector: "my-app",
template: `
<child>
<ng-template #parentTemplate let-context="context">
<div #container>Parent Template</div>
</ng-template>
</child>
`,
styleUrls: ["./app.component.css"]
})
export class AppComponent {}
#Component({
selector: "child",
template: `
<ng-container *ngTemplateOutlet="customTemplate"> </ng-container>
`,
styleUrls: ["./child.component.css"]
})
export class ChildComponent implements AfterContentInit {
#ContentChild("parentTemplate")
customTemplate: TemplateRef<any>;
#ContentChildren("container")
containerList: QueryList<HTMLElement>;
ngAfterContentInit() {
this.containerList.changes.subscribe(list => {
console.log(list.first.nativeElement.innerText);
// prints 'Parent Template'
});
}
}

Get HTML content with styles in Angular

I have an application created using Angular. I need to get html of template of some big component but with styles that used there for classes. How can I do that in angular? Currently I'm trying to achieve this with getting innerHTML:
const myTemplate = document.getElementById("someWrapperId");
const content = myContent.innerHTML;
It doesn't return styled html.
I also tried with setting encapsulation: ViewEncapsulation.None - it also doesn't help in my case.
Styles are set in component.css file and not inline.
Hi I think you can use #ViewChild and ElementRef to get the HTML content style. I have a sample code below =>
HTML:
<div #infoDiv class='col-md-2' style='color: blue;width:152px'>INFO DIV</div>
<br>
Your Color:: {{styleColor}}
<br>
Your Width:: {{styleWidth}}
TS:
import { Component, ElementRef, ViewChild } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
styleColor:any='';
styleWidth:any='';
#ViewChild('infoDiv') infoDivRef: ElementRef;
ngAfterViewInit(): void {
if (this.infoDivRef.nativeElement) {
console.log(this.infoDivRef.nativeElement.style.color);
this.styleColor=this.infoDivRef.nativeElement.style.color;
this.styleWidth=this.infoDivRef.nativeElement.style.width;
}
}
}
NOTE: Code is also available in stackblitz. Please check the LINK DEMO link.

In Angular I want to project the nativeElement to another component without using innerHTML

In Angular I want to project (Transclude) a nativeElement to another component without using innerHTML.
Currently I'm trying to do this using ngComponentOutlet using a child component that has an ng-content.
I want to load the component and send html data to it all rendered so I dont need to use innerHTML to compile it to HTML.
Reason for this is, innerHTML looses the bindings.
This is my attempt:
My Parent Component:
import { Component, ContentChildren, QueryList } from '#angular/core';
import { TableRowComponent } from '../table-row/table-row.component';
import { TableCellComponent } from '../table-cell/table-cell.component';
#Component({
selector: 'ls-table-body',
template: `
<ng-container>
<tr ls-table-row *ngFor="let row of rows">
<ng-container *ngFor="let cell of cells">
<ng-container *ngComponentOutlet="tableCellComponent; content: [[cell.elem.nativeElement]]"></ng-container>
</ng-container>
</tr>
</ng-container>
`,
styleUrls: ['./table-body.component.scss']
})
export class TableBodyComponent {
#ContentChildren(TableRowComponent) rows: QueryList<TableRowComponent>;
#ContentChildren(TableCellComponent) cells: QueryList<TableCellComponent>;
public tableCellComponentComponent = TableCellComponent;
}
My Child Component called TableCellComponent:
import { Component, HostBinding, Input, ContentChildren, ViewEncapsulation, QueryList } from '#angular/core';
#Component({
selector: '[ls-table-cell]',
template: `
<ng-content></ng-content>
`,
styleUrls: ['./table-cell.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class TableCellComponent {
#HostBinding() #Input('class') classList = 'ls-table-cell';
#ContentChildren(TableCellComponent) cells: QueryList<TableCellComponent>;
#Input() span: number;
#Input() data: any;
}
Outcome I'm looking for:
I'm looking to compile the HTML - as I cannot use innerHTML due to the bindings getting removed - so I've optted for the ngComponentOutlet solution using ng-content - as I believe that compiles my HTML I pass through using cell.elem.nativeElement.
I'm also open to alternatives

multiple projections of same content using <ng-content> [duplicate]

depending on the value of a (boolean) class variable I would like my ng-content to either be wrapped in a div or to not be wrapped in div (I.e. the div should not even be in the DOM) ... Whats the best way to go about this ? I have a Plunker that tries to do this, in what I assumed was the most obvious way, using ngIf .. but it's not working... It displays content only for one of the boolean values but not the other
kindly assist
Thank you!
http://plnkr.co/edit/omqLK0mKUIzqkkR3lQh8
#Component({
selector: 'my-component',
template: `
<div *ngIf="insideRedDiv" style="display: inline; border: 1px red solid">
<ng-content *ngIf="insideRedDiv" ></ng-content>
</div>
<ng-content *ngIf="!insideRedDiv"></ng-content>
`,
})
export class MyComponent {
insideRedDiv: boolean = true;
}
#Component({
template: `
<my-component> ... "Here is the Content" ... </my-component>
`
})
export class App {}
Angular ^4
As workaround i can offer you the following solution:
<div *ngIf="insideRedDiv; else elseTpl" style="display: inline; border: 1px red solid">
<ng-container *ngTemplateOutlet="elseTpl"></ng-container>
</div>
<ng-template #elseTpl><ng-content></ng-content> </ng-template>
Plunker Example angular v4
Angular < 4
Here you can create dedicated directive that will do the same things:
<div *ngIf="insideRedDiv; else elseTpl" style="display: inline; border: 1px red solid">
<ng-container *ngTemplateOutlet="elseTpl"></ng-container>
</div>
<template #elseTpl><ng-content></ng-content></template>
Plunker Example
ngIf4.ts
class NgIfContext { public $implicit: any = null; }
#Directive({ selector: '[ngIf4]' })
export class NgIf4 {
private context: NgIfContext = new NgIfContext();
private elseTemplateRef: TemplateRef<NgIfContext>;
private elseViewRef: EmbeddedViewRef<NgIfContext>;
private viewRef: EmbeddedViewRef<NgIfContext>;
constructor(private viewContainer: ViewContainerRef, private templateRef: TemplateRef<NgIfContext>) { }
#Input()
set ngIf4(condition: any) {
this.context.$implicit = condition;
this._updateView();
}
#Input()
set ngIf4Else(templateRef: TemplateRef<NgIfContext>) {
this.elseTemplateRef = templateRef;
this.elseViewRef = null;
this._updateView();
}
private _updateView() {
if (this.context.$implicit) {
this.viewContainer.clear();
this.elseViewRef = null;
if (this.templateRef) {
this.viewRef = this.viewContainer.createEmbeddedView(this.templateRef, this.context);
}
} else {
if (this.elseViewRef) return;
this.viewContainer.clear();
this.viewRef = null;
if (this.elseTemplateRef) {
this.elseViewRef = this.viewContainer.createEmbeddedView(this.elseTemplateRef, this.context);
}
}
}
}
Remember that you can put all this logic in separate component! (based on yurzui answer):
import { Component, Input } from '#angular/core';
#Component({
selector: 'div-wrapper',
template: `
<div *ngIf="wrap; else unwrapped">
<ng-content *ngTemplateOutlet="unwrapped">
</ng-content>
</div>
<ng-template #unwrapped>
<ng-content>
</ng-content>
</ng-template>
`,
})
export class ConditionalDivComponent {
#Input()
public wrap = false;
}
You can then use it like this:
<div-wrapper [wrap]="'true'">
Hello world!
</div-wrapper>
I checked into this and found an open issue on the subject of multiple transclusions with the tag. This prevents you from defining multiple tags in a single template file.
This explains why the content is displayed correctly only when the other tag is removed in your plunker example.
You can see the open issue here:
https://github.com/angular/angular/issues/7795

Categories