如何在AngularJS中包含视图/部分特定的样式


132

对我的应用程序使用的各种视图使用单独的样式表的正确/可接受的方法是什么?

目前,我在视图/部分的html顶部放置了一个link元素,但有人告诉我这是一种不好的做法,即使所有现代浏览器都支持它,但我可以理解为什么对此不满意。

另一种可能性是将单独的样式表放置在我的index.html中,head但是我希望它仅在以性能名义加载其视图时才加载样式表。

这是不好的做法吗,因为样式只有在将css从服务器加载后才会生效,从而导致在慢速的浏览器中快速刷新未格式化的内容?尽管我正在本地进行测试,但我还没有目睹这一点。

有没有一种方法可以通过传递给Angular的对象加载CSS $routeProvider.when

提前致谢!


我验证了您的“未格式化内容的快速闪烁”声明。我<link>这种格式使用css 标记,并使用最新的Chrome浏览器,本地计算机上的服务器(并启用了“禁用缓存”来模拟“首次加载”情况)。我想<style>在服务器的html部分中预先插入标签可以避免此问题。
2015年

Answers:


150

我知道这个问题现在已经很老了,但是在对这个问题的各种解决方案进行了大量研究之后,我认为我可能已经提出了一个更好的解决方案。

更新1:自发布此答案以来,我已将所有这些代码添加到我发布到GitHub的简单服务中。仓库位于此处。随时查看更多信息。

更新2:如果您需要的只是为路线引入样式表的轻量级解决方案,那么此答案就很好。如果您想要一个更完整的解决方案来管理整个应用程序中的按需样式表,则可能需要检出Door3的AngularCSS项目。它提供了更多细粒度的功能。

如果将来有人感兴趣,这是我想出的:

1.为该<head>元素创建一个自定义指令:

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;
                        });
                    }
                });
            }
        };
    }
]);

该指令执行以下操作:

  1. 它编译(使用$compile)一个html字符串,该字符串使用和<link />scope.routeStyles对象中的每个项目创建一组标签。ng-repeatng-href
  2. 它将编译后的<link />元素集附加到<head>标签。
  3. 然后,它使用$rootScope侦听'$routeChangeStart'事件。对于每个'$routeChangeStart'事件,它都会捕获“当前” $$route对象(用户将要离开的路线),并从<head>标记中删除其部分特定的css文件。它还捕获“下一个” $$route对象(用户将要去的路线),并将其部分特定的css文件中的任何一个添加到<head>标签中。
  4. 并且ng-repeat,已编译<link />标签的一部分根据添加到scope.routeStyles对象或从对象中删除的内容来处理特定于页面的样式表的所有添加和删除。

注意: 这要求您的ng-app属性位于<html>元素上,而不是上的<body>或内部<html>

2.使用以下命令指定哪些样式表属于哪些路由$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']
        })
}]);

此配置将自定义css属性添加到用于设置每个页面路由的对象。该对象'$routeChangeStart'作为传递给每个事件.$$route。因此,在监听'$routeChangeStart'事件时,我们可以获取css我们指定的属性,并<link />根据需要添加/删除这些标签。请注意,css在路由上指定属性是完全可选的,因为'/some/route/2'示例中已将其省略。如果路由没有css属性,则该<head>指令将对该路由完全不执行任何操作。还要注意,您甚至可以在每个路由上使用多个特定于页面的样式表,如'/some/route/3'上面的示例所示,其中css属性是该路由所需样式表的相对路径的数组。

3.完成工作 我认为,这两件事设置了所需的一切,并且使用了最干净的代码来完成了所有工作。

希望这对我可能一直在努力解决此问题的其他人有所帮助。


2
天哪,谢谢你!正是我在寻找什么:)。现在就对其进行测试,即可完美运行(而且易于实施)。也许您应该为此创建一个拉取请求并将其放入核心。我知道AngularJS的家伙正在研究范围内的CSS,这可能是朝正确方向迈出的一步?
smets.kevin 2014年

那些家伙比我聪明。我敢肯定,他们之前会考虑过这个(或类似的)解决方案,并且出于任何原因都选择不将其实现到核心中。
Tennisgent 2014年

CSS文件的正确位置是什么?css:“ css / partial1.css”是否将css文件夹隐含在角度应用程序文件夹的根目录中?
Cordle 2014年

它相对于您的index.html文件。因此,在上面的示例中,文件夹将位于根目录中,index.html并且css文件夹将位于根目录中,其中包含所有css文件。但是只要您使用正确的相对路径,就可以根据需要构建应用程序。
网球网

1
@Kappys,当您移至新视图时,脚本会删除上一个视图的样式。如果您不希望发生这种情况,只需从伪指令中删除以下代码:angular.forEach(current.$$route.css, function(sheet){ delete scope.routeStyles[sheet]; });
Tennisgent

34

@tennisgent的解决方案很棒。但是,我认为有点有限。

