20

I want to create a generic html fragment component. The idea being we may store html fragments in a db somewhere, and styles that should be applied to them.

I can easily set the innerHtml to insert the html structure into a base element in my view template, but how do i dynamically insert a <style> tag in my view template?

Here's what I have:

 @Component({
    moduleId: module.id,
    selector: 'htmlFragment',
    styleUrls: ['html-fragment.css'],
    templateUrl:'html-fragment.html'
})

export class HtmlFragmentComponent {

    @Input() htmlContent: string;
    @Input() styleContent: string;
}

Here is the view template:

<style [innerHTML]="styleContent"></style>
<div [innerHTML]="htmlContent"></div>

Then I'm trying to use it like this:

 <htmlFragment [htmlContent]='dHtml' [styleContent]="sHtml"></htmlFragment>

Where:

dHtml: string = '<div>hello this is html<ul><li>bla bla</li><li>bla bla 2</li></ul></div>';
    sHtml: string = 'ul{list-style-type: none;}';

The html fragment is properly injected in here:

<div [innerHTML]="htmlContent"></div>

However the style element here:

 <style [innerHTML]="styleContent"></style>

isn't working right. Is there a way to do this?

cobolstinks
  • 6,801
  • 16
  • 68
  • 97
  • I have been searching this for some time and the ways that you will find are either too specified like [style.color]="blue" or over the top solutions with dynamic components etc that break AOT. It's apparently a hack now(with shadow dom) to do things the way they've always been done. So, a quick hack, if you add the style tag as a string to your html string it should render. But I would consider another approach or maybe even another framework that isn't component based. – Dylan May 15 '17 at 21:30

4 Answers4

32

It cannot be done in the template itself (Angular template compiler does not allow it, and will just remove any <style> tag), but it can be done programmatically within the component:

ngOnInit() {
  const css = 'a {color: pink;}';
  const head = document.getElementsByTagName('head')[0];
  const style = document.createElement('style');
  style.type = 'text/css';
  style.appendChild(document.createTextNode(css));
  head.appendChild(style);
}
ebrehault
  • 2,471
  • 21
  • 22
16

By default, Angular will gobble <style> elements and put them into the default Angular CSS pipeline. You can bypass this by:

@Component({
  template: `<div [innerHTML]="styles"></div>`
})
export class OutputStyleTagComponent implements OnInit {
  public styles: SafeHtml;

  constructor(
    private sanitizer: DomSanitizer
  ) {}

  ngOnInit() {
    this.styles = this.sanitizer.bypassSecurityTrustHtml(`
      <style>
       .my-styles {
         ...
       }
      </style>
    `);
  }
}

It has drawbacks, like requiring the wrapping div container. You also must ensure you don't allow user submitted content in this tag because it opens you up to XSS attacks. If you must, then you need to ensure the content is safe before passing it into bypassSecurityTrustHtml.

Zach Dahl
  • 609
  • 5
  • 12
5

Related to the OP I don't recommend the below bypass (or any of the others in this thread) though am putting it here for completeness. The recommended solution is to store your .scss / .css / .html separately in your DB -- run a server-side gulp task & compile your modules & lazy-load them (supports then shadow-dom, no security issues, and lots of versatility).

In any case there are a litany of reasons why putting styles in templates (through injection or otherwise) is a bad practice including violating the encapsulation, etc.

The styles applied this way are globally applied styles. This may be this favorable over using a keepStyle pipe or [attr.style] in that it allows angular-computed style sheets that can be overwritten externally using !important if it was used outside of a library or whatever. Styles directly applied to a style attribute cannot be overwritten by external CSS.

Anyways, aside from that preamble you can just prevent the removal of <style> tags by placing them in an <svg> tag such as:

<svg>
  <style>
     .global-style-x {
        transform: rotate({{someAngleComputation}}deg);
     }
  </style>
</svg>

<button class="global-style-x">
</button>

Again, none of these approaches to inserting style in a template are best practices.

  • The one I mention is likely to be caught/sanitized by future template compilers
  • @ebrehault solution isn't shadow-dom compatible and also makes changes to the dom outside of the event handler and also doesn't get removed when your component gets destroyed. Further, it doesn't store with the template as OP requested.
  • @ZachDahl's solution isn't likely to break and can be turned into a pipe version (ala [innerHTML]='styleTag | keepHtml").
  • @JuliaPassynkova's solution of inline style also isn't likely to break though it means no classes can be used, no cascading, and would be very limited in usefulness.

@ZachDahl and @JuliaPassynkova solutions with or without use of pipes such as [attr.style]="style | keepStyle" or [innerHTML]="yourStyleTag | keepHTML" -...are bypassing the security apparatus and create a potential threat for injection attacks.

Matthew Erwin
  • 1,116
  • 11
  • 7
0

style element is supposed to be in the HEAD.

You can apply style from string to html element using [attr.style]

  @Component({
    selector: 'my-app',
    template: `
      <div>
        <h2 [attr.style]="style">Hello {{name}}</h2>
      </div>
    `,
  })
  export class App {
    style;
    myStyleStr = 'color:blue';

    name:string;
    constructor(private sanitizer: DomSanitizer) {
      this.name = `Angular! v${VERSION.full}`;
      this.style = sanitizer.bypassSecurityTrustStyle(this.myStyleStr);
    }
  }
Julia Passynkova
  • 17,256
  • 6
  • 33
  • 32
  • 1
    While many consider placing – Zach Dahl Jan 24 '19 at 13:55