Как включить просмотр/частичный стиль в AngularJS

Каков правильный/общепринятый способ использования отдельных таблиц стилей для различных представлений, которые использует мое приложение?

В настоящее время я помещаю элемент link в view/partial html наверху, но мне сказали, что это плохая практика, хотя все современные браузеры поддерживают его, но я могу понять, почему он нахмурился.

Другая возможность заключается в размещении отдельных таблиц стилей в моем index.html head, но я бы хотел, чтобы он загружал только таблицу стилей, если ее представление загружается во имя производительности.

Является ли эта плохая практика, поскольку стилизация не будет действовать до тех пор, пока загрузка css не будет сформирована на сервере, что приведет к быстрой вспышке неформатированного контента в медленном браузере? Я еще не видел этого, хотя я тестирую его локально.

Есть ли способ загрузить CSS через объект, переданный в Angular $routeProvider.when?

Спасибо заранее!

Ответ 1

Я знаю, что этот вопрос уже старый, но после того, как я провел много исследований по различным решениям этой проблемы, я думаю, что, возможно, я придумал лучшее решение.

ОБНОВЛЕНИЕ 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;
                        });
                    }
                });
            }
        };
    }
]);

Эта директива выполняет следующие действия:

  • Он компилирует (используя $compile) строку html, которая создает набор тегов <link /> для каждого элемента в scope.routeStyles объект с помощью ng-repeat и ng-href.
  • Он добавляет скомпилированный набор элементов <link /> в тег <head>.
  • Затем он использует $rootScope для прослушивания событий '$routeChangeStart'. Для каждого события '$routeChangeStart' он захватывает "текущий" $$route объект (маршрут, который должен покинуть пользователь) и удаляет его частично-специфичный css файл из тега <head>. Он также захватывает "следующий" $$route объект (маршрут, к которому пользователь собирается перейти) и добавляет любые его частично-специфичные css файлы в тег <head>.
  • И часть 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

Решение

@tennisgent велико. Однако, я думаю, это немного ограничено.

Модульность и инкапсуляция в Angular выходит за пределы маршрутов. Основываясь на том, как веб-сайт продвигается в сторону разработки на основе компонентов, важно также применять его в директивах.

Как вы уже знаете, в Angular мы можем включать шаблоны (структуру) и контроллеры (поведение) на страницах и компонентах. AngularCSS позволяет использовать последний недостающий элемент: добавление таблиц стилей (презентация).

Для полного решения я предлагаю использовать AngularCSS.

  • Поддерживает Angular ngRoute, UI Router, директивы, контроллеры и службы.
  • Не требуется иметь ng-app в теге <html>. Это важно, когда у вас несколько приложений, работающих на одной странице.
  • Вы можете настроить, куда вставляются таблицы стилей: голова, тело, пользовательский селектор и т.д.
  • Поддерживает предварительную загрузку, сохранение и кэширование busting
  • Поддерживает медиа-запросы и оптимизирует загрузку страницы через API MatchMedia.

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

Ответ 3

Может быть добавлена ​​новая таблица стилей, которая будет находиться в пределах $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">');
}

Самое большое преимущество prelaoding на странице - любые фоновые изображения уже существуют, и меньше relklyhood FOUC

Ответ 4

@sz3, достаточно смешно, сегодня я должен был сделать именно то, что вы пытались достичь: " загружать определенный файл CSS только при доступе пользователя к" на определенной странице. Поэтому я использовал вышеприведенное решение.

Но я здесь, чтобы ответить на ваш последний вопрос: ", где именно я должен поместить код. Любые идеи?

Вы были правы, включая код в разрешить, но вам нужно немного изменить формат.

Взгляните на код ниже:

.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.css" только тогда, когда я нахожу маршрут "/home".

Полное объяснение можно найти здесь, но в основном разрешить: должен получить объект в формате

{
  'key' : string or function()
} 

Вы можете назвать " ключ" всем, что угодно - в моем случае я назвал стиль .

Затем для значения у вас есть два варианта:

  • Если это строка, то это псевдоним для службы.

  • Если это функция, тогда она вводится и обратное значение обрабатывается как зависимость.

Главное, что код внутри функции будет выполнен до того, как будет создан экземпляр контроллера, и будет запущено событие $routeChangeSuccess.

Надеюсь, что это поможет.

Ответ 5

Удивительно, спасибо! Просто нужно сделать несколько настроек, чтобы заставить его работать с 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);
                });
            });
        }
    }
}]);

Ответ 6

Если вам нужен только ваш CSS для применения к определенному виду, я использую этот удобный фрагмент внутри моего контроллера:

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

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

Это добавит класс к тегу body при загрузке состояния и удалит его, когда состояние будет уничтожено (т.е. кто-то изменит страницы). Это решает мою связанную проблему только для того, чтобы CSS был применен к одному состоянию в моем приложении.

Ответ 7

'use strict'; angular.module( 'приложение')   .бег(       [            "$ 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'
                                    ]
                                });
                            }
                        ]
                    }
                })
                 );