Skip to content

Dynamically import a stylesheet depending on the environment #4685

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
masaanli opened this issue Feb 14, 2017 · 19 comments
Closed

Dynamically import a stylesheet depending on the environment #4685

masaanli opened this issue Feb 14, 2017 · 19 comments

Comments

@masaanli
Copy link

Please provide us with the following information:

I'm having a question about how to dynamically import 'scss' files based on the environment which is serving or building.

Example.

I have a /styles/app/app-1.scss.
I have a /styles/app/app-2.scss.

Now i have also 2 environments called app-1 and app-2
I want to be able to say if it's served/builded environment is app-2 for example that it's loading the /styles/app/app-2.scss stylesheet.

This so I can develop 1 website and depending on how I build it's including the stylesheet for overwriting some parts of the application, style the application.

OS?

Windows 7, 8 or 10. Linux (which distribution). Mac OSX (Yosemite? El Capitan?)
Mac OSX Sierra

Versions.

Please run ng --version. If there's nothing outputted, please run in a Terminal: node --version and paste the result here:
cli c30 and node v 7.5.0

Repro steps.

Was this an app that wasn't created using the CLI? What change did you do on your code? etc.

The log given by the failure.

Normally this include a stack trace and some more information.

Mention any other details that might be useful.


Thanks! We'll be in touch soon.

@agatsoh
Copy link

agatsoh commented Feb 14, 2017

Similarly I want to know how to include .less stylesheet files in an angular2 cli project in the current scheme of things.
It would also be nice if you include in the Wiki in what order are CSS files loaded.

  1. From angular-cli.json
  2. From styles.css
  3. styleUrls array inside the Component.
    Which stylesheets are applied first and which ones last and which stylesheets would override the others ?

@RicardoVaranda
Copy link
Contributor

@agatsoh I believe you already posted the order correctly.

At start up it loads it from styles array in angular-cli.json which is where styles.css is loaded from lastly your specific stylesUrls within your components.

@RicardoVaranda
Copy link
Contributor

@agatsoh @masaanli I have found a solution for this, keep in mind this isn't exactly supported by the cli but rather a feature of Webpack.

Example of app.component.ts:

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

