access SASS values ($colors from variables.scss) in Typescript (Angular2 ionic2)

54,476

Solution 1

Unfortunately, there is no way to access SASS variable directly from typescript/javascript code. However, we can make a workaround to access those variables.

Let me describe briefly the steps to access SASS variables from within typescript source code:

1. Creating a SASS Helper Component

Create ../providers/sass-helper/sass-helper.component.scss:

$prefix: "--"; //Prefix string for custom CSS properties

//Merges a variable name with $prefix
@function custom-property-name($name) {
    @return $prefix + $name;
}

// Defines a custom property
@mixin define-custom-property($name, $value) {
    #{custom-property-name($name)}: $value;
}

body {
    // Append pre-defined colors in $colors:
    @each $name, $value in $colors {
        @include define-custom-property($name, $value);
    }

    // Append SASS variables which are desired to be accesible:
    @include define-custom-property('background-color', $background-color);
}

In this SCSS file, we simply create custom properties inside the body section of the DOM. You should add each SASS variable that you want to be accessible into this SCSS file by using the mixin called define-custom-property which expects two parameters: variable name and variable value.

As an example, I have added entries for all the colors defined in $colors as well as an entry for the SASS variable $background-color defined in my theme/variables.scss file. You can add as many variables as you wish.

Create ../providers/sass-helper/sass-helper.component.ts:

import { Component } from '@angular/core';

export const PREFIX = '--';

@Component({
    selector: 'sass-helper',
    template: '<div></div>'
})
export class SassHelperComponent {

    constructor() {

    }

    // Read the custom property of body section with given name:
    readProperty(name: string): string {
        let bodyStyles = window.getComputedStyle(document.body);
        return bodyStyles.getPropertyValue(PREFIX + name);
    }
}

2. Integrating SASS Helper Component

From now on, we can follow standard Ionic2 framework principles for component integration and usage.

  • Add the component class name (SassHelperComponent) into the declarations section of your NgModule in app.module.ts
  • Insert the following HTML code into the HTML template of your page from where you want to access those magic variables:

    <sass-helper></sass-helper>


3. Using Helper Component

In your page's TS file, you should insert the following lines into your page class:

@ViewChild(SassHelperComponent)
private sassHelper: SassHelperComponent;

Finally, you can read the value of any SASS variable by just calling the child class method as follows:

// Read $background-color:
this.sassHelper.readProperty('background-color');

// Read primary:
this.sassHelper.readProperty('primary');

Solution 2

One possibility is to generate a .ts file from the .scss file. A simple example of this process:

1) Install npm i --save-dev scss-to-json.

2) Put this in your package.json:

  "scripts": {
    ...
    "scss2json": "echo \"export const SCSS_VARS = \" > src/app/scss-variables.generated.ts && scss-to-json src/variables.scss >> src/app/scss-variables.generated.ts"
  },

and run it with npm run scss2json. Windows users will need to adjust the example.

3) Access the variables:

import {SCSS_VARS} from './scss-variables.generated';
...
console.log(SCSS_VARS['$color-primary-1']);

One advantage of this is, that you'll get type completion from IDE's and it's a quite simple means to achieve your goal in general.

Of course you could make this more advanced, for example by making the generated file read only and by putting the script into it's own .js file and make it work on every OS.

Solution 3

This is possible using CSS Modules.

CSS Modules

From the project description:

When importing the CSS Module from a JS Module, it exports an object with all mappings from local names to global names.

In a way that we could read variables from css/scss file like this:

import styles from "./style.css";    

element.innerHTML = '<div class="' + styles.className + '">';

Support for CSS Modules is already setup by default by the Angular CLI which uses Webpack configured with the css-loader.

The steps to make it work are:

  1. Export only the scss variables that you want to use.
  2. Configure a typescript module for styles.scss.
  3. Import the variables in your typescript components.

1 - Export the variables

In your styles.scss, use the keyword :export to export $colors. It seems that :export doesn't support exporting maps, only strings, so we have to create a mixin to convert a map into strings:

$colors: (
  primary: #387ef5,
  secondary: #32db64,
  danger: #f53d3d,
  light: #f4f4f4,
  dark: #222,
  favorite: #69bb7b,
);

@mixin rule($key, $value, $prefix) {
  #{$prefix}-#{$key}: $value;
}
@mixin map-to-string($map, $prefix) {
  @each $key, $value in $map {
    @include rule($key, $value, $prefix);
  }
}

:export {  
  @include map-to-string($colors, "colors");
}

The generated :export will be:

:export {
  "colors-danger": "#f53d3d";
  "colors-dark": "#222";
  "colors-favorite": "#69bb7b";
  "colors-light": "#f4f4f4";
  "colors-primary": "#387ef5";
  "colors-secondary": "#32db64";
}

2 - Configure a typescript module for styles.scss

We have to create a styles.scss.d.ts file with the following content to allow the import of styles.scss in our typescript files:

export interface globalScss {}

export const styles: globalScss;