Angular中的模块化和封装超越了路线。基于Web向基于组件的开发的方式,将其应用于指令也很重要。

如您所知,在Angular中,我们可以在页面和组件中包括模板(结构)和控制器(行为)。AngularCSS启用了最后一个缺少的部分:附加样式表(演示文稿)。

对于完整的解决方案,我建议使用AngularCSS。

  1. 支持Angular的ngRoute,UI路由器,指令,控制器和服务。
  2. 不需要ng-app<html>标签中。当您在同一页面上运行多个应用程序时,这一点很重要
  3. 您可以自定义样式表的注入位置:头,主体,自定义选择器等。
  4. 支持预加载,持久化和缓存清除
  5. 支持媒体查询并通过matchMedia API优化页面加载

https://github.com/door3/angular-css

这里有些例子:

路线

  $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'
        }
      ]
    });

指令

myApp.directive('myDirective', function () {
  return {
    restrict: 'E',
    templateUrl: 'my-directive/my-directive.html',
    css: 'my-directive/my-directive.css'
  }
});

此外,您可以将$css服务用于极端情况:

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();

});

您可以在此处阅读有关AngularCSS的更多信息:

http://door3.com/insights/introducing-angularcss-css-demand-angularjs


1
我真的很喜欢您的方法,但是想知道如何在需要将所有CSS样式连接在一起的生产应用程序中使用它?对于html模板,我将$ templateCache.put()用于生产代码,并且对css做类似的事情也很好。
Tom Makin

如果需要从服务器获取串联的CSS,则始终可以执行/getCss?files=file1(.css)、file2、file3之类的操作,并且服务器将以给定的顺序响应所有3个文件并进行串联。
Petr Urban

13

可以在内附加一个新样式表$routeProvider。为简单起见,我使用了一个字符串,但也可以创建新的link元素,或为样式表创建服务

/* 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">');
}

在页面中进行预排版的最大好处是,任何背景图像都将已经存在,并且减少了 FOUC


这是不是做到像刚才包括同样的事情<link><head>中的index.html的静态,虽然?
布兰登

如果when尚未调用该路线的,则为false。可以把这个代码中controller的回调whenrouteProvider,或者是内resolve回调这可能触发越早
charlietfl

哦,好的,我不好,点击不了。看起来很扎实,但如果我在任何时候注入它,您能否解释它的预加载方式?
布兰登

1
如果在页面上添加routeprovider注释,则不是在预加载...该注释是关于在提供页面时将其包含在主页的开头
charlietfl 2013年

-_-对不起,如果您不告诉我,我就无法入睡。无论如何,这就是我现在的位置。尝试弄清楚一次加载所有样式表的开销是否比在用户切换视图时使用一些FOUC更好。我想这确实不是一个与Angular相关的问题,而是关于Web应用程序UX的问题。不过,谢谢,如果我决定不进行预加载,我可能会接受您的建议。
布兰登

5

@ sz3,今天足够有趣,我必须做您想达到的目标:' 仅当用户访问特定页面时才加载特定CSS文件。所以我用了上面的解决方案。

但是我在这里回答您的最后一个问题:' 我应该将代码放在哪里。有任何想法吗什么吗?

将代码包含到resolve中是正确的,但是您需要稍微更改格式。

看一下下面的代码:

.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">');
      }
    }
  }
})

我刚刚测试过,一切正常,仅当我点击“ / home”路由时,它才会注入html并加载“ home.css”。

可以在这里找到完整的解释,但基本上可以解决:应该以以下格式获取对象

{
  'key' : string or function()
} 

您可以随意命名“ ”,在我的情况下,我称其为“ 样式” ”。

然后,对于该值,您有两个选择:

  • 如果是string,则它是服务的别名。

  • 如果是function,则将其注入并将返回值视为依赖项。

这里的要点是,在实例化控制器和触发$ routeChangeSuccess事件之前,将执行函数内部的代码。

希望有帮助。


2

太棒了,谢谢你!!只需进行一些调整即可使其与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);
                });
            });
        }
    }
}]);

由于CSS混乱,我并不需要在所有地方进行删除和添加,但这对ui-router很有帮助!谢谢:)
imsheth

1

如果您只需要将CSS 应用于一个特定的视图,则可以在控制器中使用以下方便的代码段:

$("body").addClass("mystate");

$scope.$on("$destroy", function() {
  $("body").removeClass("mystate"); 
});

这将body在状态加载时为我的标签添加一个类,并在状态被破坏(即有人更改页面)时将其删除。这解决了我的相关问题,即只需要将CSS应用于应用程序中的一种状态。


0

“使用严格”;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'
                                    ]
                                });
                            }
                        ]
                    }
                })
                 );

一个没有上下文的简单代码示例很少是一个问题的充分答案。此外,这个问题已经得到了高度认可的答案。
AJ X.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.