Angular 6 - bind HTML created after compilation to click event - html

Im adding HTML elements at run-time when the user clicks a button.
I do this by setting the inner html of a div to a built up string then using DOMSanitizer.
Visually this look fine but the click events in the new HTML are not bound so nothing works, I guess because the HTML is generated after compilation.
Here is the code called when the user clicks to add a new component (it get populated with the correct data), can anyone suggest how I should hook it up to the click event in the delete image?
html on the page:
<div class="col-sm-9" >
<div [innerHtml]="contentHtml"></div>
</div>
code:
async AddText(contentText: string) {
this.htmlToAdd = this.htmlToAdd + ( '<br> <div class="card text-left">' +
'<div class="card-header text-secondary">Attraction Text' +
'<img align="right" class="image-hover pull-right table-header-padding" src="assets/images/LockLineIcon.png" />' +
'<img #delete class="image-hover float-right text-danger icon-pad draft-icon-indent" src="assets/images/DeleteIcon.png" (click)="this.delete(0)"/>' +
'</div>' +
'<div class="card-body" >' +
'<textarea id="text" name="text" type="text" class="form-control" required maxlength="2048" >' + contentText + '</textarea>' +
'</div>' +
'<div class="card-footer">' +
'<img align="right" class="pull-right table-header-padding" src="assets/images/DragUpDownIcon.png" />' +
'</div>' +
'</div>');
this.contentHtml = this.sanitizer.bypassSecurityTrustHtml(this.htmlToAdd);
}