export default styles;

3 - Import the variables in the target typescript component

As we used a default export, we could import it in our component like this:

//...
import styles from 'src/styles.scss';

@Component({
  selector: 'app-colors-use',
  templateUrl: './colors-user.component.html',
  styleUrls: ['./colors-user.component.scss'],
})
export class ColorsUserComponent implements OnInit {

  buttonColor = styles["colors-primary"] //"#387ef5"

4 - (Plus) Add type definition to styles.scss.d.ts

You could add type information to style.scss.d.ts:

export interface globalScss {  
  "colors-danger": string
  "colors-dark": string
  "colors-favorite": string
  "colors-light": string
  /**
   * Used for app-button, usually blue
   */
  "colors-primary": string
  /**
   * Used for borders, usually green
   */
  "colors-secondary": string
}

export const styles: globalScss;

export default styles;

In that way, you could have some benefits in an editor like VS code:

Comments

Auto complete

UPDATE:

The configuration above only works until ng 10. Css Modules configuration has changed considerably from ng 10 to ng 11.

Solution 4

I would like to add up something to @mete-cantimur answer.

import {Component, OnInit, ViewEncapsulation} from '@angular/core';

const PREFIX = '--';

@Component({
  selector: 'app-styles-helper',
  templateUrl: './styles-helper.component.html',
  styleUrls: ['./styles-helper.component.scss'],
  encapsulation: ViewEncapsulation.None
})
export class StylesHelperComponent implements OnInit {

  ngOnInit(): void {

  }

  readProperty(name: string): string {
    const bodyStyles = window.getComputedStyle(document.body);
    return bodyStyles.getPropertyValue(PREFIX + name);
  }
}

My helper component wasn't being able to modify body styles. Even I set up everything correctly, custom properties were not being saved.

I had to add encapsulation: ViewEncapsulation.None to the component in order to let it modify body styles.

Hope this helps.

Solution 5

I know this question is now a few years old, but I thought I'd share the solution I use. It is a more simplistic version of @mete-cantimur's answer, there is no requirement to set up any extra CSS style sheets. It will read from the loaded styles on the page instead.

import {Directive, ElementRef} from '@angular/core';

@Directive({
    selector: '[css-helper]',
})
export class CssHelperDirective {

    element: any;

    constructor(_ref: ElementRef) {
        this.element = _ref.nativeElement;
    }

    readProperty(name: string): string {
        return window.getComputedStyle(this.element).getPropertyValue(name);
    }
}

Usage:

<div #primary css-helper class="primary"></div>
@ViewChild('primary', {read: CssHelperDirective})
private cssHelper: CssHelperDirective;
let color = this.cssHelper.readProperty('background-color');
Share:
54,476
nyluje
Author by

nyluje

Updated on July 09, 2022

Comments

  • nyluje
    nyluje over 1 year

    In Ionic 2, I would like to access the $colors variables from the file "[my project]\src\theme\variables.scss".

    This file contains:

    $colors: (
      primary:    #387ef5,
      secondary:  #32db64,
      danger:     #f53d3d,
      light:      #f4f4f4,
      dark:       #222,
      favorite:   #69BB7B
    );
    

    In a component, I draw a canvas. It looks like that:

    import {Component, Input, ViewChild, ElementRef} from '@angular/core';
    
    @Component({
        selector: 'my-graph',
    })
    @View({
        template: `<canvas #myGraph class='myGraph'
         [attr.width]='_size'
         [attr.height]='_size'></canvas>`,
    })
    
    export class MyGraphDiagram {
        private _size: number;
    
        // get the element with the #myGraph on it
        @ViewChild("myGraph") myGraph: ElementRef; 
    
        constructor(){
            this._size = 150;
        }
    
        ngAfterViewInit() { // wait for the view to init before using the element
    
          let context: CanvasRenderingContext2D = this.myGraph.nativeElement.getContext("2d");
          // HERE THE COLOR IS DEFINED AND I D LIKE TO ACCESS variable.scss TO DO THAT
          context.fillStyle = 'blue';
          context.fillRect(10, 10, 150, 150);
        }
    
    }
    

    As one can see, at some point in this code the color of the shape is defined: context.fillStyle = 'blue' , I would like to use instead something like context.fillStyle = '[variables.scss OBJECT].$colors.primary '.

    Has anyone an idea?

