how to insert html with angular bindings dynamically - html

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.

Related

Render string input of ng-content directly as HTML

Is it possible for a component to receive content passed to it between opening and closing tags and assign the input directly to innerHTML inside the receiving template?
I have a string input like this:
public content: string =
'<div class="row">' +
' <div class="col">' +
' <h1>Friendly greetings!</h1>' +
' <p>' +
' This is my homepage..' +
' </p>' +
' </div>' +
'</div>';
And put it in my component like this:
<app-content>{{ content }}</app-content>
Inside app-content I can use ng-content in the template to project the input content. Since the input is a string I only get to see a string, the HTML elements it describes are not rendered.
I tried to inject the ElementRef and access the text node via this.elementRef.nativeElement.textContent, but the content must have been rendered first to access it that way.
Of course, I could hide the content ng-content produces like this:
<div style="display: none">
<ng-content></ng-content>
</div>
And then get the textContent in a variable (e.g. content) and set it like this:
<div [outerHTML]="content"></div>
But that seems hacky to me.
StackBlitz project: get-input-of-ng-content-directly
Am I missing something?
I want to do something like this in the template: <div [outerHTML]="incoming ng-content stuff"></div>
Or at least inject the content in the components constructor and set it to a variable, but without rendering the content first just to hide it.
I'd say you have two options:
1.) #Input() field that receives your content above and then renders the content as innerHTML like you mentioned. So something like
<app-content [content]="content"></app-content>
And then you can render you content Input like you have above with the <div [innerHTML]=...
2.) Or you can use <ng-content> inside of your <app-content> HTML file and then just do something like this:
<app-content>
<div [innerHTML]="content"></div>
</app-content>
But this second option probably assumes <app-content> is not the root element from which your Angular app is boostrapped.

Angular 6 - bind HTML created after compilation to click event

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.
*/
}
});

(click) event don't work in string angular 4

My function don't call when i clicked in tag a
i have the next code in my component
public htmlstr: string;
public idUser:number;
this.idUser = 1;
this.htmlstr = `<a (click)="delete(idUser)">${idUser}</a>`;
public delete(idUser){
alert("id " + idUser);
}
my html
<div [innerHTML]="htmlstr"></div>
but the function delete don't call and not show the alert
Angular string interpolation braces won't work in a variable like this.
You could use template literals (note the backticks, not single quotes):
this.iduser = `<a style="font-weight: bold;"> id: ${idperson} </a>`
Or String.format:
this.iduser = '<a style="font-weight: bold;"> id: {} </a>'.format(idperson)
You are using Angular 4 so why not simply use interpolation?
Your code would thus look something like this:
idLabel = 'id: ';
id: number;
And then your template would look like this:
<span>
<a style="font-weight:bold;" href="#">{{idLabel + id}}</a>
</span>
Ideally, if you are using a router for this link you could attach the routerLink diractive to the anchor tag like so:
<a style="font-weight:bold;" [routerLink]="'/user/' + id"></a>
...or whatever your route configuration looks like.
Try this:
this.idperson = 1;
this.iduser = `<a style='font-weight: bold;'> id: ${idperson} </a>`;
Note that i'm using backquote, no simple quote. The result will be:
id: 1
if you're using AOT it might be as simple as making the function public?
also check your browser's console for errors

ng-bind-html not working with my $scope.variable

