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

7

67

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?

Vermicide answered 4/11, 2016 at 9:8 Comment(2)
If you use CSS variables (custom properties), you can grab them easily using getComputedStyle.Tam
This seems to be an alternative solution.Gibbsite
T
65

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');
Taxation answered 28/12, 2016 at 0:1 Comment(9)
Yes, but there's got to be an easier way.Tam
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.Taxation
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.Vermicide
@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.Vermicide
Wow, I wish I could upvote this 100 times! Very clever solution!Haupt
I followed everything exactly. Mine's not saving the colors in body. When I tried selecting properties set by bootstrap, it works though.Autocrat
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.Kessia
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);Americaamerican
Seeing the same issue as @AbhinavAtul on Angular 15. Is this working for anyone else? Is there a way to make this work.Deerdre
G
16

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

  1. Install npm i --save-dev @crocsx/scss-to-json. Note: Previously I had scss-to-json here, but since this is not maintained I updated to the fork.

  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.

  1. 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.

Grumble answered 6/6, 2018 at 6:17 Comment(4)
It works very well, at least with a little development. gist.github.com/alberto-chiesa/b21500885a22586767a36a7e8f10beb6Newsstand
just a side note, this plugin doesn't support mapsThirzia
scss-to-json seems no longer maintainedHillyer
I guess you noticed because it causes problems when upgrading mac to ventura? We're now using a fork, I'll update the snippets to reflect that.Grumble
P
12

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.

Preoccupancy answered 15/8, 2020 at 17:45 Comment(13)
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.Expiable
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?Grazia
@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'Tugboat
Which angular version are you using ?Preoccupancy
@Preoccupancy 11.0.5Tugboat
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.Preoccupancy
@Preoccupancy 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;colors-danger:#f53d3d;colors-light:#f4f4f4;colors-dark:#222;colors-favorite:#69bb7b} but console.log(styles["colors-primary"]) is undefinedTugboat
Stackblitz - stackblitz.com/edit/…Tugboat
@ChrisA @Grazia 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.Preoccupancy
OK thanks @Preoccupancy . 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 :)Tugboat
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.Preoccupancy
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/19622Tugboat
ChrisA @Preoccupancy thanks for getting a resolution on this. It seems the Angular team will not do anything about it.Grazia
A
8

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.

Autocrat answered 8/5, 2019 at 14:35 Comment(4)
Didn't work for me. In the body there are still no styles. Also you are not using sassHelper ...Gibbsite
@Gibbsite 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.Autocrat
Instead of creating separate files I now used ng gc SassHelper. After importing the stylesheet and including your tip it seems to work. Thanks!Gibbsite
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?Anaximenes
D
3

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');
Duyne answered 6/2, 2020 at 10:9 Comment(0)
S
2

Another way to share variables between SASS and Typescript files.

First, declare the variables.

// Screen Sizes
$screen-tablet1: 768px;
$screen-tablet2: 800px;
$screen-tablet3: 1240px;

In the styles.scss, read the variables and declare CSS custom properties to be used globally.

// import the declared variables
:root {
  --screen-tablet1: #{$screen-tablet1};
  --screen-tablet2: #{$screen-tablet2};
  --screen-tablet3: #{$screen-tablet3};
}

For example, I want to update the number of grid columns according to the screen width.

getGridCols(width: number) {
    const sizeTablet1 = parseInt(
      window
        .getComputedStyle(document.documentElement)
        .getPropertyValue('--screen-tablet1')
        .replace('px', '')
    );

    const sizeTablet3 = parseInt(
      window
        .getComputedStyle(document.documentElement)
        .getPropertyValue('--screen-tablet3')
        .replace('px', '')
    );
    if (width < sizeTablet1) {
      return 1;
    }
    if (width < sizeTablet3) {
      return 2;
    }
    return 3;
  }
Scatology answered 25/7, 2023 at 9:1 Comment(0)
A
1

In Windows, I used the following taken from Bersling's answer.

npm i --save-dev ruoqianfengshao/scss-to-json

npm i --save-dev node-sass

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

npm run scss2json

import {SCSS_VARS} from './scss-variables.generated';
...
console.log(SCSS_VARS['$color-primary-1']);
Anaximenes answered 24/10, 2019 at 17:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.