Angular 5 Dynamic template in MatDialog - javascript

in my component I open a MatDialog and pass data to it. In my object data.obj is under this.data.obj.html html-code stored.
In electron I would use a webview to display the html-site.
How do I display the html-code in proper way in my MatDialog in angular 5? Its possible to create the template dynamically or is there any smoother way?
#Component({
selector: 'dialog-popup',
template:`
<h1 mat-dialog-title>Content-HTML</h1>
<mat-dialog-content>
{{this.data.obj.html}}
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button>Complain</button>
<button mat-button (click)=onNoClick()>Cancel</button>
</mat-dialog-actions>
`
})
export class DialogOverview {
constructor(
public dialogRef: MatDialogRef<DialogOverview>,
#Inject(MAT_DIALOG_DATA) public data: any) { }
ngOnInit() {
console.log(this.data.obj.html);
}
onNoClick(): void {
this.dialogRef.close();
}
}

You can bind it to the [innerHtml] property of a html element
<mat-dialog-content>
<div [innerHtml]="data.obj.html | keepHtml"></div>
</mat-dialog-content>
You can use dom sanitizer and write a html pipe like below to escape html sanitizing
import { Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
#Pipe({ name: 'keepHtml', pure: false })
export class KeepHtmlPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) { }
transform(content) {
return this.sanitizer.bypassSecurityTrustHtml(content);
}
}

Related

How to bind statically defined HTMl content and click Event returned from java service to angular component

I am using angular 5 and java 8 for my web application. I am having a java service which return html content with angular click event. The same i am binding to angular component. The html content is working but click event is not working.
Below is the sample code from Java
#RequestMapping(value="/hitSample",method = RequestMethod.GET)
public String hitSample() {
StringBuilder sb = new StringBuilder();
sb.append("<a (click)=\"callSampleFunction()\"><p>This is a paragraph.</p> A Tag end
</a>");
sb.append("<p>This is a paragraph. 2 </p>");
return sb.toString();
}
SampleComponent.ts
import { Component, OnInit } from '#angular/core';
import {ViewReviewService} from '../../services/view-review-service';
import {Response} from '#angular/http';
#Component({
selector: 'app-sample-hit',
templateUrl: './sample-hit.component.html',
styleUrls: ['./sample-hit.component.css']
})
export class SampleHitComponent implements OnInit {
sampleData: any;
constructor(private viewReviewService: ViewReviewService) { }
ngOnInit() {
this.hitSample();
}
hitSample() {
this.viewReviewService.hitSample().subscribe((res: Response) => {
console.log(res['_body']);
this.sampleData = res['_body'];
});
}
callSampleFunction() {
alert('got call');
}
}
sampleComponent.html
<div [innerHTML]="sampleData" > </div>
I want callSampleFunction() to be triggered on click.
You can use the same way you've used <div [innerHTML]="sampleData" > </div> but you need to sanitise the string template for security reasons.
<div [innerHtml]="sampleData | safeHtml">
SafeHtmlPipe.ts
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 this.sanitizer.bypassSecurityTrustHtml(sanitizedContent);
}
}

Angular - Structural directive with embedded view does not pass children to ng-template

