1

How can I add separate CSS for one page in Angular?

This is the CSS I need, as per How to remove the URL from the printing page?:

@media print{
    @page{
        margin:0;
    }
    body{
        margin:30px;
    }
}

But putting CSS into the component with ::ng-deep or ViewEncapsulation.None won't help, because when navigating away from a page, the CSS of the page isn't deleted.

And adding a class to the body via ngOnInit/ngOnDestroy won't help, because allow us to change the @page CSS, because @page isn't part of the body.

I've added a Stackblitz, which explains the problem clearly.

Perhaps the question would be better summarized as "How to add global CSS, but remove it when we leave the page?".

Eliezer Berlin
  • 3,170
  • 1
  • 14
  • 27
  • 1
    Try using component style sheet for separate style of specific page, that wont effect styles globally – Awais Nov 11 '20 at 10:16
  • Then follow binding/encapsulation meathod mentioned in this https://stackoverflow.com/questions/34881401/style-html-body-from-web-component-angular-2 – Awais Nov 11 '20 at 11:11
  • @Awais That solution would work for ``, but it wouldn't work for other global CSS like `@page`. (If you look at the CSS I wrote in my post, you'll see what I mean.) – Eliezer Berlin Nov 11 '20 at 12:36
  • You can have a look at this answer to a similar question I asked https://stackoverflow.com/a/64672874/13680115 . I believe the answer should also work for your case, it worked for me – Owen Kelvin Nov 15 '20 at 14:37

4 Answers4

3

Solved!

We print the <style> block directly into the component's HTML, and therefore when the component gets removed, our <style> block gets removed too. (Normally this wouldn't work, but thanks to DomSanitizer.bypassSecurityTrustHtml, Angular won't break our code when running optimizations.)

Here's a StackBlitz.

First, create a new component to handle the work:

