【问题标题】:How to include view/partial specific styling in AngularJS如何在 AngularJS 中包含视图/部分特定样式
【发布时间】:2013-02-18 01:33:37
【问题描述】:

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

目前我在顶部的视图/部分 html 中放置了一个链接元素,但有人告诉我这是不好的做法,尽管所有现代浏览器都支持它,但我明白为什么它不受欢迎。

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

这是不好的做法,因为样式在从服务器加载 css 后才会生效,导致在慢速浏览器中快速闪烁未格式化的内容?虽然我正在本地测试,但我还没有看到这一点。

有没有办法通过传递给Angular的$routeProvider.when的对象来加载CSS?

提前致谢!

【问题讨论】:

  • 我验证了您的“未格式化内容的快速闪现”断言。我在this format 中使用了css <link> 标签,使用最新的Chrome,我的本地机器上的服务器(并且“禁用缓存”以模拟“首次加载”条件)。我想在服务器的 html 部分中预先插入一个<style> 标签可以避免这个问题。

标签: css model-view-controller angularjs


【解决方案1】:

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

/* 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 的可能性较小

【讨论】:

  • 这难道不会与在 index.html 的 &lt;head&gt; 中静态包含 &lt;link&gt; 一样吗?
  • 如果路由的when 没有被调用,则不会。可以将此代码放在whenwhen 回调中的controller 回调中,或者可能在resolve 回调中,这可能会更快触发
  • 哦,好吧,我的错,点击不。看起来很可靠,除非你能解释一下如果我在什么时候注入它是如何预加载的吗?
  • 如果您将其附加到 routeprovider 中,则不会预加载...该评论是关于在提供页面时将其包含在主页头中
  • -_- 抱歉,如果你看不出来,我睡眠不足。无论如何,这就是我现在所处的位置。试图弄清楚一次加载所有样式表的开销是否比在用户切换视图时使用一些 FOUC 更好。我想这真的不是一个与 Angular 相关的问题,而是关于 Web 应用 UX 的问题。不过谢谢,如果我决定不进行预加载,我可能会接受您的建议。
【解决方案2】:

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

我刚刚测试过,它工作正常,它会注入 html,并且只有在我点击 '/home' 路由时才会加载我的 'home.css'。

完整的解释可以找到here,但基本上resolve:应该得到一个格式的对象

{
  'key' : string or function()
} 

您可以将“key”命名为任何您喜欢的名称 - 在我的例子中,我称之为“style”。

那么对于值你有两个选择:

  • 如果是字符串,那么它就是服务的别名。

  • 如果是function,则注入并处理返回值 作为依赖。

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

希望对您有所帮助。

【讨论】:

    【解决方案3】:

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

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

    更新 2: 如果您只需要一个轻量级的解决方案来为您的路线引入样式表,那么这个答案非常棒。如果您想要一个更完整的解决方案来管理整个应用程序中的按需样式表,您可能需要查看Door3's AngularCSS project。它提供了更细粒度的功能。

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

    1.为 &lt;head&gt; 元素创建自定义指令:

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

    注意: 这要求您的ng-app 属性位于&lt;html&gt; 元素上,而不是&lt;body&gt;&lt;html&gt; 内的任何内容上。 p>

    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 属性添加到用于设置每个页面的路由的对象。该对象作为.$$route 传递给每个'$routeChangeStart' 事件。因此,在监听'$routeChangeStart' 事件时,我们可以获取我们指定的css 属性,并根据需要附加/删除那些&lt;link /&gt; 标记。请注意,在路由上指定 css 属性是完全可选的,因为它在 '/some/route/2' 示例中被省略了。如果路由没有css 属性,&lt;head&gt; 指令将对该路由无任何作用。另请注意,您甚至可以在每个路由中拥有多个特定于页面的样式表,如上面的 '/some/route/3' 示例,其中 css 属性是该路由所需样式表的相对路径数组。

    3.你完成了 这两件事设置了所需的一切,并且在我看来,它使用了尽可能干净的代码。

    希望对可能和我一样在这个问题上苦苦挣扎的其他人有所帮助。

    【讨论】:

    • 天哪,谢谢!正是我想要的:)。现在刚刚对其进行了测试,它运行良好(而且易于实施)。也许您应该为此创建一个拉取请求并将其纳入核心。我知道 AngularJS 的人正在研究作用域 css,这可能是朝着正确方向迈出的一步?
    • 那些家伙比我聪明多了。我敢肯定,他们之前会想出这个(或类似的)解决方案,并且无论出于何种原因都选择不将其实施到核心中。
    • css 文件的正确位置是什么? css: 'css/partial1.css' 是否暗示 angular app 文件夹根目录下的 css 文件夹?
    • 它与您的 index.html 文件相关。因此,在上面的示例中,index.html 将位于根目录,css 文件夹将位于根目录,包含所有 css 文件。但只要使用正确的相对路径,您就可以根据需要构建应用程序。
    • @Kappys,当您移动到新视图时,脚本会删除前一个视图的样式。如果您不希望这种情况发生,只需从指令中删除以下代码:angular.forEach(current.$$route.css, function(sheet){ delete scope.routeStyles[sheet]; });
    【解决方案4】:

    太棒了,谢谢!!只需进行一些调整即可使其与 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 有很大帮助!谢谢:)
    【解决方案5】:

    @tennisgent 的解决方案很棒。不过,我觉得有点局限。

    Angular 中的模块化和封装超越了路由。基于 Web 正在朝着基于组件的开发方式发展,在指令中应用这一点也很重要。

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

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

    1. 支持 Angular 的 ngRoute、UI 路由器、指令、控制器和服务。
    2. 不需要在&lt;html&gt; 标记中包含ng-app。当您在同一页面上运行多个应用时,这一点很重要
    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

    【讨论】:

    • 我真的很喜欢您的方法,但想知道如何在需要将所有 css 样式连接在一起的生产应用程序中使用它?对于 html 模板,我将 $templateCache.put() 用于生产代码,对 css 做类似的事情会很好。
    • 如果您需要从服务器获取串联的 CSS,您可以随时执行类似 /getCss?files=file1(.css),file2,file3 的操作,服务器会按照给定顺序响应所有 3 个文件并连接起来。
    【解决方案6】:

    如果您只需要将 CSS 应用到一个特定的视图,我会在我的控制器中使用这个方便的 sn-p:

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

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

    【讨论】:

      【解决方案7】:

      '使用严格'; angular.module('app') 。跑步( [ '$rootScope', '$state', '$stateParams', 功能($rootScope,$state,$stateParams){ $rootScope.$state = $state; $rootScope.$stateParams = $stateParams; } ] ) .config( [ '$stateProvider', '$urlRouterProvider', 函数($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'
                                          ]
                                      });
                                  }
                              ]
                          }
                      })
                       );
      

      【讨论】:

      • 没有上下文的简单代码示例很少能充分回答问题。此外,这个问题已经有了一个高度接受的答案。
      猜你喜欢
      • 2010-10-16
      • 1970-01-01
      • 1970-01-01
      • 2011-06-10
      • 2010-10-29
      • 1970-01-01
      • 1970-01-01
      • 2016-02-10
      • 2014-11-12
      相关资源
      最近更新 更多