Angular 2 - innerHTML styling
AngularInnerhtmlAngular Problem Overview
I am getting chunks of HTML codes from HTTP calls. I put the HTML blocks in a variable and insert it on my page with [innerHTML] but I can not style the inserted HTML block. Does anyone have any suggestion how I might achieve this?
@Component({
selector: 'calendar',
template: '<div [innerHTML]="calendar"></div>',
providers: [HomeService],
styles: [`h3 { color: red; }`]
})
The HTML that I want to style is the block contained in the variable "calendar".
Angular Solutions
Solution 1 - Angular
update 2 ::slotted
::slotted
is now supported by all new browsers and can be used with ViewEncapsulation.ShadowDom
https://developer.mozilla.org/en-US/docs/Web/CSS/::slotted
update 1 ::ng-deep
/deep/
was deprecated and replaced by ::ng-deep
.
::ng-deep
is also already marked deprecated, but there is no replacement available yet.
When ViewEncapsulation.Native
is properly supported by all browsers and supports styling accross shadow DOM boundaries, ::ng-deep
will probably be discontinued.
original
Angular adds all kinds of CSS classes to the HTML it adds to the DOM to emulate shadow DOM CSS encapsulation to prevent styles of bleeding in and out of components. Angular also rewrites the CSS you add to match these added classes. For HTML added using [innerHTML]
these classes are not added and the rewritten CSS doesn't match.
As a workaround try
- for CSS added to the component
/* :host /deep/ mySelector { */
:host ::ng-deep mySelector {
background-color: blue;
}
- for CSS added to
index.html
/* body /deep/ mySelector { */
body ::ng-deep mySelector {
background-color: green;
}
>>>
(and the equivalent/deep/
but /deep/
works better with SASS) and ::shadow
were added in 2.0.0-beta.10. They are similar to the shadow DOM CSS combinators (which are deprecated) and only work with encapsulation: ViewEncapsulation.Emulated
which is the default in Angular2. They probably also work with ViewEncapsulation.None
but are then only ignored because they are not necessary.
These combinators are only an intermediate solution until more advanced features for cross-component styling is supported.
Another approach is to use
@Component({
...
encapsulation: ViewEncapsulation.None,
})
for all components that block your CSS (depends on where you add the CSS and where the HTML is that you want to style - might be all components in your application)
Update
Solution 2 - Angular
The simple solution you need to follow is
import { DomSanitizer } from '@angular/platform-browser';
constructor(private sanitizer: DomSanitizer){}
transformYourHtml(htmlTextWithStyle) {
return this.sanitizer.bypassSecurityTrustHtml(htmlTextWithStyle);
}
Solution 3 - Angular
We pull in content frequently from our CMS as [innerHTML]="content.title"
. We place the necessary classes in the application's root styles.scss
file rather than in the component's scss file. Our CMS purposely strips out in-line styles so we must have prepared classes that the author can use in their content. Remember using {{content.title}}
in the template will not render html from the content.
Solution 4 - Angular
If you're trying to style dynamically added HTML elements inside an Angular component, this might be helpful:
// inside component class...
constructor(private hostRef: ElementRef) { }
getContentAttr(): string {
const attrs = this.hostRef.nativeElement.attributes
for (let i = 0, l = attrs.length; i < l; i++) {
if (attrs[i].name.startsWith('_nghost-c')) {
return `_ngcontent-c${attrs[i].name.substring(9)}`
}
}
}
ngAfterViewInit() {
// dynamically add HTML element
dynamicallyAddedHtmlElement.setAttribute(this.getContentAttr(), '')
}
My guess is that the convention for this attribute is not guaranteed to be stable between versions of Angular, so that one might run into problems with this solution when upgrading to a new version of Angular (although, updating this solution would likely be trivial in that case).
Solution 5 - Angular
Use the below method to allow CSS styles in innerhtml
.
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
.
.
.
.
html: SafeHtml;
constructor(protected _sanitizer: DomSanitizer) {
this.html = this._sanitizer.bypassSecurityTrustHtml(`
<html>
<head></head>
<body>
<div style="display:flex; color: blue;">
<div>
<h1>Hello World..!!!!!</h1>
</div>
</div>
</body>
</html>`);
}
Example code stackblitz
Or use the below method to write directly in HTML. https://gist.github.com/klihelp/4dcac910124409fa7bd20f230818c8d1
Solution 6 - Angular
The recommended version by Günter Zöchbauer works fine, but I have an addition to make. In my case I had an unstyled html-element and I did not know how to style it. Therefore I designed a pipe to add styling to it.
import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@Pipe({
name: 'StyleClass'
})
export class StyleClassPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) { }
transform(html: any, styleSelector: any, styleValue: any): SafeHtml {
const style = ` style = "${styleSelector}: ${styleValue};"`;
const indexPosition = html.indexOf('>');
const newHtml = [html.slice(0, indexPosition), style, html.slice(indexPosition)].join('');
return this.sanitizer.bypassSecurityTrustHtml(newHtml);
}
}
Then you can add style to any html-element like this:
<span [innerhtml]="Variable | StyleClass: 'margin': '0'"> </span>
With:
Variable = '<p> Test </p>'
Solution 7 - Angular
For anyone that wants just applies a certain style to innerHTML :
Follow Create a safe HTML pipe
And you can concat your HTML string with CSS style like this:
return this.sanitizer.bypassSecurityTrustHtml(value+='<style type="text/css">.image img { width: 100% }</style>');
This value
is from transform(value, ...args)
Solution 8 - Angular
I went the this.sanitizer.bypassSecurityTrustHtml()
route initially, and set encapsulation to ViewEncapsulation.NONE
, but had 2 problems:
ViewEncapsulation.NONE
was causing other styling issues in my component- My "safe" html didn't appear to work with css variables, ie var(--blue)
This worked for me (without having to change anything else): InsertAdjacentHTML
Template:
<div id=template></div>
Code:
ngOnInit() {
const el = document.getElementById('template');
el.insertAdjacentHTML('afterbegin', `<span style="color: var(--blue)">hello</span>`);
}
Disclaimer: In my case, I was parsing html from config files. You wouldn't want to go this route with user inputted html.
Solution 9 - Angular
The easiest and most straight forward is to use the global styles file located in angular project src folder.
Assuming the component selector is: app-my-component
Add a class to the element hosting the innerHtml content in app-my-component template:
<div class="innerhtml-class" [innerHTML]="variable.innerHtml"></div>
Add to the global styles file:
app-my-component {
.innerhtml-class {
declaration goes here
}
}
Solution 10 - Angular
Using inline CSS variables is an alternative solution if you have limited styles that are dynamic.
I.e.
// file.ts
someVarWithHtml = 'Hello <span class="dynamic">World</span>';
// file.ng.html
<div [style]="'--my-var: ' + value"
[innerHTML]="someVarWithHtml"></div>
// style.css
.dynamic {
background: var(--my-var);
}
Solution 11 - Angular
If you are using sass as style preprocessor, you can switch back to native Sass compiler for dev dependency by:
npm install node-sass --save-dev
So that you can keep using /deep/ for development.