I am trying to add something like dynamic HTML using ng-bind-html but its not working with $scope variable
Here is my Angular code
1)My controller
$scope.name = "Parshuram"
$scope.thisCanBeusedInsideNgBindHtml = $sce.trustAsHtml("<div>{{name}}</div>");
Also that my string is dynamic
"<div><table class=" + "\"table table - bordered table - responsive table - hover add - lineheight table_scroll\"" + "><thead><tr><td>Name</td><td>Age</td></tr></thead><tbody><tr ng-repeat=" + "\"tr in dyna\"" + "><td>{{tr.name}}</td><td>{{tr.age}}</td></tr></tbody></table></div>"
So i cant replace every variable with $scope
2)- My HTML
<div ng-app="mymodule" ng-controller="myModuleController">
<div ng-bind-html="thisCanBeusedInsideNgBindHtml"></div>
</div>
I got this output
{{name}}
My expected output is
Parshuram
Please can anyone help i am stuck at this point,does that $sce does not bind scope variable?? ..
I've created a working plnkr here: https://plnkr.co/edit/uOdbHjv1B7fr0Ra1kXI3?p=preview
the problem is that ng-bind-html is not bound to the scope.
you should manually compile the content.
a valid and reusable solution should be creating a directive, whitout using any external modules.
function compileTemplate($compile, $parse){
return {
link: function(scope, element, attr){
var parsed = $parse(attr.ngBindHtml);
function getStringValue() { return (parsed(scope) || '').toString(); }
scope.$watch(getStringValue, function() {
$compile(element, null, -9999)(scope);
});
}
}
}
<div ng-app="mymodule" ng-controller="myModuleController">
<div ng-bind-html="thisCanBeusedInsideNgBindHtml" compile-template></div>
</div>
ng-bind-html does what it says on the tin: it binds html. It doesn't bind angular template code into your dom.
You need to do this instead:
$scope.thisCanBeusedInsideNgBindHtml =
$sce.trustAsHtml("<div>"+$sanitize(name)+"</div>");
To do this you'll want to include the module ngSanitize from the javascript angular-sanitize.js. See https://docs.angularjs.org/api/ngSanitize
If you want to insert some html that includes angular directives then you should write your own custom directive to do it.
In your html just use
{{name}}
The {{var}} notation is to be used in the HTML code to evaluate that variable.
You can do :
$scope.thisCanBeusedInsideNgBindHtml = $sce.trustAsHtml('<div ng-bind="name"></div>');
Sorry I make another answer.
If you have
$scope.thisCanBeusedInsideNgBindHtml = $sce.trustAsHtml("<div>{{name}}</div>");
Then you can do
var str = "<div>{{name}}</div>";
var output = str.replace('{{name}}', $scope.name);
It seems to be the only option.

How to make an a-tag editable in custom CKEditor widget?

I have created a custom faq widget for the CKEditor 4.5. The plugin.js file looks like this. Basically the widget adds a div to render a FAQ entry using Twitter Bootstrap's collapse feature.
CKEDITOR.plugins.add('faq', {
requires: 'widget',
icons: 'faq',
init: function (editor) {
var id = Math.random().toString(36).substr(2);
editor.widgets.add('faq', {
button: 'Create FAQ entries',
template: '<div class="faq"><a class="faq-question" data-toggle="collapse" href="#faq-' + id +
'" aria-expanded="true" aria-controls="faq-' + id + '"><p>Question ...?</p></a>' +
'<div class="collapse in faq-answer" id="faq-' + id +'">Answer ...</div></div>',
editables: {
question: {
selector: '.faq-question'
},
answer: {
selector: '.faq-answer'
}
},
allowedContent: 'div(!faq); a(!faq-question); div(!faq-answer); a',
requiredContent: 'div(!faq)',
upcast: function (element) {
return element.name == 'div' && element.hasClass('faq');
}
});
}
});
Editing the answer field works fine, but within the CKEditor I can not edit the question.
The generated source code in the editor looks like this:
<div class="faq">
<a aria-controls="faq-fk7stx54sfpnl8fr" aria-expanded="true" class="faq-question" data-toggle="collapse" href="#faq-fk7stx54sfpnl8fr">Question ...?</a>
<div class="collapse in faq-answer" id="faq-fk7stx54sfpnl8fr"><p>Answer ...</p></div>
</div>
Ok, sometines writing about it helps to solve it ;-)
I had to add the <a> tag to the list of editable elements like this:
CKEDITOR.dtd.$editable['a'] = 1;
at top of the plugins.js file