  • Admin
    Admin almost 7 years
    Yes, but there's got to be an easier way.
  • Mete Cantimur
    Mete Cantimur almost 7 years
    Yeah, I wish there was an easier way but remember SASS must be compiled to CSS therefore, all the variable definitions get lost after compilation. Accessing SASS files directly from typescript code would be an alternative method, however, it requires parsing and HTTP access, which is another trouble.
  • nyluje
    nyluje over 6 years
    Did not have time to try it yet (priority shifting), but since it has already two votes up, It seems validated by other users. Thanks for your help @Mete Cantimur. I know I'll use your input at some point.
  • nyluje
    nyluje over 6 years
    @Mete Cantimur , I finaly implemented it and it works. Thanks (upvote given on top of validation). The only slightly different think I did was renaming "sass-helper.component.ts" and "sass-helper.component.scss" to "sass-helper.ts" and "sass-helper.scss" . Before doing that the colors were not in the body of the CSS when controlling in the Chrome console.
  • rbasniak
    rbasniak over 5 years
    Wow, I wish I could upvote this 100 times! Very clever solution!
  • Alberto Chiesa
    Alberto Chiesa almost 5 years
    It works very well, at least with a little development. gist.github.com/alberto-chiesa/b21500885a22586767a36a7e8f10b‌​eb6
  • Emobe
    Emobe over 4 years
    just a side note, this plugin doesn't support maps
  • yuva
    yuva over 4 years
    I followed everything exactly. Mine's not saving the colors in body. When I tried selecting properties set by bootstrap, it works though.
  • testing
    testing over 4 years
    Didn't work for me. In the body there are still no styles. Also you are not using sassHelper ...
  • yuva
    yuva over 4 years
    @testing I renamed sassHelper into StylesHelperComponent. You can name it anything :). Try saving some custom property from styles.scss and see if it saves. You can check from DevTools > Elements > Computer Styles. If it appears there, then your issue is definitely related to view encapsulation.
  • testing
    testing over 4 years
    Instead of creating separate files I now used ng gc SassHelper. After importing the stylesheet and including your tip it seems to work. Thanks!
  • Post Impatica
    Post Impatica about 4 years
    Is this whole component-style-helper approach used for obtaining styles that are already applied somewhere in the DOM? I want to be able to read scss variables that aren't necessary used anywhere... so I'm guessing this approach won't work, correct?
  • monkeybuffer
    monkeybuffer almost 4 years
    For those copy/pasting and it isn't saving to the body - the op forgot to include styleUrls: ['./sass-helper.component.scss'] in the @Component declaration. If that's not included, the scss never runs, and stuff doesn't get added to the body - ergo, blank variables when reading them in javascript.
  • Rene Hamburger
    Rene Hamburger about 3 years
    It maybe be helpful to point out that the export has to happen in a global scss file, as Angular only uses the Webpack css-loader, which parses the export, for global styles.
  • Abhinav Atul
    Abhinav Atul about 3 years
    doesn't work with current angular version, while the style elements are injected in the body into one of the many style tags, they attributes are not present in the window.getComputedStyle(document.body);
  • Hicaro
    Hicaro almost 3 years
    I don't seem to be able to make it work. All I get back when I import the module is an empty object ({}). I have the exact same content from the post. Did anything change since in this setup?
  • Chris A
    Chris A almost 3 years
    @ReneHamburger I can't get this to work either. Literally copied the steps line for line. In my component where I try to import styles from '../../styles.scss'; it causes an error "export 'default' (imported as 'styles') was not found in '../../styles.scss'
  • tiagolisalves
    tiagolisalves almost 3 years
    Which angular version are you using ?
  • Chris A
    Chris A almost 3 years
    @tiagolisalves 11.0.5
  • tiagolisalves
    tiagolisalves almost 3 years
    Maybe something have changed on it. I remember using angular 9 or 10 on this answer. I will try to update it to ng 11.
  • Chris A
    Chris A almost 3 years
    @tiagolisalves that would be super appreciated. I'm trying to translate a project from Vue to Angular and in Vue I used CSS Modules in this exact way. Strangely, I am getting different behaviour on StackBlitz compared to locally on my IDE. On SB the module loads, console.log(styles) gives :export{colors-primary:#387ef5;colors-secondary:#32db64;colo‌​rs-danger:#f53d3d;co‌​lors-light:#f4f4f4;c‌​olors-dark:#222;colo‌​rs-favorite:#69bb7b} but console.log(styles["colors-primary"]) is undefined
  • Chris A
    Chris A almost 3 years
  • tiagolisalves
    tiagolisalves almost 3 years
    @ChrisA @Hicaro Css modules configuration changed considerably from ng 10 to ng 11. Now, its not the default. You have to set modules : true on the webpack configuration, which is not enabled on ng 11. I think its the case to create an issue on the @angular/cli repo to ask for expose the configuration of it.
  • Chris A
    Chris A almost 3 years
    OK thanks @tiagolisalves . It seems for now my best option is to downgrade to 10 while I wait for any issue to be resolved. I have created github.com/angular/angular-cli/issues/19667 , we will see what happens :)
  • tiagolisalves
    tiagolisalves almost 3 years
    I saw it. Great contribution. I won’t have a compromise on it, but I will try to come up with a solution as a PR.
  • Chris A
    Chris A almost 3 years
    Was marked as a duplicate of an already closed issue. Looks like this is not something they want to support going forward :( github.com/angular/angular-cli/issues/19622
  • Hicaro
    Hicaro almost 3 years
    ChrisA @tiagolisalves thanks for getting a resolution on this. It seems the Angular team will not do anything about it.