Ionic2 - Styles (css) per component instead of global

Hi, I am a bit curious about the default behaviour of Ionic 2 regarding generation of css files from component scss files. I don’t know if this is the right place to ask, but anyway… :slight_smile:

From what I see, the gulp sass task generate a single css file from the /app/theme/app.core.scss file (and other scss files from the /app/theme/ folder, where app.core.scss import each component file.

This means that a component scss file will pretty much work as a global css file, so it can change the styles of other (unrelated) components inadvertedly.

Reading the Angular 2 specification about component styles https://angular.io/docs/ts/latest/guide/component-styles.html, they refer to components as independent pieces of code, including the template (html) and style (css), and I see their point:

This is a big improvement in modularity compared to how CSS traditionally works:

  1. We can use the CSS class names and selectors that make the most sense in the context of each component.
  1. Class names and selectors are local to the component and won’t collide with classes and selectors used elsewhere in the application.
  1. Our component’s styles cannot be changed by changes to styles elsewhere in the application.
  1. We can co-locate the CSS code of each component with the TypeScript and HTML code of the component, which leads to a neat and tidy project structure.
  1. We can change or remove component CSS code in the future without trawling through the whole application to see where else it may have been used. We just look at the component we’re in.

Workaround

**
For now, I just changed the way the gulpfile.js generates css from:

gulp.task('sass', buildSass);

to:

gulp.task('sass', function() {
	buildSass(); // generate files from /app/theme/
	buildSass({
		src: 'app/pages/**/*.scss',
		dest: 'www/build/css/components',
		sassOptions: {}
	}); 
});

So I can use a component specific style using styleUrls in the @Component decorator:

In a component .ts file

/app/pages/my-component/my-component.ts

I use

styleUrls: ['build/css/components/my-component/my-component.css']

Updated

Changed the code in the gulpfile with:

var customBuildSass = function () {
	buildSass({
		src: 'app/theme/app.+(ios|md|wp).scss',
		dest: 'www/build/css',
		sassOptions: {
			includePaths: [
				'node_modules/ionic-angular',
				'node_modules/ionicons/dist/scss'
			]
		}
	}); // generate files from /app/theme/ (original)
	buildSass({
		src: 'app/pages/**/*.scss',
		dest: 'www/build/css/components',
		sassOptions: {}
	}); // generate files from /app/pages/ (customized)
};

gulp.task('sass', customBuildSass);

The previous code works the first time, but if gulp is watching for changes, only the components scss files will be built again, but the global ones will not, and you will need to execute the gulp build task again or execute ionic serve again. See why here.


I would like to know if there is a good/better reason for using component styles as global styles (like Ionic 2 does now), or if this behaviour (merging in 1 css file all component styles and making the styles global) is just a matter of choice or something like that (and if this default behaviour may change in the future to use styles per component).

(Of course, I think that global styles would still be used (massively), but they could be, for example, in the /app/theme/ folder (and subfolders), instead of being in the components’ scss files)

2 Likes

So this was done before the styles property was actually created to be honest. With the current setup of the framework, Im not sure how much of a benefit the framework would gain for this, but I could see this being very helpful for apps users make. We’ll investigate and see what the outcome is after we finish up our tooling refactor…

1 Like

Thanks for the reply. It’s fine, after all I just need to change the gulpfile. Even so, this is a good approach towards the development of independent components, avoiding code/style with global scope. Hoping that in the future this default behaviour will change :smile:

Thanks for sharing this! I’ve wondered why CSS was global as well:-)

Glad to know :smile:

If you changed your gulpfile with the code I posted, instead use this code:

var customBuildSass = function () {
	buildSass({
		src: 'app/theme/app.+(ios|md|wp).scss',
		dest: 'www/build/css',
		sassOptions: {
			includePaths: [
				'node_modules/ionic-angular',
				'node_modules/ionicons/dist/scss'
			]
		}
	}); // generate files from /app/theme/ (original)
	buildSass({
		src: 'app/pages/**/*.scss',
		dest: 'www/build/css/components',
		sassOptions: {}
	}); // generate files from /app/pages/ (customized)
};

gulp.task('sass', customBuildSass);

Reason

The code from the first post works the first time, but if gulp is watching for changes, only the components scss files will be built again, but the global ones will not, and you will need to execute the gulp build task again or execute ionic serve again.

This happens because the object with options that is passed as argument will override the default options, and because the default options is an object outside the exported function, this will persist for subsequent calls, even without options passed as arguments.

That is, when you customize once, you need to use the default options explicitly the next time if you want to use them (at least the ones you changed with the custom options).

The default options are changed in the line with options = assign(defaultOptions, options); in the file that exports the ionic gulp task for sass https://github.com/driftyco/ionic-gulp-tasks/blob/master/sass-build/index.js.

