Angular.js: Support CSS on components (backport from ng2)

Created on 11 Aug 2016  路  17Comments  路  Source: angular/angular.js

Do you want to request a _feature_ or report a _bug_?

Feature.


Has there been any discussion about backporting Angular 2's component CSS support (with emulated view encapsulation) support to work with Angular 1 components?

While projects like angular-css provide similar functionality, there does not appear to be a solution for Angular 1.x applications that provides true view-encapsulation.

IMO, this is one of the most useful (and under-appreciated) features of Angular 2. This feature would be an enormous benefit to existing Angular 1.x applications, and could provide another tool for owners of legacy applications to gradually adopt Angular 2 (while improving the modularity of their existing applications).


If this has been discussed before, please feel free to close as a duplicate -- I couldn't find any previous discussion on this topic.

$compile moderate investigation public api inconvenient feature

Most helpful comment

since the #15805 was closed adding it here also:

Do you want to request a feature or report a bug?

feature

What is the current behavior?

angularjs does not include css styles loader like the html template loader as part of the components definitions.

lets say we have a component:

module.component('myComponent', {
    template: '<some-html></some-html>'    // or we can use templateUrl: 'path/to/template.html'
}

What is the expected behavior?

I would love to add something like:

module.component('myComponent', {
    template: '<some-html></some-html>' ,   // or we can use templateUrl: 'path/to/template.html'
   // the addion
    styles: '.some-css { color: green;}' // or styleUrls: ['path/to/file.css', 'another/path/to/file.css']
}

What is the motivation / use case for changing the behavior?

  1. help developers or designers using the platform to use a structural styles.
  2. the final destination is to encapsulate the styles within a component.
  3. bigger apps often using one or few huge css files, the dynamic css style will help in performance since the majority of rules will not load at all only the relevant rules.
  4. get closer to the angular 2.x syntax - which will make the transition from angularjs to angular more natural.

Other information (e.g. stacktraces, related issues, suggestions how to fix)

https://github.com/angular/angular.js/pull/15799

All 17 comments

That would be a nice addition indeed. I am not sure if this is reasonably possible in Angular 1.x.

Note that Angular 2 has its own parsers for HTML and CSS (among other things), which gives it the ability to do much more advanced transformations. (And it also has offline compiling capabilities, which gives it the option to get rid of the bloat at runtime.)

Adding all that to Angular 1 would considerably increase the size of the framework and the internal complexity (as if $compile is not complicated enough already :stuck_out_tongue:).

But it is a nice feature indeed. Putting it in the backlog, but don't hold your breath :stuck_out_tongue:

I would think this would be doable by creating an external module that has an extensibility hook into $compile (maybe making it exclusive to components), then adding the module as a dependency. Such a module would probably need to use the same parsing mechanism from @angular/compiler though for parity & for seamless upgrading (compiling appears to be a simple matter, as seen here. I don't think HTML parsing would be necessary, since this request sounds like it refers more to the style option of the component decorator in Angular 2.

Combined with @angular/upgrade, this may be a doable task, although if one is going the upgrade path, I'm not sure how much value there is in duplicating logic here (it would increase the JS served to bootstrap the app). I get the impression that Angular 1's core feature set is mostly frozen due to the amount of resources shifted to Angular 2 & to create stability due to a greater focus on getting users to Angular 2, although I may be mistaken. In addition, there is the challenge of making sure it is in sync with whatever breaking changes happen in Angular 2, which there are some still yet to happen in the release candidates.

If you wanted to prototype this for interest, I actually don't think you need any extensibility hooks at all - it can be done entirely with overriding .directive and .component in a .config call.

The class and/or attribute used to emulated Shadow DOM can be added by modifying the compile function, and the addition/removal of stylesheets can be easily done in link/$destroy.

I had prototyped a project to dynamically add/remove stylesheets so that the styles only exist when a directive is actually present on the page (perf testing), but it's not that big of a stretch to expand that to this, would just need to drop a CSS parser into it.

I'm actually pretty fascinated that Angular 2 has its own (large and fairly complex) CSS parser.

I wonder if the ng2 folks have ever considered pulling it out into a standalone library...

I'd like to discuss a simple but seemingly good enough solution for this issue. What if components had a cssClassPrefix setting? With this setting set, every class and ng-class directive in the component's _template_ should work a bit differently: 1) prefix every class name, 2) understand some escaping syntax for 'global' class names.

E.g. this markup

<div class="foo bar @baz">...</div>

would become the following DOM

<div class="my-component--foo my-component--bar baz">...</div>

It's important that this setting should work on the _template_ _level_ and shouldn't affect any nested templates. For this to be possible, the class and ng-class directives need to understand somehow in which template they're located and be able to obtain the _settings_ associated with this template. It's something that directives can't do in the current version of AngularJS.

If we had this, the rest could be done just with LESS and its & syntax or rather with a simple custom PostCSS-based preprocessor. E.g. we can have custom syntax like:

@component my-component {
  foo { color: black; }
  bar { color: white; }
}

compiled to

my-component--foo { color: black; }
my-component--foo { color: white; }

Component names seem to be good enough prefixes, however it's totally easy to make class names even more unique by passing them through some function used by both AngularJS and the preprocessor.

@thorn0 That defeats the point though, no? You would need to use said prefix in all of your CSS, so you may as well just have written the prefixed className to begin with. You would also need to make sure that your prefix itself is unique between components, otherwise you again risk collisions. This also gives up a different optimization wherein the styles for a component are only present on the page if at least one component is currently present.

I have not circled back to this yet, but one idea to keep the impact of this change down would actually be to use the browser's own CSS parser for the CSS modifications, but that potentially means that any styleUrl styles that aren't present in the local cache would need to be present on the same domain to be fetched (or have CORS enabled), but that's no different than templateUrl I suppose.

@dcherman For a component, I'd have to write the prefix only twice: one time in its settings and one time in the LESS file. As for uniqueness, component names are unique. Why not use them as prefixes?

Instead of LESS, a simple custom build-time transformation can be easily created using a framework like PostCSS. Moreover, for something as simple as prefixing, I doubt we even need a parser. A good regexp may suffice, so the code might turn out to be really tiny and usable for a run-time transformation. (Yet I don't really think run-time transformations and optimizations are that valuable. For production, everyone uses bundling anyway. If templateUrl got removed from Angular, I wouldn't be sad at all.)

since the #15805 was closed adding it here also:

Do you want to request a feature or report a bug?

feature

What is the current behavior?

angularjs does not include css styles loader like the html template loader as part of the components definitions.

lets say we have a component:

module.component('myComponent', {
    template: '<some-html></some-html>'    // or we can use templateUrl: 'path/to/template.html'
}

What is the expected behavior?

I would love to add something like:

module.component('myComponent', {
    template: '<some-html></some-html>' ,   // or we can use templateUrl: 'path/to/template.html'
   // the addion
    styles: '.some-css { color: green;}' // or styleUrls: ['path/to/file.css', 'another/path/to/file.css']
}

What is the motivation / use case for changing the behavior?

  1. help developers or designers using the platform to use a structural styles.
  2. the final destination is to encapsulate the styles within a component.
  3. bigger apps often using one or few huge css files, the dynamic css style will help in performance since the majority of rules will not load at all only the relevant rules.
  4. get closer to the angular 2.x syntax - which will make the transition from angularjs to angular more natural.

Other information (e.g. stacktraces, related issues, suggestions how to fix)

https://github.com/angular/angular.js/pull/15799

+1

+1

+1

+1

Yes please!

I tried to play with the approach described in my comment. However, hacking class and ng-class directives, I faced problems due to #4383.

If anyone is curious to see it, a proof of concept can be found here: http://jsbin.com/timageb/edit?html,css,js,output

It might be a monstrous hack, but its potential for maintainability improvement is huge.

+1

+1

+1

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jtorbicki picture jtorbicki  路  3Comments

brijesh1ec picture brijesh1ec  路  3Comments

jetta20162 picture jetta20162  路  3Comments

tdumitrescu picture tdumitrescu  路  3Comments

nosideeffects picture nosideeffects  路  3Comments