Render CSS, either from #Input or object property - html

We have an RTF control in our main application (standalone) which also generates CSS classes, and HTML that uses these classes. This is being loaded via an API.
It's outputted like this:
.cs95E872D0{} .csCF6BBF71{font-weight:normal;font-style:normal;}
and the HTML is outputted like so:
<p class="cs95E872D0"><span class="csCF6BBF71">this is a test</span></p>
It's terribly formatted but I guess that's what you get with auto-generated stuff! We are unable to change the generation of this CSS/HTML so unfortunately this is what I have to work with.
I need to display this HTML on a page (easy enough with the [innerHTML] attribute) however when it comes to doing it with the CSS I can't seem to figure it out.
I've tried creating a new component:
import { Component, Input } from "#angular/core";
#Component({
selector: 'htmlrender',
template: `<span [innerHtml]="html"></span>`,
styles: ['{{styles}}']
})
export class TestComponent {
#Input() html: string;
#Input() styles: string;
}
However it gets rendered as this:
<htmlrender _ngcontent-c9="" _nghost-c11="" ng-reflect-styles=".cs95E872D0{} .csCF6BBF71{font">
<span _ngcontent-c11=""></span>
</htmlrender>
Which doesn't work. I've also just tried to render the CSS inside <style> tags but that doesn't seem to work.

If the output is always the same you can use a string.slice() and string.replace().
I did it like this:
this.str = '.cs95E872D0{} .csCF6BBF71{font-weight:normal;font-style:normal;}';
let slice = this.str.split(".", 3);
console.log('.' + slice[1]);
console.log('.' + slice[2]);
They output of slice[1] and slice[2] look like:
.cs95E872D0{}
.csCF6BBF71{font-weight:normal;font-style:normal;}

Related

How do you generate dynamic <style> tag content in Angular template at runtime?

I have an Angular component that generates mat-checkbox dynamically at runtime and I need to change the individual background of each checkbox differently with different color and I don't (won't) have the information before hand, only available at runtime.
I have the following ng-template for the checkboxes:
<ng-template #renderCheckbox let-id="id" let-attr="attr">
<mat-checkbox
[checked]="attr.show"
[color]="'custom-' + id"
(change)="onChange($event.checked, attr)">
{{attr.name}}
</mat-checkbox>
</ng-template>
where, attr in the template has the following interface type, these infomation are pulled from Highcharts' series and I didn't want to hardcode the color.
interface LinkedSeriesAttributes {
id: string;
name: string;
index: number;
color: string;
checked: boolean;
}
Since there is no way to create css classes before hand and there is no way to directly apply color to the mat-checkbox, I could only generate the <style>...</style> right at the beginning of my template.
In my component, I have code that will generate the style which would give me something like this:
.mat-checkbox.mat-custom-hello.mat-checkbox-checked .mat-checkbox-background::before {
color: #6E8BC3 !important;
}
.mat-checkbox.mat-custom-world.mat-checkbox-checked .mat-checkbox-background::before {
color: #9ED6F2 !important;
}
...
However, I tried various ways to dump it inside <style> without success. I tried:
<style>{{ dynamicCSSStyles }}</style>
Which, my IDE shows that's an error with the curly braces, although it compiled fine and ran without errors, I got nothing, can't even see the <style> tag.
I also tried to include <style> inside my dynamicCSSStyles variable, and angular just dumped the whole thing out as text...
What's the correct way to generate a <style> in Angular.
I've found a REALLY dirty way of "making this work" but it causes Angular to keep adding the <style> back into the DOM.
First, set encapsulation to ViewEncapsulation.None.
Second, create a function to generate the <style> tag the old fashion way with an id:
updateDynsmicStyleNode() {
const id = 'dynamic-css-styles';
const nativeElm = this.elmRef.nativeElement;
const existing = nativeElm.querySelector(`style#${id}`);
if (!existing) {
const styleTag = document.createElement('style');
styleTag.setAttribute('id', id);
styleTag.innerHTML = this.dynamicCSSStyles;
nativeElm.prepend(styleTag);
} else {
existing.innerHTML = this.dynamicCSSStyles;
}
}
Third, call our function in ngAfterViewChecked:
ngAfterViewChecked() {
this.updateDynsmicStyleNode();
}
I mean while this worked, it is really bad, since moving the mouse around the screen would cause Angular to just continuously reinsert the <style> tag.
Does anyone know some other way more legit to archive this? LOL
You can use ngClass or [class] attribute. Since you can have the styles ready from the component.ts file.
You can do something like this:
Way 1: If you already know what the dynamic ids might be, (like if it always will be 'hello' and 'world')
let dynamicClasses = {};
// Once you get some classes from your logic, you can add them to the object above
dynamicClasses['hello'] = 'custom-hello';
dynamicClasses['world'] = 'custom-world';
// Then in HTML
<mat-checkbox [ngClass]="dynamicClasses"></mat-checkbox>
Way 2: If you dont know what the classes also might be, like if its not always be hello or world, then create a method and call it where required, you might need to do something similar to #codenamezero said.