1 Like

Thanks so much for this! It helped greatly. I wanted component-based styling and now I have it, thanks!

Has anyone else had trouble with the styles not being available for app builds? ionic emulate and app builds can’t find build/css/pages/mycustomcss

Have you seen if your paths are correct? Are your css files being generated into www/build/css/components?

If instead you want them generated into:

www/build/css/pages/mycustompage/mycustompage.css

then change the gulpfile sass task to

dest: 'www/build/css/pages'

and your component styleUrls to:

build/css/pages/mycustompage/mycustompage.css

(See that I used pages in the path instead of components)

For me I could use either ionic serve and ionic upload and both of them worked. I think in your case the problem is probably the path. Just take a look where your css files are being generated and if your component styleUrls refer to that location.

EDIT:

All below is correct code,
my problem was that one of my css files (happened to be the very first one that gets loaded) only had this in it:

.page1 {

}

It had no rules!
I’ve been bitten by this exact problem before in a Polymer app. Be careful everyone! If you ever use a css file in a framework, you Must have at least one rule in it. I fixed it by putting display: block; and that was that. Thanks for your time @lucasbasquerotto


Hey thanks for replying! I appreciate it. So my app gets the CSS files just fine when I use Ionic serve, but not on device builds. So I have pages and components directories I’d like to build CSS from. Here’s my folder structure (irrelevant folders not included):

app
    - pages
          - page1
                 - page1.html
                 - page1.scss
                 - page1.ts
    - components
          - component1
                 - component1.html
                 - component1.scss
                 - component1.ts

    - app.ts

And here is my customBuildSass function:

var customBuildSass = function () {
  buildSass({
    src: 'app/theme/app.+(ios|md|wp).scss',
    dest: 'www/build/css',
    sassOptions: {
      includePaths: [
        'node_modules/ionic-angular',
        'node_modules/ionicons/dist/scss'
      ]
    }
  }); // generate files from /app/theme/ (original)
  buildSass({
    src: 'app/pages/**/*.scss',
    dest: 'www/build/css/pages',
    sassOptions: {}
  }); // generate files from /app/pages/ (customized)
  buildSass({
    src: 'app/components/**/*.scss',
    dest: 'www/build/css/components',
    sassOptions: {}
  }); // generate files from /app/components/ (customized)
};


gulp.task('sass', customBuildSass);

I’m bringing in my styles with:

styleUrls: ['build/css/pages/page1/page1.css']

I checked in my platforms/ios/www/build/css/
and I have both components and pages folders in there with the correct sub-folders and css files.

Any ideas?

Edit

I see that you solved your problem, that is good to know :smile: That only happens when in native platform?

Original

That’s weird. From what I see it should work. To help identify the problem:

  1. Try to create one simpler project only with the /app/pages folder (without the /app/components folder) with only one component (besides the one in app.ts) to see if you can simulate the issue.

  2. Execute ionic serve --lab to see if the styles are correct in ios, android (md) and windows phone (wp).

  3. Try to see if the css page is not loaded, or if it is loaded and just not applied. If the component can’t load the css, the component isn’t even shown. You can change the css file name in the styleUrls to a wrong one (so the css isn’t loaded) and see if the behaviour is the same (in this case, it wasn’t loaded before either) or another (so it was loaded before, and the problem is probably that it isn’t applied for some reason).

IMO, the problem isn’t (or at least shouldn’t be) related to the platform and if it is in browser or emulated, but who knows… Try the steps above to see if you can isolate the problem.

If the page/component isn’t even loaded, then I think the issue is most probably the path (although from what I see, your path is correct, so that would be strange).

If the component is loaded, but the css isn’t applied, then I think your css file is correctly added to the component, but the css isn’t being applied for some reason. Maybe your are applying the styles to a subcomponent and should use /deep/ for the css to work:

.my-component-class /deep/ .my-child-component-class

(Ex: ion-content /deep/ .item-inner)

(My scss syntax checker display error for /deep/, so I disabled the checker, after all I almost don’t use it for scss files.)

So first try to know if the css file is not loaded, or if it is loaded and just not applied.

Importing blank css files has caused hard to find errors in a web app I was building, It seems to be happening to me personally all the time loL. Point being, seems like it happens in both native and web environments.

@wswoodruff Is the cause of your problem because the .css file isn’t generated in those cases? When you execute ionic serve with an empty .scss file, the corresponding .css file is generated? In my case it is generated. If it isn’t for you, maybe it’s related to your gulpfile, or sass task, maybe some setting to not generate the file if there is no css rules in it. But I don’t know for sure, I couldn’t reproduce the problem… If I delete the generated css file manually after I run ionic serve, I get an exception: Failed to load build/css/components/my-component/my-component.css. Is this what happens in your case?