Как включить вид / частичный стиль в AngularJS

132

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

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

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

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

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

Заранее спасибо!

Brandon
источник
Я подтвердил ваше утверждение о "быстрой вспышке неформатированного содержания". Я использовал <link>теги css в этом формате с последней версией Chrome, сервером на моем локальном компьютере (и «Отключить кеш» для имитации условий «первой загрузки»). Я полагаю, что предварительная вставка <style>тега в партиал html на сервере позволит избежать этой проблемы.
Posest

Ответы:

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

Надеюсь, что это поможет кому-то еще, кто, возможно, борется с этой проблемой так же сильно, как и я.

tennisgent
источник
2
Holy moly, спасибо за это! Именно то, что я искал :). Просто протестировал его сейчас, и он отлично работает (плюс прост в реализации). Возможно, вам стоит создать для этого пул-реквест и поместить его в ядро. Я знаю, что ребята из AngularJS изучали css с областью видимости, это может быть шагом в правильном направлении?
smets.kevin
Эти парни намного умнее меня. Я уверен, что они придумали бы это (или подобное) решение раньше и решили не внедрять его в ядро ​​по какой-либо причине.
Tennisgent
Какое правильное место для файла css? Под css: 'css / partial1.css' подразумевается папка css в корне папки приложения angular?
Cordle
Это относительно вашего index.htmlфайла. Таким образом, в приведенном выше примере index.htmlэто будет в корне, а cssпапка будет в корне, содержащая все файлы css. но вы можете структурировать свое приложение по своему усмотрению, если используете правильные относительные пути.
Tennisgent
1
@Kappys, сценарий удаляет стиль для предыдущего представления при переходе к новому представлению. Если вы не хотите, чтобы это произошло, просто удалите следующий код из директивы: angular.forEach(current.$$route.css, function(sheet){ delete scope.routeStyles[sheet]; });.
Tennisgent
34

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

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

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

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

  1. Поддерживает Angular ngRoute, UI Router, директивы, контроллеры и службы.
  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

castillo.io
источник
1
Мне очень нравится ваш подход, но мне было интересно, как его можно использовать в производственном приложении, где все стили css должны быть объединены вместе? Для html-шаблонов я использую $ templateCache.put () для производственного кода, и было бы неплохо сделать что-то подобное для css.
Tom Makin
Если вам нужно получить объединенный CSS с сервера, вы всегда можете сделать что-то вроде /getCss?files=file1(.css),file2,file3, и сервер ответит всеми тремя файлами в заданном порядке и объединенными.
Петр Урбан
13

Можно добавить новую таблицу стилей в заголовок $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

charlietfl
источник
Не будет ли это осуществить то же самое, только в том числе и <link>в <head>в index.html статически, хотя?
Brandon
нет, если whenдля маршрута не был вызван. Можно поместить этот код в controllerобратный вызов whenвнутри routeProviderили, возможно, в resolve
обратном вызове,
О, ладно, плохо, это не нажимает. Выглядит довольно солидно, за исключением того, не могли бы вы объяснить, как происходит предварительная загрузка, если я все равно ввожу его?
Брэндон,
1
это не предварительная загрузка, если вы добавите его в routeprovider... этот комментарий
касался
-_- извини, мне не хватает сна, если ты не можешь сказать. Во всяком случае, вот где я сейчас нахожусь. Пытаться выяснить, лучше ли накладные расходы на загрузку всех моих таблиц стилей сразу, чем иметь некоторую FOUC, когда пользователь переключает представления. Думаю, это действительно не вопрос Angular, а скорее касается UX веб-приложений. Хотя спасибо, я, вероятно, приму ваше предложение, если решу не выполнять предварительную загрузку.
Брэндон
5

@ 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.

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

Денисон Луз
источник
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);
                });
            });
        }
    }
}]);
CraigM
источник
Мне не нужно было удалять и добавлять везде, так как мой css был испорчен, но это очень помогло с ui-router! Спасибо :)
imsheth
1

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

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

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

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

Matt
источник
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'
                                    ]
                                });
                            }
                        ]
                    }
                })
                 );
rambaburoja
источник
Простой пример кода без контекста редко может быть достаточным ответом на вопрос. Более того, на этот вопрос уже есть общепринятый ответ.
AJ X.