Inner HTML of ng-container, Angular 4? - html

I want to dynamically place an html code in my html code, So I write the following code:
<ng-container [innerHTML]="buttonIcon"></ng-container>
Angular says innerHTML is not valid attribute for ng-container
I don't want to use third html tag like follows:
<div [innerHTML]="buttonIcon"></div>
So how can I insert html codes without any tag inner html binding?

[outerHTML]
will do the trick to replace the outer element.
In your case
<div [outerHTML]="buttonIcon"></div>
It's true, that it's important to have a clean HTML structure for e.g. keeping CSS rules as simple as possible.

You can use ngTemplate:
<ng-template #buttonIcon>
<div> Your html</div>
</ng-template>
<ng-container
*ngTemplateOutlet="buttonIcon">
</ng-container>

** Please read the comments. This answer might be wrong. I dont know, have not looked into it again **
ng-container does not get rendered to html, it is a mere structural directive.
The Angular is a grouping element that doesn't interfere with styles or layout because Angular doesn't put it in the DOM.
So there is no element to put html into. You need to work with a sub-div. If there is no need for a sub-div in your opinion, then you could most probably also just replace ng-container with div itself and not use the container at all.

If for any reason you need to replace the DOM element you can use a div with an id and then use the #ViewChild decorator and ElementRef to get access to the nativeElement from the controller and set the outerHtml property.
app.component.tml
<div #iconButton></div>
app.component.ts
import { Component, ViewChild, ElementRef, ViewEncapsulation, AfterViewInit }from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ],
encapsulation: ViewEncapsulation.None
})
export class AppComponent implements AfterViewInit{
#ViewChild('iconButton')
iconButton: ElementRef;
ngAfterViewInit(){
this.iconButton.nativeElement.outerHTML = '<button>My button</button>'
}
}
We need to use none as encapsulation policy because our template only includes the div to be replaced.
Stackblitz example: https://stackblitz.com/edit/angular-fa1zwp

Related

Using selector in HTML (Angular) doesn't follow structure of table [duplicate]

I am experimenting with angular2 2.0.0-beta.0
I have a table and the line content is generated by angular2 this way:
<table>
<tr *ngFor="#line of data">
.... content ....
</tr>
</table>
Now this works and I want to encapsulate the content into a component "table-line".
<table>
<table-line *ngFor="#line of data" [data]="line">
</table-line>
</table>
And in the component, the template has the <tr><td> content.
But now the table does no more work. Which means, the content is no longer shown in columns.
In the browser, the inspector shows me that the DOM elements look like this:
<table>
<table-line ...>
<tbody>
<tr> ....
How can I make this work?
use existing table elements as selector
The table element doesn't allow <table-line> elements as children and the browser just removes them when it finds them. You can wrap it in a component and still use the allowed <tr> tag. Just use "tr" as selector.
using <template>
<template> should be allowed as well but doesn't yet work in all browsers. Angular2 actually never adds a <template> element to the DOM, but only processes them internally, therefore this can be used in all browsers with Angular2 as well.
Attribute selectors
Another way is to use attribute selectors
#Component({
selector: '[my-tr]',
...
})
to be used like
<tr my-tr>
I found the example very usefull but it didn't work in the 2,2.3 build, so after much head scratching made it work again with a few small changes.
import {Component, Input} from '#angular/core'
#Component({
selector: "[my-tr]",
template: `<td *ngFor='let item of row'>{{item}}</td>`
})
export class MyTrComponent {
#Input("line") row:any;
}
#Component({
selector: "my-app",
template: `<h1>{{title}}</h1>
<table>
<tr *ngFor="let line of data" my-tr [line]="line"></tr>
</table>`
})
export class AppComponent {
title = "Angular 2 - tr attribute selector!";
data = [ [1,2,3], [11, 12, 13] ];
constructor() { console.clear(); }
}
Here's an example using a component with an attribute selector:
import {Component, Input} from '#angular/core';
#Component({
selector: '[myTr]',
template: `<td *ngFor="let item of row">{{item}}</td>`
})
export class MyTrComponent {
#Input('myTr') row;
}
#Component({
selector: 'my-app',
template: `{{title}}
<table>
<tr *ngFor="let line of data" [myTr]="line"></tr>
</table>
`
})
export class AppComponent {
title = "Angular 2 - tr attribute selector";
data = [ [1,2,3], [11, 12, 13] ];
}
Output:
1 2 3
11 12 13
Of course the template in the MyTrComponent would be more involved, but you get the idea.
Old (beta.0) plunker.
Adding 'display: contents' to the component style worked out for me.
CSS:
.table-line {
display: contents;
}
HTML:
<table>
<table-line class="table-line" [data]="line">
</table-line>
</table>
Why this works?
When instancing a component, angular (after compiling) wraps the content of the component in the DOM as follows:
<table>
<table-line>
<tr></tr>
</table-line>
</table>
But in order for the table to display properly, the tr tags can't be wrapped by anything.
So, we add display: contents, to this new element. As I understand, what this does is to tell the explorer that this tag should not be rendered, and display the inner content as if there was no wrapping around. So, while the tag still exists, it doesn't affect visually to the table, and the tr tags are treated as if they were direct children of the table tag.
If you'd like to investigate further on how contents works:
https://bitsofco.de/how-display-contents-works/
try this
#Component({
selecctor: 'parent-selector',
template: '<table><body><tra></tra></body></table>'
styles: 'tra{ display:table-row; box-sizing:inherit; }'
})
export class ParentComponent{
}
#Component({
selecctor: 'parent-selector',
template: '<td>Name</td>Date<td></td><td>Stackoverflow</td>'
})
export class ChildComponent{}