I've got a structural directive that creates an embedded view by looking up a template ref using ng-template. My problem is that from this parent component (with structural directive), I cannot pass down children.
Parent component with structural directive
import { ViewChild, Component, OnInit, ElementRef } from "#angular/core";
import { TestJsonService } from "../../services/test-json.service";
#Component({
selector: "xfr-json-renderer",
template: `
<template-lookup></template-lookup>
<div class="NA-TEMPLATE-CHOOSER" *replaceWith="'flexCol'">
<div>Why can't i pass this down to the child?</div>
</div>
`,
styleUrls: ["./json-renderer.component.css"],
})
export class JsonRendererComponent implements OnInit {
#ViewChild("childTemplate") childTemplate;
constructor(el: ElementRef, json: TestJsonService) {}
ngOnInit(): void {}
ngAfterViewInit() {}
}
Child component
import { Injectable, TemplateRef, Component, ViewChild } from "#angular/core";
#Injectable()
export class TemplateStore {
templates = new Map<string, TemplateRef<any>>();
}
#Component({
selector: "template-lookup",
template: `
<ng-template #flexRow></ng-template>
<ng-template #flexCol><xfr-flex-col>
// I want to pass the children into here
</xfr-flex-col></ng-template>
`,
})
export class TemplateLookup {
#ViewChild("flexRow") flexRowTemplate;
#ViewChild("flexCol") flexColTemplate;
constructor(private service: TemplateStore) {}
ngAfterViewInit() {
this.service.templates.set("flexRow", this.flexRowTemplate);
this.service.templates.set("flexCol", this.flexColTemplate);
}
}
Structural directive
import { ViewContainerRef } from "#angular/core";
import { TemplateStore } from "./../services/composite-template.service";
import { Directive, Input } from "#angular/core";
#Directive({
selector: "[replaceWith]",
})
export class CompositeTemplateDirective {
#Input() replaceWith: "flex-col" | "flex-row";
constructor(private service: TemplateStore, private view: ViewContainerRef) {}
ngAfterViewInit() {
this.view.createEmbeddedView(this.service.templates.get(this.replaceWith));
}
}
The problem is that you need to use internal API for that, what is not the best thing. I would use it until I stay with the same angular version and would test it before every update - then should work stable.
I was able to do the injection with Angular 9, quite sure a similar solution (but different internal API) can be applied for other angular versions.
The main thing for the injection - where to inject the content, in components we could use ng-content, but here it wouldn't work, because we have different component contexts. In this case we could use <ng-template [ngTemplateOutlet]></ng-template> to tell the script where we want the injection.
here you can find a live demo: https://codesandbox.io/s/nifty-wright-335bm?file=/src/app/json-renderer.component.ts
CompositeTemplateDirective
import {NgTemplateOutlet} from '#angular/common';
import {AfterViewInit, Directive, Input, TemplateRef, ViewContainerRef} from '#angular/core';
import {TemplateStore} from 'src/app/TemplateLookup/TemplateLookup';
#Directive({
selector: '[replaceWith]',
})
export class CompositeTemplateDirective implements AfterViewInit {
#Input() replaceWith: 'flex-col' | 'flex-row';
constructor(
private service: TemplateStore,
private view: ViewContainerRef,
private templateRef: TemplateRef<any>,
) {
}
public ngAfterViewInit(): void {
const wrapper = this.service.templates.get(this.replaceWith);
const source = this.templateRef;
const view: any = this.view.createEmbeddedView(wrapper);
let directive: NgTemplateOutlet;
const nodes: Array<any> = view._lView ? view._lView : view._view && view._view.nodes ? view._view.nodes : [];
for (const node of nodes) {
if (typeof node !== 'object') {
continue;
}
if (node instanceof NgTemplateOutlet) {
directive = node;
}
if (typeof node.instance === 'object' && node.instance instanceof NgTemplateOutlet) {
directive = node.instance;
}
}
if (directive) {
directive.ngTemplateOutlet = source;
directive.ngOnChanges({
ngTemplateOutlet: {
previousValue: null,
currentValue: source,
firstChange: true,
isFirstChange: () => true,
},
});
}
}
}
TemplateLookup
import {AfterViewInit, Component, Injectable, TemplateRef, ViewChild} from '#angular/core';
#Injectable()
export class TemplateStore {
templates = new Map<string, TemplateRef<any>>();
}
#Component({
selector: 'template-lookup',
template: `
<ng-template #flexRow>
<div>
flexRow template
</div>
</ng-template>
<ng-template #flexCol>
<div>
<div>wrap</div>
<ng-template [ngTemplateOutlet]></ng-template>
<div>wrap</div>
</div>
</ng-template>
`,
})
export class TemplateLookup implements AfterViewInit {
#ViewChild('flexRow', {static: false}) flexRowTemplate;
#ViewChild('flexCol', {static: false}) flexColTemplate;
constructor(
private service: TemplateStore,
) {
}
ngAfterViewInit() {
console.log('TemplateLookup:ngAfterViewInit');
this.service.templates.set('flexRow', this.flexRowTemplate);
this.service.templates.set('flexCol', this.flexColTemplate);
}
}
so the most pragmatic thing here seems to be to just put the child you want to pass as a child of the template-lookup component and use ng-content...
do this in the parent:
<template-lookup>
<div>I will pass to child</div>
</template-lookup>
<div class="NA-TEMPLATE-CHOOSER" *replaceWith="'flexCol'">
</div>
and this in the child:
<ng-template #flexRow></ng-template>
<ng-template #flexCol>
<xfr-flex-col>
<ng-content></ng-content>
</xfr-flex-col>
</ng-template>
and that will solve your problem / fulfill the stated requirements.
You could also consider a rewrite to your service to solve timing problems between templates being set and gotten once and for all:
import { Injectable, TemplateRef } from "#angular/core";
import {ReplaySubject} from 'rxjs';
import {map, filter, distinctUntilChanged} from 'rxjs/operators';
#Injectable({providedIn: 'root'}) // provide appropriately, root for example
export class TemplateStore {
private templates = new Map<string, TemplateRef<any>>();
private tmpSource = new ReplaySubject<Map<string, TemplateRef<any>>>(1);
setTemplate(key: string, template: TemplateRef<any>) {
this.templates.set(key, template);
this.tmpSource.next(this.templates)
}
getTemplate(key: string) {
return this.tmpSource.pipe(
map(tmpMap => tmpMap.get(key)),
filter(tmp => !!tmp),
distinctUntilChanged()
)
}
}
and make the associated changes in the directive and child components...
export class CompositeTemplateDirective implements OnInit, OnDestroy {
#Input() replaceWith: "flex-col" | "flex-row";
private sub: Subscription;
constructor(private service: TemplateStore, private viewContainer: ViewContainerRef) { }
ngOnInit() {
this.sub = this.service.getTemplate(this.replaceWith).subscribe(t => {
this.viewContainer.clear()
this.viewContainer.createEmbeddedView(t)
})
}
ngOnDestroy() {
this.sub.unsubscribe()
}
}
export class TemplateLookup {
#ViewChild("flexRow") flexRowTemplate;
#ViewChild("flexCol") flexColTemplate;
constructor(private service: TemplateStore) {}
ngAfterViewInit() {
this.service.setTemplate("flexRow", this.flexRowTemplate);
this.service.setTemplate("flexCol", this.flexColTemplate);
}
}
functioning example: https://stackblitz.com/edit/angular-ygdveu
it's been pointed out that this doesn't support nesting... so make the following adjustments and you can nest. in template lookup, you'll need to use the SkipSelf modifier in your constructor, and also provide the TemplateStore... in the case of no nesting, this will have no effect, SkipSelf just tells the injector to start looking for the service at the parent rather than at the component:
#Component({
selector: "template-lookup",
template: `
<ng-template #flexRow>FLEX ROW</ng-template>
<ng-template #flexCol>
FLEX COL
<div class="flex-col">
<ng-content></ng-content>
</div>
</ng-template>
`,
providers: [TemplateStore]
})
export class TemplateLookup {
#ViewChild("flexRow") flexRowTemplate;
#ViewChild("flexCol") flexColTemplate;
constructor(#SkipSelf() private service: TemplateStore) {}
ngAfterViewInit() {
this.service.setTemplate("flexRow", this.flexRowTemplate);
this.service.setTemplate("flexCol", this.flexColTemplate);
}
}
then you can nest to your hearts content like so:
<template-lookup>
<div>I can pass this to the child!</div>
<template-lookup>NESTED</template-lookup>
<div class="nested-content" *replaceWith="'flexCol'"></div>
</template-lookup>
<div class="NA-TEMPLATE-CHOOSER" *replaceWith="'flexCol'">
</div>
which is a little ugly, as you need to repeat the template-lookup component, but it does get the job done. This works by allowing the directive and template lookup to communicate with a different copy of the TemplateStore so you can nest different content.
working example of this variant: https://stackblitz.com/edit/angular-lpner2

Ionic3 tags like click function are not working in InnerHTML

Ionic tags not working in ionic 3. for example (click) method is not working with [innerHTML]. Below is my code.
#IonicPage()
#Component({
selector: 'page-html-test',
templateUrl: 'html-test.html',
})
export class detailNewsPage {
html: any;
constructor(public navCtrl: NavController,
public navParams: NavParams,
) {}
ionViewDidLoad() {
this.detailNews = `<button ion-button (click)="detailNews1()">Test</button>`;
}
detailNews1(){
console.log('test-detailNews1 button clicked');
}
}
**htmlTest.html:**
<ion-content padding>
<div [innerHTML]="detailNews | safeHtml"></div>
</ion-content>
**safeHtml.ts**
#Pipe({
name: 'safeHtml',
})
export class SafeHtmlPipe implements PipeTransform {
/**
* Takes a value and makes it lowercase.
*/
constructor(private sanitizer:DomSanitizer){}
transform(html) {
return this.sanitizer.bypassSecurityTrustHtml(html);
}
}
Any help, much appreciate

