Angular-cli: Q: How to use bootstrap-sass with @extend and mixins

Created on 1 Jul 2016  Â·  33Comments  Â·  Source: angular/angular-cli

I tried to ask this on Stackoverflow, but did not get any response yet. My question:

I'm trying to use Twitter Bootstrap with Angular 2, SCSS and the scaffold generated by angular-cli

I want to apply the best practice to not litter my markup with Bootstrap-specific CSS classes. Instead I want to apply/use them via @extend or as mixins:

.mybutton {
   @extend .btn;
}

My problem:

  • The SASS compiler throws an error, because it does not know about .btn
  • If I add @import "bootstrap"; to my .scss-File, it will render the full Bootstrap-CSS in every component css

Any clues for me how this can be matched with the Angular 2 / Angular-CLI approach of handling styles?

What I've done so far

  1. add @import "bootstrap"; to my app.component.scss
  2. Apply ViewEncapsulation.None to the AppComponent, so Angular 2 does not apply Shadow DOM emulation and the Bootstrap CSS applies to the whole application

    @Component({
       encapsulation: ViewEncapsulation.None
    })
    
  3. Include the following block into angular-cli-build.js, so relative @import will work

    sassCompiler: {
     cacheExclude: [/\/_[^\/]+$/],
     includePaths: [ 'node_modules/bootstrap-sass/assets/stylesheets' ]
    },
    vendorNpmFiles: [
       ...
       'bootstrap-sass/assets/**/*'           
    ]
    
RFC / discussion / question

Most helpful comment

@spongessuck There are _two_ issues here, the simpler of these was the explicit imports, to which @zurie was referring :

If anything we need a global mixin file we can put variables and mixins that will be available to all components but not need reimporting over and over.

But yes, the more complicated is the issue of bloat. However, as I said initially, mixins do not contribute anything to css. And Bootstrap separates the mixins from rules. So, if you import:

@import '~bootstrap/scss/functions';
@import '~bootstrap/scss/variables';
@import '~bootstrap/scss/mixins';

instead of:

@import '~bootstrap/scss/bootstrap';

