Angular DomSanitizer not binding angular component - html

I am trying to add html content dynamically into a DIV. Statically this works nicely.
Code which works:
<popover-content #pop1
title="Cool Popover"
placement="right"
[closeOnClickOutside]="true">
Popped up!!!
</popover-content>
<div>
<span>Testing with <span [popover]="pop1" [popoverOnHover]="true">popover</span> as they are not working with DomSanitizer</span>
</div>
Now I need to generate this div content in the backend and then have to dynamically add this inside the div.
Code which doesn't work:
HTML:
<popover-content #pop1
title="Cool PopOver"
placement="right"
[closeOnClickOutside]="true">
Popped up!!!
</popover-content>
<div [innerHtml]="message | safeHtml">
</div>
.ts file:
this.message = '<span>Testing with <span [popover]="pop1" [popoverOnHover]="true">popover</span> as they are not working with DomSanitizer</span>'
Pipe:
import {Pipe, PipeTransform} from '#angular/core';
import {DomSanitizer} from '#angular/platform-browser';
#Pipe({
name: 'safeHtml'
})
export class SafeHtmlPipe implements PipeTransform {
constructor(private sanitized: DomSanitizer) {
}
transform(value) {
return this.sanitized.bypassSecurityTrustHtml(value);
}
}
After this also the popover component was not getting called.
While inspecting, I did see that, for dynamically added innerHtml content to DIV, angular is not adding some special behavior to the tag attributes. Why so?
And how can I make it work?

With [innerHTML]="..." you can add HTML to the DOM, but Angular won't care what HTML it contains, except for sanitization.
Angular components, directives, event and property bindings only work for HTML added statically to a components template.
What you can do is to compile the HTML with a components template at runtime like explained in How can I use/create dynamic template to compile dynamic Component with Angular 2.0?

Related

Angular 10 Load SVG Dynamically Without IMG or OBJECT Tags

I'm working on a simplified way to load SVG files without using IMG or OBJECT tags as it impedes my ability to control fill colors through external CSS. Using inline SVG is ideal, but with so many components using repeated icons, it's a lot of maintenance and I'd prefer to centralize them in their .svg file format. I thought about just making each one their own component, but that means there's a component.ts file I don't need for each one, and it might be a little confusing or other developers.
So far, creating a custom element that pulls the svg location from a "src" attribute is working:
#Component({
selector: 'app-svg',
template: `
<ng-template>
{{ src }}
</ng-template>
<span [innerHTML]="svg"></span>
`
})
export class SvgComponent implements OnInit {
svg: SafeHtml = '';
#Input() public src = '';
constructor(private http: HttpClient, private sanitize: DomSanitizer) {
}
ngOnInit(): void {
this.http.get(this.src, {responseType: 'text'}).subscribe(svg => {
this.svg = this.sanitize.bypassSecurityTrustHtml(svg);
});
}
}
Then I use my custom element in another component.html:
<app-svg src="assets/test.svg"></app-svg>
The result of course is an inline SVG with an inline element as a wrapper:
<app-svg src="assets/test.svg" ng-reflect-source="assets/test.svg">
<span>
<svg>
<path d="...">
</svg>
</span>
</app-svg>
I suppose this is harmless enough, but it's a little annoying and there's unnecessary extra markup. Ideally, I'd want to have the innerHTML applied to APP-SVG, but that means the svg in the binding would need to exist outside of the TS for for the custom element due to scoping issues. It's also messy having to remember to include [innerHTML] on every APP-SVG tag. I've tried using [outerHTML] on the SPAN tag in the template, but I get a runtime error saying there is no parent container element.
So, my question is can this work?:
Replace the in the template with the loaded SafeHtml? Or,
Apply the loaded SafeHtml as the innerHTML of the selector in the SvgComponent TS? Or,
Use <svg [innerHTML]="svg"> as part of the template instead of SPAN, but remove the parent SVG from the loaded SafeHtml before applying it to the innerHTML? Or,
Is there something in NPM that already does what I'm trying to create?
I wish they made this easier. Any advice or explanation as to why this won't work would be greatly appreciated. Thank you!
Naturally, as SOON as I post my question, I trip over the solution. The trick is to use ElementRef so that I can target the selector's innerHTML, and I don't have to use DomSanitizer to do it. The new code looks as follows (including imports this time):
import {Component, OnInit, Input, ElementRef} from '#angular/core';
import {HttpClient} from '#angular/common/http';
#Component({
selector: 'app-svg',
template: `
<ng-template>
{{ src }}
</ng-template>
`
})
export class SvgComponent implements OnInit {
#Input() public src = '';
constructor(
private el: ElementRef,
private http: HttpClient,
) {}
ngOnInit(): void {
this.http.get(this.src, {responseType: 'text'}).subscribe(svg => {
this.el.nativeElement.innerHTML = svg;
});
}
}
If you don't want to have app-svg as a container, you can use instead:
this.el.nativeElement.outerHTML = svg;
And it will replace app-svg with he loaded svg. Hope this helps anyone else trying to accomplish the same thing. Cheers!

Having a date inside a div with innerHTML content

I have a <div> and I want to have the date inside it. (For some reasons I can't change this configuration) This box may have a text with html properties. It is clear that I should use [innerHTML] but when using it, I can't put the date inside the <div>. I'm wondering if there is any way to have the text without showing the properties like <br/> and having a line break instead, and also having the date just inside the div?
Here is my StackBlitz
text = 'Hi<br/>How are you?';
<div>{{ text }}</div>
<hr/>
<div>{{ text }}<span>HH:MM</span></div>
<hr/>
<div [innerHTML]="text"></div>
<hr/>
<div [innerHTML]="text"><span>HH:MM</span></div>
The snippet is not working because it is not Angular. Please refer to my code above.
You could replace your <br> tags with \n and then add CSS to preserve the line break:
div {
white-space: pre-wrap;
}
<div>{{ text }}</div>
text = 'Hi\nHow are you?';
To put html content to the div, it is needed to convert the html code to stay safe from XSS using DomSanitizer.
So it will be good to generate a new pipe to do this action as follows.
import { Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
import DOMPurify from 'dompurify';
#Pipe({
name: 'safeHtml'
})
export class SafeHtmlPipe implements PipeTransform {
constructor(protected sanitizer: DomSanitizer) {}
public transform(value: any, type: string): any {
const sanitizedContent = DOMPurify.sanitize(value);
return angular.bypassSecurityTrustHtml(sanitizedContent);
}
}
And on the main module, you can import this pipe and can use it as follows.
<div [innerHTML]="text | safeHtml"></div>

Adding to component constructor in Angular makes the entire page return blank?

I am trying to add a basic MatDialog to my project. In the project I have 2 components, a header for the page and another called "CardBox", which basically just holds cardboxes of links to different websites.
When you click on the "i" icon, I would like to open a dialog box with more information.
See image below.
Initially, my understanding was that I just add a MatDialog field in the constructor of Cardbox component. Like so:
cardboxes.component.html
<mat-card id="CARDBOX">
<img class="info" src="path/image.jpg" alt="image" height=25px (click)="openDialog()"/>
</mat-card>
cardboxes.component.ts
#Component({
selector: 'app-cardbox',
templateUrl: './cardbox.component.html',
styleUrls: ['./cardbox.component.scss']
})
export class CardboxComponent implements OnInit {
constructor(private dialog: MatDialog) { }
ngOnInit(): void {}
openDialog() {
this.dialog.open(CardBoxComponent);
}
}
(I'm aware that this is calling its own component, and would just open the same thing again. I am just trying to get it to work first.)
app.component.html
<div id="bg">
<app-header></app-header>
<br>
<app-cardbox></app-cardbox>
</div>
However, in doing so, it removes EVERYTHING from the page except the background, including the header component. This is what it looks like when the program is run when there is SOMETHING in the constructor of Cardbox.
As you can see, having something in the constructor gets rid of everything on the page, which does not make sense to me as it removes the header, which is a completely separate component from the cardbox. I have tried everything to make it work but still it is not working.
Why is touching the constructor makes the entire project blank? Is there something I forgot to add to another file? And how can I add a MatDialog popup feature to the project in a way that works?
TLDR: When I put anything in the constructor of one of my components, the entire page disappears. How do I resolve this?
Still seeking answer to this :(
You are using it wrong.
I am surprised your app compiles when doing this.dialog.open(CardBoxComponent)
What you need to do is, first create your dialog component.
To make things simple you can create it in the same file as you CardBox component, but make sure you put it outside CardBox class:
cardboxes.component.ts
#Component({
selector: 'dialog-overview-example-dialog',
templateUrl: 'dialog-overview-example-dialog.html',
})
export class DialogOverviewExampleDialog {
constructor(
public dialogRef: MatDialogRef<DialogOverviewExampleDialog>,
// data is gonna be the data you pass to dialog when you open it from CardBox
#Inject(MAT_DIALOG_DATA) public data: DialogData) {}
onNoClick(): void {
this.dialogRef.close();
}
}
then you create a template for the dialog component:
dialog-overview-example-dialog.html
<h1 mat-dialog-title>more info</h1>
<div mat-dialog-content>
<p>{{data.info}}</p>
</div>
finally you add openDialog(myInfo) function to your ts file, inside CardBox component:
cardboxes.component.ts
openDialog(myInfo): void {
const dialogRef = this.dialog.open(DialogOverviewExampleDialog, {
width: '250px',
// data you pass to your dialog
data: {info: myInfo}
});
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
this.animal = result;
});
}
and add it to your template too:
cardboxes.component.ts
<mat-card id="CARDBOX">
<img class="info" src="path/image.jpg" alt="image" height=25px (click)="openDialog('info about first site')"/>
</mat-card>
in this example I pass the info as a text, but it can be an object too.
Here is a demo to make things easier for you: link