@Component({
  selector: 'app-root',
  template: '<h1>Testing :)</h1>',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  ngOnInit(){
    if(isDevMode()) {
      require("style-loader!./../styles2.css");
    } else {
      require("style-loader!./../styles.css");
    }
  }
}

Where styles2:

h1 {
  color: red;
}

Styles:

h1 {
  color: green;
}

While this may work it was brought to my attention by @filipesilva that there might be some drawbacks to using this solution such as:

  • Your code not being as portable
  • Styles may not be processed correctly
  • Might break if cli changes its loaders

@masaanli
Copy link
Author

@RicardoVaranda nice ! Something like this was what I searched for.

But then it's loading a css file. But is there maybe a option to do it when building or serving the project? So it's scss compiled together?

@RicardoVaranda
Copy link
Contributor

RicardoVaranda commented Feb 14, 2017

@masaanli Similar to how I have stated above

if(isDevMode()) {
      require("style-loader!./../styles2.css");
    } else {
      require("style-loader!./../styles.css");
    }

You can also do:

if(isDevMode()) {
      require("style-loader!./../styles2.scss");
    } else {
      require("style-loader!./../styles.scss");
    }

Where for example styles2.scss is:

$green : green;

h1 {
  color: $green;
}

But once again the same implications I stated above apply.

@masaanli
Copy link
Author

masaanli commented Feb 14, 2017

@RicardoVaranda

I tested it and it is working!

The problem I discovered is, I use Bootstrap and have in the angular-cli.json my variables defined.

I cannot override them in the app.component (require .scss)

You know maybe a solution for this?

I managed to fix it, but maybe there is abetter solution.

I fixed it by @import everything in that stylesheet.

so i require a stylesheet dynamically in the app.component.ts and in that stylesheet i import the other stylesheets. (so i have double code) for several apps. but it's working.

STILL HAVE THEN THE PROBLEM: That the variables inside the style file for components which are included like 'styleUrl' are not working anymore!!

(because 2 times require will not work. (cannot read variables from the other files).

@RicardoVaranda
Copy link
Contributor

That is because the instance of style-loader is different when called by the cli and when It's called by the require and thus you won't have access to variables from the cli instance and vice versa.

To be honest I think you are over complicating the logic to do this, A better approach for you in my opinion would be to have 2 different styles: styles.scss styles-dev.scss

Inside each of these just do the @import like you mentioned above requiring any other file you need to overwrite. Yes you will have change it in angular cli every time you want to switch between environments but realistically how often are you really switching between both of these.

If you wanted to change environments you would have to turn off the cli and re-run it with the new environment variable anyway with your current setup.

@masaanli
Copy link
Author

@RicardoVaranda true, but I have 1 code base and depending on the commando's I wanna be able to say deploy this version or deploy that version. You cannot add logic in the angular-cli json ;) So that's no option for me.

@agatsoh
Copy link

agatsoh commented Feb 15, 2017

@RicardoVaranda Do you have any idea how we import .less stylesheets in angular2 ?

And I apologise I forgot to ask you, what about the stylesheets declared in index.html ? Do they override all other stylesheets? This is what I am observing.

@filipesilva
Copy link
Contributor

Closing as this is now mostly a discussion question instead of an issue.

The CLI doesn't provide you with any preferred way out of the box to dynamically lazy load styles and apply then. You'll have to come up with a strategy yourself. Stack Overflow has great answers for this sort of question.

Functionality that we do have is a way to have a global style that isn't loaded by default in index.html, and instead are just outputted to dist. You can set a global style to be lazy, see https://github.com/angular/angular-cli/blob/master/docs/documentation/stories/global-styles.md for more info. This has actually been around for a while but wasn't documented.

Here are a couple of random web results that show how you apply styles via javascript:

Approaches that intrinsically depend on webpack loader might work right now but are not officially supported, which means they can stop working at any time or might not work as expected.

@RicardoVaranda
Copy link
Contributor

@agatsoh Depends on where you want to import them and how. If you mean dynamically like what masaanli was trying to achieve you can use the same solution as I posted above I've tested it by refactoring styles2.scss to be styles2.less and the contents to be:

@green : green;

h1 {
  color: @green;
}

This will load correctly but it's prune to the drawbacks I have posted above.

The priority in which it applies the css is as follows:

Component -> angular-cli styles [] -> index.html

This screenshot might clarify it for you:
image

Blue is the component styles, red is styles.css being loaded from angular-cli.json and finally green is a link tag from index.html.

Hope this clarifies your queries.

@kujma10-zz
Copy link

kujma10-zz commented Jul 26, 2017

I have used solution from this issue. However when isDevMode is undefined, it loads both scss files. When I console.log it only goes to the second block. Any idea why this happens?

    if(isDevMode()) {
      require("style-loader!./../styles2.scss");
    } else {
      require("style-loader!./../styles.scss");
    }

@yasiryc
Copy link

yasiryc commented Jul 26, 2017

@kujma10 , did you import isDevMode from @angular/core? Also ensure that you are doing development build and not production.

@sandangel
Copy link

sandangel commented Oct 12, 2017

I have the same question but different use case. My deploy page is https://username.gitlab.io/admin/ with /admin suffix while development mode is just localhost. I had to use route hash strategy because gitlab not support serve index.html with all route, then I had to create 2 same scss file in my home component, home.component.scss and home.prod.component.scss because the img url in dev mode is differ with prod mode (/assets/img/img.jpg and /admin/assets/img/img.jpg). I tried dynamic load with environment.production but when I run ng serve, it has an error not found home.component.scss. but it work in ng serve --aot and ng build -prod.

@angular/cli: 1.5.0-beta.4
node: 8.7.0
os: linux x64
@angular/animations: 5.0.0-rc.1
@angular/cdk: 2.0.0-beta.12
@angular/cli: 1.5.0-beta.4
@angular/common: 5.0.0-rc.1
@angular/compiler: 5.0.0-rc.1
@angular/core: 5.0.0-rc.1
@angular/flex-layout: 2.0.0-beta.9-c3c7151
@angular/forms: 5.0.0-rc.1
@angular/http: 5.0.0-rc.1
@angular/material: 2.0.0-beta.12
@angular/platform-browser: 5.0.0-rc.1
@angular/platform-browser-dynamic: 5.0.0-rc.1
@angular/platform-server: 5.0.0-rc.1
@angular/router: 5.0.0-rc.1
@angular/compiler-cli: 5.0.0-rc.1
@angular/language-service: 5.0.0-rc.1
typescript: 2.5.3

@andre-f-paggi
Copy link

Any other workarounds or a solution for this?

@matiishyn
Copy link

Trying to load SCSS file in my TS files gives me an error while build:
Cannot find name 'require'

@mousavidev
Copy link

mousavidev commented Jan 26, 2018

I am looking for such a solution.

theme.ts

interface Theme {
  url: string;
  rel: string;
  media: string;
}

app.component.ts

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  theme: AppComponentThemeProvider
})
export class AppComponent {
  constructor() {
  }
}

app.component.theme.ts

export class AppComponentThemeProvider {
  constructor(@Inject(LOCALE_ID) private localeId) {
  }

  getThemes() {
    let themes = [<Theme>{url:'./app.component.css'}];

    if(this.localeId == 'fa-IR') {
      themes.push(<Theme>{url: './app.component.rtl.css'})
    }

    return themes;
  }
}

@ghiscoding
Copy link

@matiishyn
You just need to add a Type for the require method, you can also add it to your Global Types if you have (maybe global.d.ts) or directly in your code like so

declare function require(name: string): any;
require('style-loader!./custom-styles.scss');

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 9, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests