1

In my CSS file, I have a class:

.test{
   background: red;
}

But at the beginning of my app, I'd like to redefine this class based on the server response such that the background becomes blue or green depending on a variable.

It is very important to attribute to this class (.test) the new color as many of my elements have already this class and I don't want to apply a new class to them.

Not sure it's very clear but to summarize, I want to create a class from javascript (using angular 2) that will apply to the whole document.

2 Answers 2

1

The code below will find any style rules (including those inside media rules) that are part of the document, and overwrite any styles that are matched by the selector.

You can call modifyStyles('.test', { 'background': 'blue' }) on an instance of the service to make all styles with the .test class to have a blue background. You probably want to play with the way the selector functions, because in its current implementation any rule that has .test anywhere within it will have its background changed. You might prefer changing the regex to ^.test$ so that it matches .test and only .test.

@Injectable()
export class CssUpdateService {
    constructor( @Inject(DOCUMENT) private document: Document) {
    }
    modifyStyles(selector: string, styles: any) {
        const rulesToUpdate = this.findRules(new RegExp(`\b${selector}\b`, 'g'));
        for (let rule of rulesToUpdate) {
            for (let key in styles) {
                rule.style[key] = styles[key];
            }
        }
    }
    /**
     * Finds all style rules that match the regular expression
     */
    private findRules(re: RegExp) {
        let foundRules: CSSStyleRule[] = [];

        let ruleListToCheck = Array.prototype.slice.call(this.document.styleSheets);
        for (let sheet of ruleListToCheck) {
            for (let rule of (<any[]>(sheet.cssRules || sheet.rules || []))) {
                if (rule instanceof CSSStyleRule) {
                    if (re.test(rule.selectorText)) {
                        foundRules.push(rule);
                    }
                }
                else if (rule instanceof CSSMediaRule) {
                    ruleListToCheck.push(rule);
                }
            }
        }
        return foundRules;
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks... it seems extremely complicated for doing that. I thought it would be possible to add a class to the body somehow from angular
It would be much easier and better to add a class to some element than changing style rules. So if you had some classes with backgrounds that correspond to your variable values then that would be the way to go. You can just use the NgClass directive for that.
I may be dumb ... but why using Array.prototype.slice.call(this.document.styleSheets) ? Do Array.from(this.document.styleSheets) would not work ?
1

EDIT (bc I was confused on your requirements initially) -

I don't think there's a good way to modify the global styles file after the application loads, but if I am wrong on that someone please correct me.

The shadow DOM makes this tricky. I would provide a runtime configuration variable to your module and then conditionally add a class to your application's root component.

 <div class="outer-app-wrapper"  [ngClass]="someValue">

Then in your global styles.css file, you can just define all the different variations of .test there could be.

.someValue1 .test { 
     background: red;
}

.someValue2 .test {
     background: green;
 }

 .someValue3 .test {
     background: yellow;
 } 

I think if you define all the variations in the styles.css file, you should be able to avoid having to use the 'host-context:' selector in the descendant components. There's no need to add any class to an element outside of Angular's scope like the 'body' element, just add it to the top-most element of your app, and as long as descendant components don't redefine the test class as it is defined in the global stylesheet, it should work fine.

Note - you could also use @HostBinding to add the classes to your root component if you don't want to add a wrapper element or modify an existing one

6 Comments

Thanks but as mentioned in my question, the class is dynamic! Meaning I need to define the background based on a variable...
You can provide runtime configuration variables to the app in the 'providers' line of your module. And change behaviors based on that. But it could be tricky if the value is coming remotely from a server, it may not be ready when the app bootstraps and can throw errors.
right... Can't I just create a class and add it to the stylesheet somehow?
At runtime? That could be odd... If you were going to do that ,I'd just add the rules to a completely separate stylesheet and load it conditionally.
I think I have a better idea of what you're trying to do now. I will update my answer...
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.