When should you use ng-content and component template with inputs and whats the difference?

What's the difference between using ng-content and using the childcomponent selector inside the parent?
I can't really figure out when and why one or the other.
Examples:
//Parent
#Component({
selector: 'app-parent',
template: '<app-child [text]="'lorem ipsum'"></app-child>',
})
//Child
#Component({
selector: 'app-child',
template: '<p>{{text}}</p>',
})
// ... component class with "#Input() text"
versus
#Component({
selector: 'app-parent',
template: '<app-child>
<p>lorem ipsum</p>
</app-child>',
})
//Child
#Component({
selector: 'app-child',
template: '<ng-content></ng-content>',
})
As you can see from your example, the content projection is a bit different from Input.
With the ng-content you are projecting your component from parent to child, and you can't work with that data on your child component.
Instead, with Input the data is provided by the parent, but you can modify it in order to fit your UI and you should add a signature to the data that are incoming and work on it:
#Input() yourData: YourInterface | YourType
And with content projection (ng-content) this is not possible, because you can just draw the element from above and the child doesn't know what it is. It's logic will be: draw what it receives, no matter what.

Angular change html tag programmatically when inheriting template

I have a Component, DataGrid, which represents a table with expandable rows. Each row, when expanded, shows a child DataGrid table, which is very similar to the parent DataGrid component.
Therefore I defined a base class DataGridComponent, from which the child inherits the both the component and the template. however, I need to change one of the tags in the child's template. Do I have to rewrite the entire template, or could I just point the templateUrl to the parent's template and programmatically change the one html tag that I need to change?
Minimal Example:
base.component.ts
#Component({
selector: 'datagrid',
templateUrl: 'datagrid.component.html'
})
export class DataGridComponent {
childEnabled:boolean = true;
// stuff
}
datagrid.component.html
<div>...</div>
<div *ngIf="childEnabled">
<childgrid
[options]="childOptions"
>
</childgrid>
</div>
child.component.ts
#Component({
selector: 'childgrid',
templateUrl: 'datagrid.component.html' // <-- POINT TO BASECLASS TEMPLATE
})
export class ChildGridComponent extends DataGridComponent{
}
childgrid.component.html // <-- HOW THE (REAL) TEMPLATE SHOULD BE
<div>...</div>
<div *ngIf="childEnabled">
<grandchildgrid
[options]="childOptions"
>
</grandchildgrid>
</div>
grandchild.component.ts
#Component({
selector: 'grandchildgrid',
templateUrl: 'datagrid.component.html' // <-- POINT TO BASECLASS TEMPLATE
})
export class GrandChildGridComponent extends DataGridComponent{
constructor() {
super();
childEnable=false;
}
}
grandchildgrid.component.html // <-- HOW THE (REAL) TEMPLATE SHOULD BE
<div>...</div>
<div *ngIf="childEnabled">
<grandchildgrid
[options]="childOptions"
>
</grandchildgrid>
</div>
and so on until childEnabled is set to false. Is there any chance to do something like this and is it something that would make sense from an angularly point of view? Would ng-content be of any help in this case?
The content of DataGrid can go into a separate component and that can be used as a template in both parent and child DataGrid.
Alternative option is to have the same tags and control behavior using different class and id for parent and child

Angular interpolate inside a component like mat-checkbox