Iframe code is converting into a dom object when binding from Angular component to HTML template

I am working on a project based on Angular 2. I got stuck in one problem. I am dealing with Iframe. In my angular component, I am generating an Iframe as:
this.ifrm = document.createElement("iframe");
console.log(title_colorr);
this.ifrm.setAttribute("src", "http://web.com/newsfeed/");
this.ifrm.style.width = widthh + "px";;
this.ifrm.style.height = heightt + "px";
I am getting a full Iframe code when I am putting
console.log(this.ifrm); as:

<iframe src="http://web.com/newsfeed/" style="width: 250px; height: 250px;"></iframe>
Now the problem is when I am trying to use this in my HTML template as:
<p >{{ifrm}}</p> then I am getting :
[object HTMLIFrameElement]
I have used <p [innerHTML]="ifrm"></p> too, but no solution is there.
Is there anybody, who knows how to solve this?
Import ElementRef and Renderer2 from '#angular/core' and change your constructor to
constructor(private elementRef: ElementRef, private renderer: Renderer2) { }
Now you can use appendChild function of renderer as follows:
this.renderer.appendChild(this.elementRef.nativeElement, this.ifrm);
In your html file you can just have
<p></p>
And it should work.

Styled HTML content dynamically switched with tabs using Angular 2

I am attempting to create a reusable angular2 component that accepts an array of URLs to html files on my server and creates a content window with tabs to switch between "chapters", effectively swapping out the html and css inside the content window. I have tried all sorts of things including iframes but those don't work, the angular 1 ng-include work-arounds that I can find on StackOverflow but they have all since been deprecated, and the closest I've got is building a component that you can #Input html and it interpolates the content but style won't apply and angular strips out any style or script tags. Here is what I have tried.
In my parent component class:
htmlInput: string = "<h1>Why Does Angular make this so hard?</h1>";
cssInput: string = "h1 { color:red; }"
Parent Component HTML:
<app-html [html]='htmlInput' [css]='cssInput'></app-html>
My HTML Component:
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'app-html',
template: '<div [innerHtml]=html></div>', //This works but no style
//template: '{{html}}', //This displays the actual markup on page
styles: ['{{css}}'] //This does nothing
//styles: ['h1 { color: red; }']//Also nothing
})
export class HtmlComponent implements OnInit {
#Input() html: string = "";
#Input() css: string = "";
ngOnInit() {
}
}
The result of this code is
Why Does Angular make this so hard?
But no red color. Maybe style is applied before the innerHtml is added to DOM? I don't know but just putting {{html}} results in displaying the actual markup with the h1 tags visible.
The reason I want to do it this way is that I have a bunch of HTML pages already created sitting in a folder on my server from before I angularized my site that all share a single style sheet. I'd like to just be able to flip through them like pages in a book without reloading the page and since there are so many and I'm likely to add more all the time, I'd really rather not create routing for every single one. (I already have routing for basic site navigation.)
Does anybody have a better suggestion for how to embed styled HTML into a page dynamically in the most recent version of Angular 2? At the time of this post we are in 2.0.0-beta.17.
OR... I already figured I may be approaching this issue from the entirely wrong angle. There must be a reason Angular is making this so difficult and deprecating all the solutions people have come up with so If anyone has a suggestion about how I could achieve the same results in a more angular friendly way I'd love to hear that too.
Thank you.
Edit:
I was able to fix my issue by creating a pipe which sanatizes the html before adding it to an iframe.
import { Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
#Pipe({ name: 'safe' })
export class SafePipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
transform(url: string) {
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
}
}
And then you can just pass your html into the iframe.
<iframe width="100%" height="1000" frameBorder="0" [src]="url | safe"></iframe>
This is useful to me since I have some old pages that use all sorts of jquery and style etc. This works as a quick fix to have them show up.
Angular2 rewrites the styles added to a component by including the dynamically added attributes like _ngcontent-yle-18 into the CSS selectors.
Angular2 uses this to emulate shadow DOM style encapsulation. These attributes are not added to dynamically added HTML (for example with innerHTML).
Workarounds
add styles to index.html because these styles are not rewritten by Angular2
set ViewEncapsulation.None because then Angular doesn't add the encapsulation emulation attributes
use /deep/ to make Angular2 ignore the encapsulation emulation attributes
See also Angular 2 - innerHTML styling
You should wrap your css into an object and use ngStyle to bind it to your component rather than the styles attribute, because styles does not support data binding.
Example:
htmlInput: string = "<h1>Why Does Angular make this so hard?</h1>";
cssInput: string = "{h1 { color:red; }}"
Parent Component HTML:
<app-html [html]='htmlInput' [css]='cssInput'></app-html>
Your HTML Component:
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'app-html',
template: '<div [innerHtml]="html" [ngStyle]="css"></div>',
styles: []
})
export class HtmlComponent implements OnInit {
#Input() html: string = "";
#Input() css: string = "";
ngOnInit() {
}
}