No fancy webpack, simple Vue custom element with some global css and some inline css for overrides.
I would like to use some styling library, like from getbootstrap.com and have it change styles inside custom element.
https://jsfiddle.net/Deele/6xk1atrn/25/
<div class="btn bg-info">Zero</div>
<test-widget id="One"></test-widget>
<test-widget id="Two"></test-widget>
const TestWidget = Vue.defineCustomElement({
props: {
id: String
},
data: () => {
return {
message: 'Test'
}
},
emits: {},
template: `<div class="btn bg-info">{{id}} {{message}}</div>`,
styles: [`div { color: green; }`]
})
customElements.define('test-widget', TestWidget)
.bg-info {
background-color: red!important;
}
Was expecting divs inside rendered elements would be styled as buttons, but it does not work!?
From what I have found in the internet, it has something to do with Shadow DOM not inheriting any global styles.
Please, tell me if there is a solution to this approach? I would like to create small widgets for my website using Vue.js, but this hurdle creates fatal limitation.
Custom elements defined using the Vue API always use a shadow DOM, so they are isolated from the parent document and any global styles in the app.
So to make it happen, You can inject the bootstrap styles or any global style url's in the styles option by using #import statement.
Live Demo :
const TestWidget = Vue.defineCustomElement({
props: {
id: String
},
data: () => {
return {
message: 'Test'
}
},
template: `<div class="btn bg-info">{{id}} {{message}}</div>`,
styles: [`#import url("https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.2/css/bootstrap.css"); div { color: green; }`]
});
customElements.define('test-widget', TestWidget);
<script src="https://unpkg.com/vue#next"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.2/css/bootstrap.css"/>
<div class="btn bg-info">Zero</div>
<test-widget id="One"></test-widget>
<test-widget id="Two"></test-widget>
I upgraded my project from NextJS 10 to NextJS 12. Everything is working except for Storybook, which has no styles now.
I am using styled-jsx library to generate embedded css, using it as:
const SearchResult = ({ onClick, vehicle }: SearchResultProps): JSX.Element => {
return (
<div className="search-result" data-testid="search-result" onClick={onClick}>
<style jsx>{styles}</style>
...
</div>
);
};
It generates styles like class="jsx-2615582530 search-result". Before the update, it would also embed a style search-result.jsx-2615582530 into the generated storybook. Now, the jsx style names are there, yet the styles are gone.
I upgraded styled-jsx from 3.3.1 to 5.0.0 and NextJS to 12.0.3. Did not change any loaders or anything. My guess, webpack might have easily gotten upgraded.
My config:
const webpack = require('webpack');
module.exports = ({ config }) => {
// Fill in process.env on the client
config.plugins.push(
new webpack.DefinePlugin({
'process.serializedEnv': {},
})
);
// Sentry requires different packages for front and back end,
// but in storybook we know it's always client side
config.resolve.alias = {
'sentry-alias': '#sentry/browser',
'#/remoteConfig': './standardRemoteConfig',
};
config.module.rules.push({
test: /\.md$/,
loader: "raw-loader",
}),
config.externals = [...(config.externals || []), 'fs', 'net'];
config.resolve.extensions.push('.md');
config.resolve.fallback = {
"https": false,
"stream": false,
};
return config;
};
and main
module.exports = {
core: {
builder: 'webpack5',
},
stories: ['../stories/**/*.stories.tsx'],
addons: [
'#storybook/addon-actions',
'#storybook/addon-links',
'#storybook/addon-backgrounds',
'#storybook/addon-knobs',
'#storybook/addon-viewport',
'#storybook/addon-a11y',
'storybook-addon-paddings',
],
};
Further, if I include styles as <style>{styles}</style> without the jsx prop, they are included in the produced storybook.
Only, the text is displayed weird:
When jsx is there, <style /> is missing from the resulting markup, altogether.
Suggestions?
Newer styled-jsx required the following:
+import { StyleRegistry } from 'styled-jsx';
and
-export const decorators = [withPaddings, withRedux(), (story: Function) => <I18nextProvider i18n={i18n}>{story()}</I18nextProvider>]
+export const decorators = [(Story) => (
+ <StyleRegistry>
+ <Story />
+ </StyleRegistry>
+), withPaddings, withRedux(), (story: Function) => <I18nextProvider i18n={i18n}>{story()}</I18nextProvider>]
This yet again makes embedded jsx styles present.
I want to load a specific CSS file only when a user accesses the contact.html view on my AngularJS app/site. I found this answer which almost made sense to me How to include view/partial specific styling in AngularJS The accepted answer by charlietfl reads :
Could append a new stylesheet to head within $routeProvider. For
simplicity am using a string but could create new link element also,
or create a service for stylesheets
/* check if already exists first - note ID used on link element*/
/* could also track within scope object*/
if( !angular.element('link#myViewName').length){
angular.element('head').append('<link id="myViewName" href="myViewName.css" rel="stylesheet">');
}
Biggest benefit of prelaoding in page is any background images will
already exist, and less lieklyhood of FOUC
The problem is that I do not know exactly where in my $routeProvider to add the code. charlietfl mentions where to put it in a comment which reads
not if the when for the route hasn't been called. Can put this code in
controller callback of when within the routeProvider, or perhaps
within resolve callback which likely triggers sooner
I have modified my $routeProvider following the advice. I added a resolve in the object following .when('/contact'. Here is my code
angular.module('maxmythicApp', ['ngResponsiveImages'])
.config(function ($locationProvider, $routeProvider) {
$locationProvider.html5Mode(true);
$locationProvider.hashPrefix = '!';
$routeProvider
.when('/', {
templateUrl: '/views/design.html',
controller: 'MainCtrl'
})
.when('/design', {
templateUrl: '/views/design.html',
controller: 'MainCtrl'
})
.when('/about', {
templateUrl: '/views/about.html',
controller: 'MainCtrl'
})
.when('/contact', {
templateUrl: '/views/contact.html',
controller: 'MainCtrl',
resolve:
if( !angular.element('link#myViewName').length){
angular.element('head').append('<link id="myViewName" href="myViewName.css" rel="stylesheet">');
}
})
.otherwise({
redirectTo: '/'
});
});
Would this work?
I made a service for it.
Important part of the code :
var head = angular.element(document.querySelector('head')); // TO make the code IE < 8 compatible, include jQuery in your page and replace "angular.element(document.querySelector('head'))" by "angular.element('head')"
if(head.scope().injectedStylesheets === undefined)
{
head.scope().injectedStylesheets = [];
head.append($compile("<link data-ng-repeat='stylesheet in injectedStylesheets' data-ng-href='{{stylesheet.href}}' rel='stylesheet' />")(scope)); // Found here : http://stackoverflow.com/a/11913182/1662766
}
head.scope().injectedStylesheets.push({href: "/url/to/style.css"});
Full code in Github : https://github.com/Yappli/angular-css-injector)
UPDATED: Here is the solution to inject(load) a specific CSS using the $routeProvider.
The solution described below is an alternative to apply different classes and page title based on the route which could be used in other situations.
For each route I've created a new key called 'bodyClass' and 'title' (but you could called anything you like it) and it looks like this:
'use strict';
var myApp = angular.module('myApp', 'ngResource'])
.config(function ($routeProvider) {
myApp.siteName = 'My Cool App';
$routeProvider
.when('/home', {
title:'Home - ' + myApp.siteName,
bodyClass: 'home',
templateUrl: 'views/home.html',
controler: 'bmsHomeCtrl'
})
.when('/contact', {
title:'Contact - ' + myApp.siteName,
bodyClass: 'contact',
templateUrl: 'views/contact.html',
controler: 'bmsContactCtrl'
})
.otherwise({
redirectTo: '/home'
});
});
Then for each $routeChangeSuccess event I change the <title> of the page and also the class of the <body>.
myApp.run(['$location', '$rootScope', function($location, $rootScope) {
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
if (current.$$route) {
$rootScope.title = current.$$route.title;
$rootScope.bodyClass = current.$$route.bodyClass;
}
});
}]);
You put the code above on the same main app.js (for example) file.
On my index.html page, which renders the views, I have the following codes to pick up the title and class:
<title>{{ title }}</title>
<body class="{{ bodyClass }}">
So if i visit the home page of my application the title tag will be
<tittle> Home - My Cool App</tittle>
and the body tag will be
<body class="home">
It's working like a charm.
I know this solution doesn't load a CSS file, but you could put those styles inside a '.contact' class that is applied only when you hit a specific route.
Not sure if solves your problem but I hope that helps or at least point you on the right direction.
For a full solution I suggest using AngularCSS.
As you already know, in Angular we can include templates (structure) and controllers (behavior) in pages and components. AngularCSS enables the last missing piece: attaching stylesheets (presentation).
Routes example:
$routeProvider
.when('/page1', {
templateUrl: 'page1/page1.html',
controller: 'page1Ctrl',
/* Now you can bind css to routes */
css: 'page1/page1.css'
})
.when('/page2', {
templateUrl: 'page2/page2.html',
controller: 'page2Ctrl',
/* You can also enable features like bust cache, persist and preload */
css: {
href: 'page2/page2.css',
bustCache: true
}
})
.when('/page3', {
templateUrl: 'page3/page3.html',
controller: 'page3Ctrl',
/* This is how you can include multiple stylesheets */
css: ['page3/page3.css','page3/page3-2.css']
})
.when('/page4', {
templateUrl: 'page4/page4.html',
controller: 'page4Ctrl',
css: [
{
href: 'page4/page4.css',
persist: true
}, {
href: 'page4/page4.mobile.css',
/* Media Query support via window.matchMedia API
* This will only add the stylesheet if the breakpoint matches */
media: 'screen and (max-width : 768px)'
}, {
href: 'page4/page4.print.css',
media: 'print'
}
]
});
Directive example:
myApp.directive('myDirective', function () {
return {
restrict: 'E',
templateUrl: 'my-directive/my-directive.html',
css: 'my-directive/my-directive.css'
}
});
You can read more about AngularCSS here:
http://door3.com/insights/introducing-angularcss-css-demand-angularjs
I think the best/simplest answer is one I left here. Someone else asked the same question, so I came up with some simple code and a small github repo to handle this scenario.
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?
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 the scope.routeStyles object using ng-repeat and ng-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 the scope.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.
#tennisgent's solution is great. However, I think is a little limited.
Modularity and Encapsulation in Angular goes beyond routes. Based on the way the web is moving towards component-based development, it is important to apply this in directives as well.
As you already know, in Angular we can include templates (structure) and controllers (behavior) in pages and components. AngularCSS enables the last missing piece: attaching stylesheets (presentation).
For a full solution I suggest using AngularCSS.
Supports Angular's ngRoute, UI Router, directives, controllers and services.
Doesn't required to have ng-app in the <html> tag. This is important when you have multiple apps running on the same page
You can customize where the stylesheets are injected: head, body, custom selector, etc...
Supports preloading, persisting and cache busting
Supports media queries and optimizes page load via matchMedia API
https://github.com/door3/angular-css
Here are some examples:
Routes
$routeProvider
.when('/page1', {
templateUrl: 'page1/page1.html',
controller: 'page1Ctrl',
/* Now you can bind css to routes */
css: 'page1/page1.css'
})
.when('/page2', {
templateUrl: 'page2/page2.html',
controller: 'page2Ctrl',
/* You can also enable features like bust cache, persist and preload */
css: {
href: 'page2/page2.css',
bustCache: true
}
})
.when('/page3', {
templateUrl: 'page3/page3.html',
controller: 'page3Ctrl',
/* This is how you can include multiple stylesheets */
css: ['page3/page3.css','page3/page3-2.css']
})
.when('/page4', {
templateUrl: 'page4/page4.html',
controller: 'page4Ctrl',
css: [
{
href: 'page4/page4.css',
persist: true
}, {
href: 'page4/page4.mobile.css',
/* Media Query support via window.matchMedia API
* This will only add the stylesheet if the breakpoint matches */
media: 'screen and (max-width : 768px)'
}, {
href: 'page4/page4.print.css',
media: 'print'
}
]
});
Directives
myApp.directive('myDirective', function () {
return {
restrict: 'E',
templateUrl: 'my-directive/my-directive.html',
css: 'my-directive/my-directive.css'
}
});
Additionally, you can use the $css service for edge cases:
myApp.controller('pageCtrl', function ($scope, $css) {
// Binds stylesheet(s) to scope create/destroy events (recommended over add/remove)
$css.bind({
href: 'my-page/my-page.css'
}, $scope);
// Simply add stylesheet(s)
$css.add('my-page/my-page.css');
// Simply remove stylesheet(s)
$css.remove(['my-page/my-page.css','my-page/my-page2.css']);
// Remove all stylesheets
$css.removeAll();
});
You can read more about AngularCSS here:
http://door3.com/insights/introducing-angularcss-css-demand-angularjs
Could append a new stylesheet to head within $routeProvider. For simplicity am using a string but could create new link element also, or create a service for stylesheets
/* check if already exists first - note ID used on link element*/
/* could also track within scope object*/
if( !angular.element('link#myViewName').length){
angular.element('head').append('<link id="myViewName" href="myViewName.css" rel="stylesheet">');
}
Biggest benefit of prelaoding in page is any background images will already exist, and less lieklyhood of FOUC
#sz3, funny enough today I had to do exactly what you were trying to achieve: 'load a specific CSS file only when a user access' a specific page. So I used the solution above.
But I am here to answer your last question: 'where exactly should I put the code. Any ideas?'
You were right including the code into the resolve, but you need to change a bit the format.
Take a look at the code below:
.when('/home', {
title:'Home - ' + siteName,
bodyClass: 'home',
templateUrl: function(params) {
return 'views/home.html';
},
controler: 'homeCtrl',
resolve: {
style : function(){
/* check if already exists first - note ID used on link element*/
/* could also track within scope object*/
if( !angular.element('link#mobile').length){
angular.element('head').append('<link id="home" href="home.css" rel="stylesheet">');
}
}
}
})
I've just tested and it's working fine, it injects the html and it loads my 'home.css' only when I hit the '/home' route.
Full explanation can be found here, but basically resolve: should get an object in the format
{
'key' : string or function()
}
You can name the 'key' anything you like - in my case I called 'style'.
Then for the value you have two options:
If it's a string, then it is an alias for a service.
If it's function, then it is injected and the return value is treated
as the dependency.
The main point here is that the code inside the function is going to be executed before before the controller is instantiated and the $routeChangeSuccess event is fired.
Hope that helps.
Awesome, thank you!! Just had to make a few adjustments to get it working with ui-router:
var app = app || angular.module('app', []);
app.directive('head', ['$rootScope', '$compile', '$state', function ($rootScope, $compile, $state) {
return {
restrict: 'E',
link: function ($scope, elem, attrs, ctrls) {
var html = '<link rel="stylesheet" ng-repeat="(routeCtrl, cssUrl) in routeStyles" ng-href="{{cssUrl}}" />';
var el = $compile(html)($scope)
elem.append(el);
$scope.routeStyles = {};
function applyStyles(state, action) {
var sheets = state ? state.css : null;
if (state.parent) {
var parentState = $state.get(state.parent)
applyStyles(parentState, action);
}
if (sheets) {
if (!Array.isArray(sheets)) {
sheets = [sheets];
}
angular.forEach(sheets, function (sheet) {
action(sheet);
});
}
}
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
applyStyles(fromState, function(sheet) {
delete $scope.routeStyles[sheet];
console.log('>> remove >> ', sheet);
});
applyStyles(toState, function(sheet) {
$scope.routeStyles[sheet] = sheet;
console.log('>> add >> ', sheet);
});
});
}
}
}]);
If you only need your CSS to be applied to one specific view, I'm using this handy snippet inside my controller:
$("body").addClass("mystate");
$scope.$on("$destroy", function() {
$("body").removeClass("mystate");
});
This will add a class to my body tag when the state loads, and remove it when the state is destroyed (i.e. someone changes pages). This solves my related problem of only needing CSS to be applied to one state in my application.
'use strict';
angular.module('app')
.run(
[
'$rootScope', '$state', '$stateParams',
function($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
}
]
)
.config(
[
'$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$urlRouterProvider
.otherwise('/app/dashboard');
$stateProvider
.state('app', {
abstract: true,
url: '/app',
templateUrl: 'views/layout.html'
})
.state('app.dashboard', {
url: '/dashboard',
templateUrl: 'views/dashboard.html',
ncyBreadcrumb: {
label: 'Dashboard',
description: ''
},
resolve: {
deps: [
'$ocLazyLoad',
function($ocLazyLoad) {
return $ocLazyLoad.load({
serie: true,
files: [
'lib/jquery/charts/sparkline/jquery.sparkline.js',
'lib/jquery/charts/easypiechart/jquery.easypiechart.js',
'lib/jquery/charts/flot/jquery.flot.js',
'lib/jquery/charts/flot/jquery.flot.resize.js',
'lib/jquery/charts/flot/jquery.flot.pie.js',
'lib/jquery/charts/flot/jquery.flot.tooltip.js',
'lib/jquery/charts/flot/jquery.flot.orderBars.js',
'app/controllers/dashboard.js',
'app/directives/realtimechart.js'
]
});
}
]
}
})
.state('ram', {
abstract: true,
url: '/ram',
templateUrl: 'views/layout-ram.html'
})
.state('ram.dashboard', {
url: '/dashboard',
templateUrl: 'views/dashboard-ram.html',
ncyBreadcrumb: {
label: 'test'
},
resolve: {
deps: [
'$ocLazyLoad',
function($ocLazyLoad) {
return $ocLazyLoad.load({
serie: true,
files: [
'lib/jquery/charts/sparkline/jquery.sparkline.js',
'lib/jquery/charts/easypiechart/jquery.easypiechart.js',
'lib/jquery/charts/flot/jquery.flot.js',
'lib/jquery/charts/flot/jquery.flot.resize.js',
'lib/jquery/charts/flot/jquery.flot.pie.js',
'lib/jquery/charts/flot/jquery.flot.tooltip.js',
'lib/jquery/charts/flot/jquery.flot.orderBars.js',
'app/controllers/dashboard.js',
'app/directives/realtimechart.js'
]
});
}
]
}
})
);