14

In short: How do I get the generated CSS of my component as a string?

-> I want to get a snapshot of my component. That is, what the very view looks looks like. Just what you see when you click right->inspect. Html & css to render the component independently from any Javascript.

Motivation: Use that snapshot for later export as PDF. I could create the PDFs server-wise, but since Angular has a neat templating engine, why not use it for that? Comments welcome.

I managed to get the snapshot html code from my banana-component like this:

// banana component file
@Component( { 
    selector: 'banana',
    template: `<div id="report">some text</div>`,
    styles: [ '#report { font-weight:bold; }' ]
})
export class BananaComponent {
    constructor(public elementRef: ElementRef) { }
}

// parent component file
@Component(...)
export class App {
    @ViewChild(BananaComponent) myBanana;
    private getSnapshot() {
         let bananaSnapshotHtml = this.myBanana.elementRef.nativeElement.outerHTML;
         let bananaSnapshotCss = // ?? TODO
    }
}

The snapshotHtml variable for example looks like this:

<banana _ngcontent-c2="" _nghost-c4="" ng-reflect-render="true"><!--bindings={
  "ng-reflect-ng-if": "true"
}--><div _ngcontent-c4="" id="report">
    some text
</div></banana>

But I have no idea how to get the respective bananaSnapshotCss. According to Firefox, Angular somehow puts this inline:

#report[_ngcontent-c4] {
  font-weight: bold; 
}

Is there a way to get this ^ somehow programmatically? I want my bananaSnapshotCss variable above to hold this exactly. Or is my entire approach wrong?

Right now, I am manually copying the contents of my css-files into bananaSnapshotCss and thus, it is part of my TS code. This is ugly and duplicate code and ignores nested child component styles.

See live sample here, including the current workaround (app/app.ts with a TODO)

iehrlich
  • 3,572
  • 4
  • 34
  • 43
phil294
  • 10,038
  • 8
  • 65
  • 98

5 Answers5

3

You can apply the getComputedStyle() method to elementRef.nativeElement (read more about it here).

For your example this would mean:

// banana component file
@Component( { 
    selector: 'banana',
    template: `<div id="report">some text</div>`,
    styles: [ '#report { font-weight:bold; }' ]
})
export class BananaComponent {
    constructor(public elementRef: ElementRef) { }
}

// parent component file
@Component(...)
export class App {
    @ViewChild(BananaComponent) myBanana;
    private getSnapshot() {
         let bananaSnapshotHtml = this.myBanana.elementRef.nativeElement.outerHTML;
         let bananaSnapshotCss = getComputedStyle(this.myBanana.elementRef.nativeElement);
    }
}
fourcube
  • 762
  • 3
  • 10
  • please correct me if I'm wrong, but this does not seem to get the styling for the entire component recursively: Only for the component element itself. Meaning, the style definitions of everything **IN** the element is nowhere to be found, making this approach useless. At leasts it seems so: `getComputedStyle(...).cssText` does not contain any `color: yellow` for my `

    ` inside the banana component. Thanks anyway!

    – phil294 May 11 '17 at 16:01
  • You would have to select the element you are interested in and traverse it's children. But the styles you extract are going to be really verbose. Maybe one of the solutions from http://stackoverflow.com/questions/2952667/find-all-css-rules-that-apply-to-an-element could help you? – fourcube May 11 '17 at 17:00
2

If you can generate PDF on client side, I would suggest to use a CSS media query: @media print { ... }

The main reason is that DOM doesn't reflect "application" state. Only re-rendering exported DOM isn't compliant. More your component CSS won't be enough to reproduce all styles (agent, page, component imbrication, and so on).

Most tool that have server-side web-based rendering (PDF, images, ...) are using PhantomJS to generate a "snapshot".

In the case of Angular, you can also take a look at Angular Universal that offer server-side rendering.

LoganMzz
  • 1,597
  • 3
  • 18
  • 31
  • huh. wrapping the component inside a `@media print` block and simply pressing ctrl+p might actually be the sanest approach here (however it gives you non-reusable PDF instead "exporting" the HTML) – phil294 May 11 '17 at 16:33
1

In your @Component selector add a class using host: {'class':'reports'} . Then in your css you can set styles using the reports class. Hope this helps you

Maximilian Riegler
  • 22,720
  • 4
  • 62
  • 71
RemyaJ
  • 5,358
  • 4
  • 22
  • 41
  • I'm afraid I dont quite get it. How can I use this to get the CSS (either source or ng-generated) of my ``-component? I want to append it to the static html and create a downloadable file out of it. – phil294 Mar 29 '17 at 15:59
1

Use document.documentElement.innerHTML get the the whole DOM with inline css and save.

Julia Passynkova
  • 17,256
  • 6
  • 33
  • 32
  • ..which would get me the markup for the entire website, when I only want a specific component. So you're saying the preferred way would be to extract the inline-css via regex or similar? – phil294 May 07 '17 at 18:32
0

Here is the solution I could come up with.

@Component(...)
export class App {
    @ViewChild(BananaComponent) myBanana;
    private getSnapshot() {
        let bananaSnapshotHtml = this.myBanana.elementRef.nativeElement.outerHTML;
        // new:
        let bananaSnapshotCss = [].slice.call(document.styleSheets)
            .map(sheet => [].slice.call(sheet.rules))
            .filter(sheet => !sheet.disabled)
            .reduce((all_rules, rules) => all_rules.concat(rules), [])
            .reduce((style, rule) => `${style}\n${rule.cssText}`, "");
    }
}

What this does is simply concatenating all styleSheets of the document. This will also contain CSS for other components. These could be filtered out but that is not really necessary.

Here is the updated plunker, if anybody is interested. http://embed.plnkr.co/0BlyVihLcoDOLqzwqmNm/

With all of this, the component can finally be exported as a complete html file.

phil294
  • 10,038
  • 8
  • 65
  • 98