How to add mattooltip by custom directive in Angular

I am creating a custom directive called TooltipDirective whihc is going to add matTooltip to every host element, code is like below
import { Directive, ElementRef, Input, OnInit, Renderer } from '#angular/core';
#Directive({
selector: '[tooltip]'
})
export class TooltipDirective implements OnInit
{
#Input() tooltip: string;
constructor(private hostElement: ElementRef, private renderer: Renderer)
{
}
ngOnInit()
{
this.renderer.setElementAttribute(this.hostElement.nativeElement, 'matTooltip', this.tooltip);
}
}
In my html I have two elements to compare the result
<i class="material-icons" tooltip="Test Tooltip">reply_all</i>
<i class="material-icons" matTooltip="Test Tooltip">reply_all</i>
in the result html tooltip and mattooltip attributes are added but it doesn't show the tooltip.
and rendered html is like below
<i _ngcontent-c10="" class="material-icons" tooltip="Test Tooltip" mattooltip="Test Tooltip" ng-reflect-tooltip="Test Tooltip">reply_all</i>
<i _ngcontent-c10="" class="material-icons" mattooltip="Test Tooltip" aria-describedby="cdk-describedby-message-1" cdk-describedby-host="" ng-reflect-message="Test Tooltip">reply_all</i>
I tried adding other extra attributes but still doesn't work.
The other answer and comments are correct, btw finally I made it like this and it's working
import { Directive, ElementRef, Inject, Input, NgZone, Optional, ViewContainerRef } from '#angular/core';
import
{
MAT_TOOLTIP_DEFAULT_OPTIONS,
MAT_TOOLTIP_SCROLL_STRATEGY,
MatTooltip,
MatTooltipDefaultOptions
} from '#angular/material/tooltip';
import { AriaDescriber, FocusMonitor } from '../../../../../node_modules/#angular/cdk/a11y';
import { Directionality } from '../../../../../node_modules/#angular/cdk/bidi';
import { Overlay, ScrollDispatcher } from '../../../../../node_modules/#angular/cdk/overlay';
import { Platform } from '../../../../../node_modules/#angular/cdk/platform';
#Directive({
selector: '[tooltip]',
exportAs: 'tooltip'
})
export class TooltipDirective extends MatTooltip
{
#Input()
get tooltip()
{
return this.message;
}
set tooltip(value: string)
{
this.message = value;
}
constructor(
_overlay: Overlay,
_elementRef: ElementRef,
_scrollDispatcher: ScrollDispatcher,
_viewContainerRef: ViewContainerRef,
_ngZone: NgZone,
_platform: Platform,
_ariaDescriber: AriaDescriber,
_focusMonitor: FocusMonitor,
#Inject(MAT_TOOLTIP_SCROLL_STRATEGY) _scrollStrategy: any,
#Optional() _dir: Directionality,
#Optional() #Inject(MAT_TOOLTIP_DEFAULT_OPTIONS)
_defaultOptions: MatTooltipDefaultOptions)
{
super(
_overlay,
_elementRef,
_scrollDispatcher,
_viewContainerRef,
_ngZone,
_platform,
_ariaDescriber,
_focusMonitor,
_scrollStrategy,
_dir,
_defaultOptions
);
}
}
This works for me. In my case I needed to have some checks before displaing the tooltip on 'mouseover' event.
import { Directive, ElementRef, HostListener, Input } from '#angular/core';
import { MatTooltip } from '#angular/material/tooltip';
#Directive({
selector: '[customTooltip]',
providers: [MatTooltip]
})
export class CustomTooltipDirective {
#Input() tooltipText: string;
constructor(private elementRef: ElementRef, private tooltip: MatTooltip) {}
#HostListener('mouseover') mouseover() {
this.tooltip.message = this.tooltipText;
this.tooltip.show();
}
#HostListener('mouseleave') mouseleave() {
this.tooltip.hide();
}
}
There's no way to do it in Angular. Keep an eye on this, so if maybe Angular guys will do it in case they start doing meaningful work.
Your other option is to create a dynamic component for this situtation which sucks for this kind of little thing. I'm not sure but it may break your AOT.