component.ts: (This is all we need. We don't need an HTML or style.css file.)

// Usage Instructions:
// Inside  your local component, place this HTML 
// <app-local-css [style]="'body{background:green !important;}'"></app-local-css>
// OR
// <app-local-css [scriptURL]="'/path/to/file.css'"></app-local-css>

@Component({
  selector: "app-local-css",
  template: '<span style="display:none" [innerHTML]="this.safeString"></span>'
})
export class LocalCSSComponent implements OnInit {
  constructor(protected sanitizer: DomSanitizer) {}
  @Input() scriptURL?: string;
  @Input() style?: string;

  safeString: SafeHtml;
  ngOnInit() {
    if (this.scriptURL) {
      let string = '<link rel="stylesheet" type="text/css" href="' + this.scriptURL + '">';
      this.safeString = this.sanitizer.bypassSecurityTrustHtml(string);
    } else if (this.style) {
      let string = '<style type="text/css">' + this.style + "</style>";
      this.safeString = this.sanitizer.bypassSecurityTrustHtml(string);
    }
  }
}

And then use it like this:

mySample.component.html:

<app-local-css [style]="'body{background:green !important;}'"></app-local-css>
// OR
<app-local-css [scriptURL]="'/path/to/file.css'"></app-local-css>
Eliezer Berlin
  • 3,170
  • 1
  • 14
  • 27
0

Don't change the whole body from apple. Instead, there are a few changes to make.

  1. In the app component, hold a boolean for whether or not you are on apple, and use ngClass for class defined in scss.

  2. Track which route you are on in appComponent, and set isApple accordingly

  3. Add a div around all your html, for container to take full size

  4. Add global html, body setting height to 100% so you see color everywhere

  5. Remove body overriding in apple

so, appComponent.ts:

  isApple: Boolean;
  constructor(router: Router) {
    router.events.subscribe(v => {
      if (v instanceof NavigationEnd) {
        this.isApple = v.url === "/apple";
      }
    });
  }

appComponent.html:

<div [ngClass]="{'red':isApple}" class="container">
    <p>
        There are two components: Apple and Banana. Switching between them will show
        the problem.
    </p>

    <router-outlet></router-outlet>
</div>

appComponent.scss


.red {
  background-color: red;
}

.container {
  height: 100%;
}

apple.component.scss (remove body)

/*Sample "global" CSS, that affects something outside the current component.*/
::ng-deep {
  @media print {
    @page {
      margin: 0;
    }
  }
}

styles.scss (global)

html, body {
  height: 100%;
}

You can see this altogether at this Stackblitz link

PMO1948
  • 2,210
  • 11
  • 34
  • ....The whole objective is to put CSS on the body. (For context of why we might need global CSS, the real-world scenario that inspired my question was hiding the `
    ` and `
    ` on a particular page while printing.... without changing the code of app.component.html, since we have a LOT of pages, and only one component has this requirement.)
    – Eliezer Berlin Nov 17 '20 at 11:36
  • Anyway, your code doesn't solve anything when it comes to @media print. We still have exactly the same problem as before. – Eliezer Berlin Nov 17 '20 at 11:43
0

Angular is doing client-side rendering, which is bad news, because you do not have separate pages. You have several possible solutions though:

1. Separate page

You can create another page with or without Angular, which includes the CSS you need and load that page. In the most simplistic approach to achieve this, the other page would have a different URL. If having a different URL is not to your liking, then you could hide your page's content and show the other page inside an iframe. It would admittedly be a hacky solution, but it is a solution.

2. Client-side CSS rendering

Instead of just loading the CSS, you could have a component which would control global CSS rules, matched by your view's name. You would have a template value rendered to a property, like:

@media print{
    @page{
        margin:0;
    }
    body{
        margin:30px;
    }
}

And when you visit the page where this needs to be activated, you would simply initialize a property with a style HTML element that was generated based on the template and added to head. Once you leave the given view, your component would detect that event and would remove() that element. If you choose this solution, then it would be wise to make sure that you are supporting this on more general terms, so that if some new views will have their custom global CSS, then they would be easy to integrate into your project in the future.

3. body classes

You could add/remove some custom-print or whatever class to/from body whenever the style is to be changed. This way you could add the CSS exactly once to your HTML and change the rules accordingly, like:

body.custom-print {
    margin: 30px;
}

This would be a neat solution, but the problem in your case is that you have a @page rule as well and I'm not sure how you could make that dependant on body classes or some other HTML attributes. I would conduct quite a few experiments about this if I were you.

4. Iframe staging

You could avoid having that CSS in your main page, but would have a hidden iframe where you would have the CSS and would just copy the content into the CSS and once that's loaded, print that.

rofrol
  • 14,438
  • 7
  • 79
  • 77
Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
  • 1
    **1 & 4** are basically the same, "Put the content in an iframe"... Which is pretty drastic, but technically will work. **#3** I'd already written as part of the question, and rejected it for the same reason you did. **#2** seems like the only reasonable solution, and we can probably write some kind of service to do it, too. (Rather than put the CSS into HTML or CSS file, simply put the CSS into your `.ts` file, and use JS to append it on ngInit and remove it on ngDestroy.) It's an interesting idea. – Eliezer Berlin Nov 18 '20 at 10:02
-1

You can add different css files in the component (for instance, app-task.component.ts):

@Component({
  selector: 'app-task',
  templateUrl: './app-task.component.html',
  styleUrls: ['./app-task.component.scss', './styles2.scss', './styles3.scss']
})

In this example, the style files are in the same folder that the component, but this is not the best option: you have to put the files in assets, for example. Also, be careful with the thread of the styles, since the first one you put will be put before the second (obviously).

  • The whole problem is those styles don't get removed when the page is unloaded. I need a way to add styles that WON'T persist from page to page. – Eliezer Berlin Nov 11 '20 at 10:58
  • What Angular does is it scopes the CSS to a particular component. For example, if I'm working with `app-mybutton`, then angular will automatically add `app-mybutton` to all CSS written inside that component. Which is great for keeping code clean, but not very helpful when I'm trying to modify the ``. – Eliezer Berlin Nov 11 '20 at 11:05
  • (Well, to be more clear, it actually adds some `[ng-content]` stuff to the CSS, but the `[ng-content]` effectively keeps all the CSS you affecting only that one component.) – Eliezer Berlin Nov 11 '20 at 11:07
  • I created a [Stackblitz](https://stackblitz.com/edit/encapsulation-problem?file=src/app/banana/banana.component.html) to better show the problem. – Eliezer Berlin Nov 17 '20 at 10:52