Directive to change the string inside of a paragraph to uppercase is not working

I am trying to write a directive that turns the content of a paragraph to uppercase when you hover your mouse over it. I am not getting any errors whatsoever - it just does not work. I have written a similar code before that highlights the text to a certain color, which worked. Why wouldn't it also work when changing the text to uppercase?
filter.component.html
<p appToUpperCase>String to uppercase</p>
to-upper-case.directive.ts
import { Directive, HostListener, ElementRef } from '#angular/core';
#Directive({
selector: '[appToUpperCase]'
})
export class ToUpperCaseDirective {
constructor(public el: ElementRef) {
}
#HostListener('mouseenter2') onMouseEnter() {
this.el.nativeElement.value = this.el.nativeElement.value.toUpperCase();
}
}
EDIT: As #Ilya Rachinsky suggested, I have changed the event name from mouseenter2 to mouseenter and it still does not work.
Your directive structure looks fine. I guess you forgot to include it into the list of declarations on the module, so the directive will be available for the templates. Additionally, there is no 'value' property on 'p' element, you need to use innerHTML as previously suggested.
Checkout my example: https://stackblitz.com/edit/angular-ivy-uppercase-directive?file=src%2Fapp%2Fto-upper-case.directive.ts
You have to use correct event name - mouseenter instead mouseenter2

Display a link in dynamically obtained html text