So I want to have a mat-checkbox component with a HTML string inside the label.
I tried the following:
<mat-checkbox class="check">
{{ someHtml }}
</mat-checkbox>
But it prints the HTML string as a string and doesn't render it.
Using the following doesn't work either:
<mat-checkbox class="check" [innerHtml]="someHtml">
</mat-checkbox>
This just replaces the whole content, including the checkbox that gets generated at runtime. Is there any way to inject the html into the label?
You could use Angular Directives
The idea here is to fetch the element from the HTML, then append some raw HTML dynamically.
Supose this scenario
app.component.html
<mat-checkbox class="check" [appendHtml]="innerHtml"></mat-checkbox>
app.component.ts
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
innerHtml = `<div style="border: 1px solid red;"> Text inside </div>`;
constructor() {}
}
As you can see, I added a appendHtml attribute to the mat-checkbox element. This is a custom directive that expects a string as "raw" HTML.
append-html.directive.ts
#Directive({
selector: '[appendHtml]'
})
export class AppendHtmlDirective implements AfterViewInit {
#Input('appendHtml') html: string
constructor(private element: ElementRef) {
}
ngAfterViewInit() {
const d = this.element.nativeElement.querySelector('label');
d.insertAdjacentHTML('beforeend', this.html);
}
}
The AppendHtmlDirective expects an html property of type string and implements AfterViewInit interface (from Angular) to fetch the element once it is rendered. By injection, Angular provides us the element which is being applied; so, the ElementRef from the constructor is our MatCheckbox element, in that case.
We can use the insertAdjacentHTML function to append childs to the element. I just fetched the label element from the MatCheckbox to fit inside of it. In every case, you should see where to append the HTML.
I mean, label here works, bc MatCheckbox has a tag whitin matching that. If you want to reuse this Directive for other elements, you should be passing the literal to find inside.
i.e.:
append-hmtl.directive.ts
// ...
#Input() innerSelector: string
// ...
ngAfterViewInit() {
const d = this.element.nativeElement.querySelector(this.innerSelector);
d.insertAdjacentHTML('beforeend', this.html);
}
app.component.hmtl
<mat-checkbox class="check" [appendHtml]="innerHtml" innerSelector="label"></mat-checkbox>
Moreover, you can pass as many inputs as you need to customize the styling or behavior of your directive.
Cheers
I think you should just wrap everything in a div and put it on the outside.
<div>
<mat-checkbox class="check"> </mat-checkbox>
{{ someHtml }}
</div>

How to access html elements of component tag?

I want to access plain HTML declared in my component tag. Suppose I have component
#Component({
selector: 'app-demo'
template: '<some_template></some_template>'
})
export class AppDemoComponent {
}
if I am defining h1 inside the tag in another component
<app-demo>
<h1> demo text </h1>
</app-demo>
How can I access the h1 element inside the AppDemoComponent?
Edit:
This question is not about ViewChild as ViewChild gets information from the current template of the component. I'm asking if the component tag is called in the different file and the tag has HTML elements then how to access it.
Use ElementRef
You can use ElementRef to access the current component reference, allowing you to query it for nested elements.
getElementsByTagName, querySelectorAll, and getElementsByClassName will look down into the nested elements as they operate by inspecting what's rendered in the DOM, ignoring any encapsulation Angular does.
I am not sure if there is an Angular specific way to do it, but using vanilla JS lets you get there.
Child Component
import { Component, OnInit } from "#angular/core"
#Component({
selector: 'app-demo-child'
template: `
<h1> demo text </h1>
<h1 class="test"> demo text2 </h1>
<h1> demo text3 </h1>
`
})
export class AppChildComponent {
}
Parent Component
import { Component, OnInit, ElementRef } from "#angular/core"
#Component({
selector: 'app-demo-parent'
template: `
<app-demo-child></app-demo-child>
`
})
export class AppParentComponent {
constructor(
private elRef:ElementRef
) { }
doStuff() {
let HeaderElsTag = this.elRef.nativeElement.getElementsByTagName('h1') // Array with 3 h3 elements
let HeaderElsQuer = this.elRef.nativeElement.querySelectorAll('h1') // Array with 3 h3 elements
let HeaderElsClass = this.elRef.nativeElement.getElementsByClassName('test') // Array with 1 h3 element
}
}
Warning, this will look indiscriminately within your component, so be careful that you don't have nested elements with the same class name otherwise you'll have some hard to debug side effects
You can use content children. for your reference please follow the link below:
content children vs view children