Question
What is the proper/accepted way to use separate stylesheets for the various views my application uses?
Currently I'm placing a link element in the view/partial's html at the top but I've been told this is bad practice even though all modern browsers support it but I can see why it's frowned upon.
The other possibility is placing the separate stylesheets in my index.html's
head
but I would like it to only load the stylesheet if its view is being
loaded in the name of performance.
Is this bad practice since styling won't take effect until after the css is loaded form the server, leading to a quick flash of unformatted content in a slow browser? I have yet to witness this although I'm testing it locally.
Is there a way to load the CSS through the object passed to Angular's
$routeProvider.when
?
Answer
I know this question is old now, but after doing a ton of research on various solutions to this problem, I think I may have come up with a better solution.
UPDATE 1: Since posting this answer, I have added all of this code to a simple service that I have posted to GitHub. The repo is located here. Feel free to check it out for more info.
UPDATE 2: This answer is great if all you need is a lightweight solution for pulling in stylesheets for your routes. If you want a more complete solution for managing on-demand stylesheets throughout your application, you may want to checkout Door3's AngularCSS project. It provides much more fine- grained functionality.
In case anyone in the future is interested, here's what I came up with:
1. Create a custom directive for the<head>
element:
app.directive('head', ['$rootScope','$compile',
function($rootScope, $compile){
return {
restrict: 'E',
link: function(scope, elem){
var html = '<link rel="stylesheet" ng-repeat="(routeCtrl, cssUrl) in routeStyles" ng-href="{{cssUrl}}" />';
elem.append($compile(html)(scope));
scope.routeStyles = {};
$rootScope.$on('$routeChangeStart', function (e, next, current) {
if(current && current.$$route && current.$$route.css){
if(!angular.isArray(current.$$route.css)){
current.$$route.css = [current.$$route.css];
}
angular.forEach(current.$$route.css, function(sheet){
delete scope.routeStyles[sheet];
});
}
if(next && next.$$route && next.$$route.css){
if(!angular.isArray(next.$$route.css)){
next.$$route.css = [next.$$route.css];
}
angular.forEach(next.$$route.css, function(sheet){
scope.routeStyles[sheet] = sheet;
});
}
});
}
};
}
]);
This directive does the following things:
- It compiles (using
$compile
) an html string that creates a set of<link />
tags for every item in thescope.routeStyles
object usingng-repeat
andng-href
. - It appends that compiled set of
<link />
elements to the<head>
tag. - It then uses the
$rootScope
to listen for'$routeChangeStart'
events. For every'$routeChangeStart'
event, it grabs the "current"$$route
object (the route that the user is about to leave) and removes its partial-specific css file(s) from the<head>
tag. It also grabs the "next"$$route
object (the route that the user is about to go to) and adds any of its partial-specific css file(s) to the<head>
tag. - And the
ng-repeat
part of the compiled<link />
tag handles all of the adding and removing of the page-specific stylesheets based on what gets added to or removed from thescope.routeStyles
object.
Note: this requires that yourng-app
attribute is on the <html>
element, not on <body>
or anything inside of <html>
.
2. Specify which stylesheets belong to which routes using
the$routeProvider
:
app.config(['$routeProvider', function($routeProvider){
$routeProvider
.when('/some/route/1', {
templateUrl: 'partials/partial1.html',
controller: 'Partial1Ctrl',
css: 'css/partial1.css'
})
.when('/some/route/2', {
templateUrl: 'partials/partial2.html',
controller: 'Partial2Ctrl'
})
.when('/some/route/3', {
templateUrl: 'partials/partial3.html',
controller: 'Partial3Ctrl',
css: ['css/partial3_1.css','css/partial3_2.css']
})
}]);
This config adds a custom css
property to the object that is used to setup
each page's route. That object gets passed to each '$routeChangeStart'
event
as .$$route
. So when listening to the '$routeChangeStart'
event, we can
grab the css
property that we specified and append/remove those <link />
tags as needed. Note that specifying a css
property on the route is
completely optional, as it was omitted from the '/some/route/2'
example. If
the route doesn't have a css
property, the <head>
directive will simply do
nothing for that route. Note also that you can even have multiple page-
specific stylesheets per route, as in the '/some/route/3'
example above,
where the css
property is an array of relative paths to the stylesheets
needed for that route.
3. You're done Those two things setup everything that was needed and it does it, in my opinion, with the cleanest code possible.