Your DOM may be sanitized, but it's not part of Angular's DOM. If you want Angular to see the DOM, you have to let Angular make the DOM - and that means dynamic components. Something like this would work:
#Component({
selector: 'my-component',
template: `<h2>Stuff bellow will get dynamically created and injected<h2>
<div #vc></div>`
})
export class MyComponent {
#ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef;
private cmpRef: ComponentRef<any>;
constructor(private compiler: Compiler,
private injector: Injector,
private moduleRef: NgModuleRef<any>,
private backendService: backendService,
) {}
ngAfterViewInit() {
// Here, get your HTML from wherever.
this.someService.getThatAsyncHTMLOfYours.subscribe(rawHTML => this.createComponentFromRaw(rawHTML));
}
// Here we create the component.
private createComponentFromRaw(template: string) {
// Let's say your template looks like `<h2><some-component [data]="data"></some-component>`
// As you see, it has an (existing) angular component `some-component` and it injects it [data]
// Now we create a new component. It has that template, and we can even give it data.
const tmpCmp = Component({ template, styles })(class {
// the class is anonymous. But it's a quite regular angular class. You could add #Inputs,
// #Outputs, inject stuff etc.
data: { some: 'data'};
ngOnInit() {
/**
* HERE'S YOUR STUFF
* do stuff here in the dynamic component, like binding to locally available variables and services.
*/
}
});

Related

how to insert html with angular bindings dynamically

I'm developing Angular 8 app and I want to insert HTML with angular bindings into my page.
So, my html code is:
<button (click)="insertHtml()">Insert</button>
<div id="text"></div>
My angular code is:
insertHtml() {
document.getElementById("text").innerHTML = '<span (click)="handleClick(' + this.item.id + ')" [hidden]="' + this.item.hidden + '">'
}
Html looks fine, but it doesn't show right css class and click handler.
Can I insert html with angular bindings dynamically? And if yes, what am I doing wrong?
you can use another approch (something like this):
<button (click)="showHtml()">Insert</button>
<ng-container *ngIf="clicked">
<div id="text"><span (click)="......" [hidden]="' + ...... + '">'</div>
</ng-container>
and in you .ts you do:
#Input()
clicked:boolean =false;
showHtml(){
this.clicked=true;
}
it must work.

how to bind click event from ts to html dynamically to anchor tag using inner html in angular 7?

I want to bind click event from ts to html dynamically to anchor tag.
Ts file:
this.RecommendedTests.Description = '<a (click)="showModel()">'
+this.RecommendedTests[i].Description + '</a>';
html:
<div [innerHTML]="RecommendedTests.Description"></div>
By the above code snippet, I am able to get the anchor tag, but the click event is not getting fired. I think it is because of XSS sanitization issue, but there is no proper solution related to XSS sanitization and binding click event could be found.
Is there any security concern occurred if I achieve this using DataSanitization for binding such things?
can someone help me out of this?
Try like this:
import { DomSanitizer } from '#angular/platform-browser';
export class AppComponent {
constructor(private sanitizer: DomSanitizer) {
Window["AppComponent"] = this;
}
this.RecommendedTests.Description = this.sanitizer.bypassSecurityTrustHtml('<a onClick="Window.AppComponent.showModel()">'
+ this.RecommendedTests[i].Description + '</a>')
showModel() {
alert("ok")
}
}
Working Demo
this.RecommendedTests.Description = <a (click)="showModel()">
+this.RecommendedTests[i].Description + </a> ;
Try this in your code,i think it is work

Changing size of a textbox in html string in React

I'm using a popup library sweetalert2-react-content to generate a popup box which contains a textbox and a textarea. In the popupbox I am generating an html string to create the textbox and text area. It's generating those elements just fine, however I am unable to change the size of the textarea. Here's a code snippet:
import React, { Component } from 'react';
import Swal from 'sweetalert2';
import withReactContent from 'sweetalert2-react-content';
submitButton() {
const MySwal = withReactContent(Swal)
MySwal.fire({
confirmButtonText: 'Submit Post',
showCancelButton: true,
title: "Submit a new Post",
html:
'<br /><br />' +
'<h4>Enter the title</h4>' +
'<input type="text" id="theTitle">' +
'<h4>Enter the body</h4>' +
'<textarea rows="10" cols="50"></textarea>',
input: 'file',
focusConfirm: false,
}).then(result => this.getPostValues(result.value))
}
Any ideas how I might be able to get the textbox to display a little larger? Thanks!!
The <textarea> is likely controlled via the library's css so even if you set rows and cols, the css dictates styling. If so, you'll have to either edit the library's css, override it with your own css, or give it an inline style.
MySwal.fire({
// Other settings...
html:
'...other html' +
'<textarea style="height: 200px; width: 400px"></textarea>'
});

Adding HTML content to angular material $mdDialog

I have written the following piece of code to display some contents in angular material dialog box. it works fine when i add plain text to textContent . when i add HTML its displays HTML as text. how do i bind HTML to textContent
This Works
Sample Link
$scope.Modal = function () {
$mdDialog.show(
$mdDialog.alert()
.parent(angular.element(document.querySelector('body')))
.clickOutsideToClose(true)
.textContent('sample text')
.ok('Ok')
);
}
This Doesn't Works
Sample Link
$scope.Modal = function () {
$mdDialog.show(
$mdDialog.alert()
.parent(angular.element(document.querySelector('body')))
.clickOutsideToClose(true)
.textContent('<div class="test"><p>Sample text</p></div>')
.ok('Ok')
);
}
Thanks in advance
You need to append to the template,
$mdDialog.show({
parent: angular.element(document.body),
clickOutsideToClose: true,
template: '<md-dialog md-theme="mytheme">' +
' <md-dialog-content>' +
'<div class="test"><p>Sample text</p></div>' +
' <md-button ng-click="closeDialog();">Close</md-button>' +
' </md-dialog-content>' +
'</md-dialog>',
locals: {
},
controller: DialogController
});
DEMO
You can add html in template and just add variable in displayOption. This will work.
Template Code
<script type="text/ng-template" id="confirm-dialog-answer.html">
<md-dialog aria-label="confirm-dialog">
<form>
<md-dialog-content>
<div>
<h2 class="md-title">{{displayOption.title}}</h2>
<p>{{displayOption.content}} <img src="{{displayOption.fruitimg}}"/></p>
<p>{{displayOption.comment}}</p>
</div>
</md-dialog-content>
<div class="md-actions" layout="row">
<a class="md-primary-color dialog-action-btn" ng-click="cancel()">
{{displayOption.cancel}}
</a>
<a class="md-primary-color dialog-action-btn" ng-click="ok()">
{{displayOption.ok}}
</a>
</div>
</form>
</md-dialog>
</script>
Controller Code
$mdDialog.show({
controller: 'DialogController',
templateUrl: 'confirm-dialog-answer.html',
locals: {
displayOption: {
title: "OOPS !!",
content: "You have given correct answer. You earned "+$scope.lastattemptEarnCount,
comment : "Note:- "+$scope.comment,
fruitimg : "img/fruit/"+$scope.fruitname+".png",
ok: "Ok"
}
}
}).then(function () {
alert('Ok clicked');
});
Use template instead of textContent, textContent is used for show plan text in a model. It does not render HTML code
$mdDialog.show({
controller: function ($scope) {
$scope.msg = msg ? msg : 'Loading...';
},
template: 'div class="test"><p>{{msg}}</p></div>',
parent: angular.element(document.body),
clickOutsideToClose: false,
fullscreen: false
});
You can use htmlContent instead of textContent to render HTML. Heres an excerpt from the documentation available at https://material.angularjs.org/latest/#mddialog-alert
$mdDialogPreset#htmlContent(string) - Sets the alert message as HTML.
Requires ngSanitize module to be loaded. HTML is not run through
Angular's compiler.
It seems a bit counter intuitive to use a template when you only need to inject one or two things in. To avoid using a template, you need to include 'ngSanitize' for it to work.
angular.module('myApp',['ngMaterial', 'ngSanitize'])
.controller('btnTest',function($mdDialog,$scope){
var someHTML = "<font>This is a test</font>";
$scope.showConfirm = function(ev) {
// Appending dialog to document.body to cover sidenav in docs app
var confirm = $mdDialog.confirm()
.title('Please confirm the following')
.htmlContent(someHTML)
.ariaLabel('Lucky day')
.targetEvent(ev)
.ok('Please do it!')
.cancel('Sounds like a scam');
//Switch between .htmlContent and .textContent. You will see htmlContent doesn't display dialogbox, textContent does.
$mdDialog.show(confirm).then(function() {
$scope.status = 'Saving Data';
},
function() {
$scope.status = 'You decided to keep your debt.';
});
};
})
Notice the injected HTML:
var someHTML = "<font>This is a test</font>";
I found this example here.
The latest version of Angular Material Design API has predefined function for add HTML content to the alert dialog:
an $mdDialogPreset with the chainable configuration methods:
$mdDialogPreset#title(string) - Sets the alert title.
$mdDialogPreset#textContent(string) - Sets the alert message.
$mdDialogPreset#htmlContent(string) - Sets the alert message as HTML. Requires ngSanitize module to be loaded. HTML is not run through Angular's compiler.
$mdDialogPreset#ok(string) - Sets the alert "Okay" button text.
$mdDialogPreset#theme(string) - Sets the theme of the alert dialog.
$mdDialogPreset#targetEvent(DOMClickEvent=) - A click's event object. When passed in as an option, the location of the click will be used as the starting point for the opening animation of the the dialog.
The link to the documentation: Angular MD API

Build templated directive in AngularJS

I have blocks of HTML like this that repeat tons of time in my code:
<div>
<label for="producer">Producer:</label>
<select id="producer" ng-model="producer" ng-options="producer.name for producer in producers">
<option value="">-- Choose Producer --</option>
</select>
</div>
So I want to make a directive (??) where I could instead do this:
<gsBoundSelect gsLabel="Producer:" gsDefaultOption="-- Choose Producer --"
ng-model="producer"
ng-options="producer.name for producer in producers" />
The for/id fields could just be a random generated string.
I've been reading up on directives but I can't quite figure out exactly how to do this so that I can pass in parameters like this. The examples I've seen all want a bound scope variable passed in vs an attribute.
It seems like I need both a link function and a template, but I'm confused how to do that. Thanks.
Found enough data through various books to get something working. Not sure if it's the best way, but it definitely works:
module.directive('gsBoundSelect', function ($compile) {
function generateUUID() {
...
}
return {
restrict: 'E',
link: function (scope, element, attrs) {
var lblId = generateUUID();
var content = [
'<div>',
' <label for="' + lblId + '">' + attrs.gsLabel + '</label>',
' <select id="' + lblId + '" ng-model="' + attrs.ngModel + '" ng-options="' + attrs.ngOptions + '">',
' <option value="">-- ' + attrs.gsDefaultOption + ' --</option>',
' </select>',
'</div>'].join('');
// We need to transform the content into a jqLite object
var jqLiteElem = angular.element(content);
$compile(jqLiteElem)(scope);
element.replaceWith(jqLiteElem);
}
};
});
As a side-note...I discovered the element directives added must be closed with a full tag, not the shorter syntax like I showed in my example. So the above works with this HTML:
<gsBoundSelect gsLabel="Producer:" gsDefaultOption="-- Choose Producer --"
ng-model="producer"
ng-options="producer.name for producer in producers">
</gsBoundSelect>