How to include view/partial specific styling in AngularJS
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 your ng-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.
Hope that helps someone else who might be struggling with this issue as much as I was.
Conditionally-rendering css in html head
You should use ng-href instead of href.
<link ng-repeat="stylesheet in stylesheets" ng-href="{{stylesheet.href}}" type="{{stylesheet.type}}" rel="stylesheet" />
Example
AngularJS : dynamic stylesheet link tag fires request too soon
I solved it by adding an ngIf
directive to the link
tag, so it's not rendered until filename
isn't falsy. Kinda dirty I know, but it actually works !
<link rel="stylesheet" data-ng-if="filename" data-ng-href="css/{{ filename }}.css" />
AngularJS setting a CSS file from API using JS
Let me answer your Question 2 first.
When an angular bootstraps, it will first traverse down the DOM nodes, then look for all the appropriate directives, evaluate them, compile and finally link them up. The reason why your second piece of code doesn't work is because your {{css}}
evaluates to nothing, since it does not have a proper scope
binded to.
What you can do is actually declare an ng-controller
at your <head>
level, and let this controller do its work. Something like this:
.controller('cssCtrl',['$scope','$http', function($scope,$http){
$http.get('/api/auth/test').
then(function(response) {
$scope.css = response.data.temp.css;
}, function(response) {
alert('Error retrieving css: ' + response);
});
}]
And in your html:
<head ng-controller="cssCtrl">
<link ng-attr-href="{{css}}" rel="stylesheet" type="text/css">
</head>
Here is a plnkr that demonstrates how.
ng-controller
will come in handy here as you do not really have to define a route
for it, per se. Note that your css will not be loaded until your angular bootstraps finishes and your cssCtrl
gets instantiated. There will be some delay in this though, and hence our first question, do we have a better way in doing this?
I would say if you have really conditional presentation logic based on the angular app itself, then use ng-if
and ng-class
. If you need more low level ones, use ng-style
.
If you are talking about really loading the entire new css stylesheet based on certain configuration/settings, then I would say, let the server (backend) handle this. The server will determine what kind of css should be loaded based on the configuration (maybe different regional apps have different css styles) and spawn its site. Your front end app (angular) will just need to listen to the server, and load without really digesting it.
Of course, this is not 100% the best way. If you really need a front-end-determined css, then go for it and design the app in that way! Only you know that use case of the app well. Maybe you can wrap the entire SPA into a MainCtrl
and MainView
, and let every configuration resolves first before the DOM is manipulated.
AngularJS $routeprovider Routing in Mutitab
Update
Version 1 (Without routing) Plunker Demo
It will redirect to a new view if you use #/jobs
syntax. Here is a complete working solution without routes:
Your code on plunker
is working because you didn't include ngRoute
in your code.
View in plunker demo
app.js
$scope.tabs = [
{ id : 'jobs', label : 'Jobs', templateUrl:'jobs-partial.html' },
{ id : 'invoices', label : 'Invoices',templateUrl: 'invoices-partial.html' },
{ id : 'payments', label : 'Payments',templateUrl: 'payments-partial.html' }
];
$scope.activeTab = $scope.tabs[0];
$scope.changeActiveTab = function (tab) {
$scope.activeTab = tab;
};
$scope.isActiveTab = function (tabId) {
return tabId === $scope.activeTab;
}
Inside index.html
<body ng-controller="TabsCtrl">
<ul class="nav nav-tabs" >
<li ng-class="{active: isActiveTab(tab.id)}" ng-repeat="tab in tabs">
<a href="" ng-click="changeActiveTab(tab.id)" data-toggle="tab">{{tab.label}} </a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade" ng-class="{'in active': isActiveTab(tab.id)}" ng-repeat="tab in tabs"
ng-include="tab.templateUrl">
</div>
</div>
</body>
Version 2 (With routing) Plunker Demo
index.html
<!DOCTYPE html>
<html ng-app="plunkerApp">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css" rel="stylesheet">
<script src="https://code.angularjs.org/1.4.8/angular.js"></script>
<script src="https://code.angularjs.org/1.4.8/angular-route.js"></script>
<script src="app.js"></script>
</head>
<body>
<div ng-view></div>
</body>
</html>
app.js
angular
.module('untitled4App', [
'ngRoute'
])
.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
$routeProvider.when('/jobs', {
templateUrl: '/views/jobs-partial.html',
controller: 'JobsCtrl'
}).when('/invoices', {
templateUrl: '/views/invoices-partial.html',
controller: 'InvoicesCtrl'
}).when('/payments', {
templateUrl: '/views/payments-partial.html',
controller: 'PaymentsCtrl'
});
// make this demo work in plunker
$locationProvider.html5Mode(false);
}])
.factory('tabsService', function () {
return {
tabs: function () {
return [
{id: 'jobs', label: 'Jobs'},
{id: 'invoices', label: 'Invoices'},
{id: 'payments', label: 'Payments'}
]
},
activeTab: '',
isActiveTab: function (tabId) {
return tabId === this.activeTab;
}
}
})
.controller('JobsCtrl', ['$scope', 'tabsService', function ($scope, tabsService) {
$scope.tabs = tabsService.tabs();
$scope.tabsService = tabsService;
tabsService.activeTab = $scope.tabs[0].id;
}])
.controller('InvoicesCtrl', ['$scope', 'tabsService', function ($scope, tabsService) {
$scope.tabs = tabsService.tabs();
$scope.tabsService = tabsService;
tabsService.activeTab = $scope.tabs[1].id;
}])
.controller('PaymentsCtrl', ['$scope', 'tabsService', function ($scope, tabsService) {
$scope.tabs = tabsService.tabs();
$scope.tabsService = tabsService;
tabsService.activeTab = $scope.tabs[2].id;
}]);
jobs.partial.html
<ul class="nav nav-tabs">
<li ng-class="{active: tabsService.isActiveTab(tab.id)}" ng-repeat="tab in tabs">
<a href="#/{{tab.id}}">{{tab.label}} </a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade in active">
Jobs
</div>
</div>
invoices-partial.html
<ul class="nav nav-tabs">
<li ng-class="{active: tabsService.isActiveTab(tab.id)}" ng-repeat="tab in tabs">
<a href="#/{{tab.id}}">{{tab.label}} </a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade in active">
Invoices
</div>
</div>
payments-partial.html
<ul class="nav nav-tabs">
<li ng-class="{active: tabsService.isActiveTab(tab.id)}" ng-repeat="tab in tabs">
<a href="#/{{tab.id}}">{{tab.label}} </a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade in active">
Payments
</div>
</div>
Angular JS not loading view
I would like to start with advising you to follow this official tutorial: it shows you the beginnings of how to build an app with AngularJS and views. The reason I would advise this is because there are quite a few basic elements that are missing from the script, mostly the <a>
tags. Maybe you can start from the tutorial and build your own code based on that.
Remarks about your code:
- Add all your script tags with javascript in your
<head>
. These will then in turn be run by a browser, making sure that all dependencies are met - You forgot a comma after "app" in your module initialization
- Most important, you should change your
<p>
tags to<a>
tags if you want views to work. Angular uses the<a>
-tags to make navigation to different views possible. Thehref
attribute of an<a>
tag is used for this. Check my code below and you can see that myhref
attributes are linked to the routeprovider in the when clause.
Below you will find your adjusted code. Hope it helps!
<!doctype html>
<html data-ng-app="app">
<head>
<meta charset="utf-8"/>
<title></title>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/bootstrap-responsive.css">
<link rel="stylesheet" type="text/css" href="css/bootstrap-extended.css">
<link rel="stylesheet" type="text/css" href="css/custom.css">
<!-- Java Script -->
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.min.js"> </script>
<script type="text/javascript" src="js/bootstrap.min.js"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.min.js"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular-route.js"></script>
<script>
var app = angular.module('app', ['ngRoute']);
app.config(function($routeProvider){
$routeProvider
.when('/',
{
controller: 'basicController',
templateUrl: 'partials/viewOne.html'
})
.when('/menu', {
controller: 'basicController',
templateUrl: 'partials/viewTwo.html'
})
.when('/map',
{
controller: 'basicController',
templateUrl: 'partials/viewThree.html'
})
.otherwise({ redirectTo: '/' });
});
app.controller('basicController', ['$scope', function ($scope) {
$scope.message = "Hello!";
}]);
</script>
</head>
<body>
<div id="container">
<nav class="col-xs-12">
<table id="navTable">
<tr>
<td><a href="#">view 1</a></td>
<td><a href="#/menu">view 2</a></td>
<td><a href="#/map">view 3</a></td>
</tr>
</table>
</nav>
<!-- View Placeholder -->
<article data-ng-view="" class="col-xs-6 col-xs-offset-3">
Views goes here...
</article>
<footer class="col-xs-12 navbar-fixed-bottom"></footer>
</div>
</body>
</html>
Related Topics
How to Reset Default Button Style in Firefox 4 +
Twitter Bootstrap 3 How to Activate Navbar-Collapse on Small Devices
iOS CSS Opacity + Visibility Transition
Bootstrap 4 Ie 11 Does Not Work
Oocss Separation of Container and Content
How to Center a List with Bootstrap 4
What This CSS Code Means (Font-Size Division)
Combined Multiple Classes into One CSS Rule
How to Remove the Blue Halo Glow from Jquery Mobile Input Elements That Receive Focus
Adding Custom CSS Tags to an Rmarkdown HTML Document
What Are Cross-Browser, Cross Platfom Web Safe Fonts
Blur Effect on the Entire Webpage
Linking to CSS Files from React Index
How to Get a Grid-Gap Between the Grid Item and the Container
How to Change Color of Bootstrap Progress Bar with Custom Color