Angular, DomSanitizer, bypassSecurity script

I'm playing with bypassSecurityTrust* functions of Angular. Goal is to get a script tag to execute on the page. But it either keeps sanitizing with the message
WARNING: sanitizing HTML stripped some content
or I see in the console a
SafeHtmlImpl {changingThisBreaksApplicationSecurity: "<script>alert(1)</script>.
Goal is to get this working.
What I currently use and tried:
#Pipe({ name: 'safeHtml'})
export class SafeHtmlPipe implements PipeTransform {
constructor(private sanitized: DomSanitizer) {}
transform(value: string): string {
console.log(this.sanitized.sanitize(SecurityContext.NONE, value))
return this.sanitized.sanitize(SecurityContext.NONE, value);
}
}
#Component({
selector: 'app-demo',
templateUrl: './demo.component.html',
styleUrls: ['./demo.component.css']
})
export class DemoComponent implements OnInit {
name: string;
html: string;
constructor(private sanitizer: DomSanitizer) {
this.name = 'Angular2';
this.html = "<script> alert(8) </script>";
}
ngOnInit() {
}
}
and the template html:
<div [innerHtml]="html | safeHtml"></div>
I tried both sanitize with SecurityContext.NONE which should work looking at the code and bypassSecurityTrustHtml(value). The above code was inspired by this answer.
Any ideas on how to execute that JavaScript?
So yes, innerHtml can't insert script tags, but it doesn't stop it from one of the many other ways to inject JavaScript.
Working example:
import { Component, Pipe, PipeTransform, SecurityContext} from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser'
#Pipe({ name: 'safeHtml'})
export class SafeHtmlPipe implements PipeTransform {
constructor(private sanitized: DomSanitizer) {}
transform(value: string) {
console.log(this.sanitized.bypassSecurityTrustHtml(value));
return this.sanitized.bypassSecurityTrustHtml(value);
}
}
#Component({
selector: 'app-demo',
template: `
<div [innerHtml]="html | safeHtml">
</div>
`
})
export class DemoComponent {
html: string;
h_html: string;
constructor(private sanitizer: DomSanitizer) {
this.html = "<svg onload=\"alert(1)\"> blah </svg>"
this.h_html = sanitizer.sanitize(SecurityContext.HTML, "<svg onload=\"alert(2)\"> blah </svg>');
}
}
What doesn't work is
return this.sanitized.sanitize(SecurityContext.HTML, value);
or using
<div [innerHtml]="h_tmpl"></div>
Not sure why. Should behave the same afaiu.

Categories

Resources