I am dynamically obtaining a list of strings.I display it in angular using ngFor. But when displayed, certain strings include few hyperlinks ,but they are displayed as normal strings. I want the hyperlink distinguished like underlined.
Eg: Refer https://support.google.com/accounts/answer/abc?hl=en# to 'Create a Google Account' using email
If I get it right, its really simple and you can do that like this :
{{Text Variable}}
you can make a pipe, but this pipe must be used in a [innerHtml]
#Pipe({name: 'linkPipe'})
export class LinkPipe implements PipeTransform {
constructor(private _domSanitizer: DomSanitizer){}
transform(value: string): any {
if (value.indexOf("http")>=0)
{
//search the "link"
const link=value.match(/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9#:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9#:%_\+~#?&//=]*)?(\[.*\])?/)
if (link) //if has a link
{
const valueSplit=link[0].split('[') //check if is in the way:
//http://direccion[text to show]
value=value.replace(link[0],
"<a href='"+valueSplit[0]+"'>"+
(valueSplit[1]?valueSplit[1].slice(0,-1):valueSplit[0])+
"</a>")
}
}
return this._domSanitizer.bypassSecurityTrustHtml(value)
}
}
e.g. of use
<p [innerHTML]="'see the http://www.google.com[page of Google] for more information'|linkPipe "></p>
<p [innerHTML]="'http://www.google.com'|linkPipe"></p>
see stackblitz

(click) event not work in innerHtml string Angular 4

My function isn't called when I click the <a... tag.
I have the following 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 isn't called and does not show the alert.
The <div... is created dynamically
If anyone face same issue and above all answer not working then try my trick :
In HTML :
<button onclick="Window.myComponent.test()"> test </button>
In component :
class
constructor(){
Window["myComponent"] = this;
}
test(){
console.log("testing");
}
Your main issue here, on-top of the things pointed out by #Matt Clyde and #Marciej21592, is that you're trying to dynamically add HTML code that needs to be compiled before it can be used (you're trying to bind to a method and variable).
Some ways of doing this can be seen here.
From the code you have supplied, however, there are much easier ways to accomplish what you are after. For starters, I would have that code in the HTML to begin with and hide/show it as needed with ngIf.
i use this method and its work
public htmlstr: string;
public idUser:number;
this.idUser = 1;
this.htmlstr = `<a id='innerHtmlClick'>${idUser}</a>`
this.htmlstr.querySelector(`innerHtmlClick`).addEventListener('click', () => {
this.delete(idUser);
});
public delete(idUser){
alert("id " + idUser);
}
EventListener listen the event bye using id of innerHtml
I assume that it is not a bug but rather Angular's security measure against XSS attacks - for more information I would suggest taking a look here https://angular.io/guide/security#sanitization-example
I somewhat also fail to understand why you insist on passing the event via string literal instead of just simply using:
<div>
<a (click)="delete(idUser)">${this.idUser}</a>
</div>
Your component has inner Html.
Angular will not allow events inside inner Html portions for security reasons. You can use Child components. to make events from inside of inner Html portions. Create a child component and put your html inside the child component and pass the data by using any angular events between parent and child using Input, Output features in Angular
I don't often use [innerHTML], but it looks like the template string you're using <a (click)="delete(idUser)">${idUser}</a> is referencing ${idUser} when you might have meant ${this.idUser}?
Below code snippet worked for me:-
In component :
ngAfterViewChecked () {
if (this.elementRef.nativeElement.querySelector('ID or Class of the Html element')) {
this.elementRef.nativeElement.querySelector('ID or Class of the Html element').addEventListener('click', this.editToken.bind(this));
}
}
inside constructor parameter:-
constructor( private readonly elementRef: ElementRef) {}
import { ElementRef } from '#angular/core';---> at the top of the file
implement 'AfterViewChecked'

How many times does Angular 2 render a single page before showing it?

I'm using Angular 2 for my project. I have a simple div in my template which calls a function in my .ts file that outputs a simple text like this:
<div>{{ test() }}</div>
private test(): void {
console.log("Test text");
}
When I load a page I get the same output many times like this:
Test text
Test text
Test text
Test text
Test text
Does that mean that Angular 2 renders the template many times before it actually shows it and consequently calls function every time?
Angular renders the AppComponent and it's child components exactly once, except when you add remove parts of the DOM, then these added parts will be rendered again.
What you experience is Angulars change detection which runs quite frequently. See also Why event triggers ChangeDetection even if OnPush strategy is ON?.
It is usually a bad idea to use functions in value bindings because such functions will be called every time Angular runs change detection.
Prefer to assign the value to a property and bind to this property instead.
<div>{{ testVal }}</div>
ngOnInit() {
this.testVal = this.test();
}
private test(): string {
console.log("Test text");
return 'some string';
}
Yes It renders multiple time since ChangeDetectionStrategy is always "Default" means it check always(multiple times) for UI update
ChangeDetectionStrategy.OnPush
Use OnPush: OnPush means that the change detector's mode will be set to CheckOnce during hydration.
If you use ChangeDetectionStrategy.OnPush then it will print only once
changeDetection: ChangeDetectionStrategy.OnPush
https://angular.io/api/core/ChangeDetectionStrategy
https://plnkr.co/edit/lNXNsS?p=preview
Code Snippet
#Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'my-app',
template: `
<div>
Check Console
<h2>{{print()}}</h2>
</div>
`,
})
export class App {
name:string;
constructor() {
this.name = `Angular! v${VERSION.full}`
console.log("Called Once")
}
print(): void {
console.log("I am printing only one time"):
}
}