you will get no css generated (unless you actually use a mixin, and then you get what you used). I just tried it to confirm.
On the other hand, if you want to _extend_ a _rule_, you have to _import_ the file containing the rule, and that will, of course, pull in that rule (which we want) and any others (which we don't want). So, if you want encapsulation without bloat, prefer mixins. And maybe write your own if the the vendor doesn't provide one. I think. As I said, I'm still puzzling through this.

All 33 comments

Did you find any solution for that?

@cwagner22 Unfortunately not. Currently I _do litter_ my HTML code with foreign Bootstrap classes. Seems to be a niche problem I have here.

Tbh I'm not really sure how to reconcile this with the modular approach that encourages shadow DOM emulation.

Anybody find a solution?

Yes, you can do it like explained in the aswer at stackoverflow:
http://stackoverflow.com/questions/38071790/angular2-how-to-use-bootstrap-sass-with-extend-and-mixins-in-angular-cli

So basicly: just import the buttons part of bootstrap in your component to get it compiled.
And include the complete bootstrap.css in your angular-cli.json.

why not just import bootstrap into the root styles.scss file that is inlined on the index page? then you don't need to tweak encapsulation

I Tryed all this things... but he still dont find the mixins in my IDE (PhpStorm) Compiling is no problem. but it sucks that i dont got autocomplete in PhpStorm...

Looks like you have an answer, also the documentation for bootstrap is improving to include bootstrap-sass with this PR: #4115

The problem isn't importing bootstrap into an app; the problem is described by the OP:

If I add @import "bootstrap"; to my .scss-File, it will render the full Bootstrap-CSS in every component css.

This means if you don't want a new copy of bootstrap included with every component you need its variables/mixins for, you need to define bootstrap-dependent parts of your component styles in the global stylesheet.

Ideally, the bootstrap SCSS would be imported once globally, and then its variables/mixins would be available to any component without having to re-import the whole thing.

@spongessuck You nailed it. I always wondered what's so special about my wish to keep my code clean from Bootstrap classes that nobody else stumbles over it. Seems that most people are happy with directly using and assign Bootstrap. I would like rather to follow "Please stop embedding Bootstrap classes in your HTML!"

@bentolor I have no problem with using BS classes in my templates, and that works fine when it's included globally, but I'm still interested in being able to @extend bootstrap classes and, in particular, use bootstrap breakpoint mixins in my component stylesheets without hard-coding media queries and, more importantly, without re-importing a new copy of bootstrap for every component.

@bentolor @spongessuck We can import e.g. bootstrap-grid.scss in styles.scss (root) and use the mixins in this root file only. By using /deep/ we can make classes available to child modules/components. However this is no desirable approach because we then lose the ability to separate our css classes by components (at least for those classes that need mixins).

Another approach is to import only the mixins e.g.:

@import '../../node_modules/bootstrap/scss/_variables.scss';
@import '../../node_modules/bootstrap/scss/_mixins.scss';

:host {
  @include make-row();
}

This way you only have to include the grid in your root scss file.

Do you guys have any other updates on this issue?

Do you guys have any other updates on this issue?

@ArtworkAD Not sure what you mean; having to re-import these bootstrap pieces over and over is still a problem.

@spongessuck I mean that by importing just the mixins you will not have multiple copies of bootstrap css.

@ArtworkAD obviously it's better than importing the whole thing, but there's still duplication.

What would have to be smarter in order to only include imported styles once- Sass or the Webpack config? Something else? Do imports in component stylesheets currently get processed for view encapsulation?

EDIT
Actually, it seems like just importing the pieces doesn't increase the bundle size, at least when ng serveing.

EDIT2
They are impacting bundle size, but quite minimally (as opposed to the whole bootstrap scss, which is ~182kB)

@Brocco why did you close this issue? It is a real problem that should be reopened and fixed.
Having an ugly work around that is not maintainable is not a solution.

I dont believe this issue that is so obvious has not been solved yet !! all devs who used this starter never heard of Rule#1 in development Don't Repeat Yourself !! How on earth were you including General Sass mixins and variables in every component ?!

guys? anybody? A little hint in angular-cli would also help.

I currently moved on to VueJS and never found a good solution with Angular CLI.

AFAIK the current Angular CLI now also uses Webpack2 which _can_ make a very good job in removing unnecessary duplicates in the bundled resources. VueJS i.e. uses by default _unscoped_ CSS while Angular 2 uses _scoped_ CSS. The later requires duplication of all declarations as they are limited to the scope.

Honestly – I could live with repeating imports if it was not duplicating all the imported CSS for every component as well.

Any news on this?

I would love to use the bootstrap mixins while using the CSS encapsulation and without having to import the same files over and over.

@Nkmol @Brocco me too

@bentolor Don't you find the same problem with VueJS? I'm using vue components and I'm having the same issue.

I include the bootstrap sass file in main.js, which allows components to use bootstrap CSS but not any bootstrap mixins.

I have a couple of options that might work for folks who would like to utilize bootstraps mixins for more semantic markup.

_Please note that both options are assuming you have installed_
npm install [email protected]

Option 1:
Don't use the style urls in your decorator and import your components styles into styles.scss
I usually have a partial named _base.scss that imports my global and vendor styles for example:

@import "variables"; @import "mixins"; @import "bootstrap";

Then in your styles.scss you would import the base partial and then your components
`@import "base";

// add your components here
@import "../app/nav-bar/nav-bar.component";`

Option 2:
Use the style urls in your decorator and Import each dependancy into your component. I would not recommend this as it's not very DRY. But here is an example. Importing the following into your component will allow you to use the grid mixins for more semantic markup:

my-component.scss
@import "../../../node_modules/bootstrap/scss/variables";
@import "../../../node_modules/bootstrap/scss/mixins";
@import "../../../node_modules/bootstrap/scss/grid";`

Keep in mind when using option 1 you have to manually import your components styles to your main style sheet. And with option 2 you have to import bootstraps variables, mixins and grid into each of your components. At the moment I use option 1 until I figure out a more elegant solution.

Hope this helps

@Brocco I would agree with some of the folks regarding this issue and consider reopening it. Providing a way to utilize the mixins would be great.

Any updates about this?

If anything we need a global mixin file we can put variables and mixins that will be available to all components but not need reimporting over and over.

@zurie re: "we need a global mixin file": It might be nice, but is it a need? You can get that with minimal repetition by having a _common.scss that imports the variable and mixin partials you want, then import that where you need it. So its just one line, and you opt-in.

I'm still puzzling through all of this, but it seems to me there's no problem (other than build time) if we want to import a partial that has no rules, just mixins, variables, and functions. They don't generate any css (unless used). The problem is, we want to use something for which bootstrap hasn't provided a mixin, (and/or its in a file that also contains rules). What we want (I think) is an import-for-extend-only. This would be a "safe" import, that would only contribute mixins, functions, and variables. In this context, @extend would not add to rules, but would need to write new ones, based on the imported scss.

However, this would be a sass feature, not angular-cli. Barring something like that getting added (or maybe it exists and I just don't know about it?), I guess we need to use some variation of that suggested by @jessepinuelas. Either that, or look at what bootstrap does, and write your own mixins, as needed.

@mconner I think you're misunderstanding the problem; the issue isn't that these mixins/whatever need to be explicitly imported to be used. The problem is that importing them into your component styles puts a copy of the imported stuff in your component style, leading to pointlessly bloated style bundles.

Bootstrap mixins are global, but there's no way to include them in an encapsulated component without encapsulating those imports as well. @jessepinuelas's solution might work, but it bypasses the encapsulation.

@spongessuck There are _two_ issues here, the simpler of these was the explicit imports, to which @zurie was referring :

If anything we need a global mixin file we can put variables and mixins that will be available to all components but not need reimporting over and over.

But yes, the more complicated is the issue of bloat. However, as I said initially, mixins do not contribute anything to css. And Bootstrap separates the mixins from rules. So, if you import:

@import '~bootstrap/scss/functions';
@import '~bootstrap/scss/variables';
@import '~bootstrap/scss/mixins';

instead of:

@import '~bootstrap/scss/bootstrap';

you will get no css generated (unless you actually use a mixin, and then you get what you used). I just tried it to confirm.
On the other hand, if you want to _extend_ a _rule_, you have to _import_ the file containing the rule, and that will, of course, pull in that rule (which we want) and any others (which we don't want). So, if you want encapsulation without bloat, prefer mixins. And maybe write your own if the the vendor doesn't provide one. I think. As I said, I'm still puzzling through this.

Are there any more developments, or ideas, on how to import the most minimal amount to use a bootstrap mixin within a component's .sass or .scss file?

@mconner's solution is the best I've come across after much searching. I went further and created my own _mixins.scss file where I put the imports, and then in my components I just do:
@import "mixins";
whenever I need any of them.

@jkossis @luisaceituno @bentolor

We have faced similar issue recently. Using vuejs it is super convenient to use bootstrap mixins and class definitions for our own classes. The problem we have faced is that the resulting CSS was including bootstrap code for every component (only on dev)

Basically solution for us was to artificially create "import once" functionality during the SASS processing. We did the following:

  • Split our global scss into two parts: First, that includes the bootstrap and some global styles and second that includes the first one and have only one "split" class that we name uniquely
  • Add intermediate webpack loader after the SCSS task to remove all css before the "split" class
  • Use the second sass file in all components and use the first only once in the Main module.

Hope that helps.

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

Was this page helpful?
0 / 5 - 0 ratings