Як включити перегляд / частково специфічний стиль у AngularJS


132

Який належний / прийнятий спосіб використовувати окремі таблиці стилів для різних представлень, які використовує моя програма?

В даний час я розміщую елемент посилання в HTML-огляді / частковому HTML вгорі, але мені сказали, що це погана практика, хоча всі сучасні браузери його підтримують, але я бачу, чому це нахмурилося.

Інша можливість - розміщення окремих таблиць стилів у моєму index.html, headале я хотів би, щоб він завантажував таблицю стилів лише у тому випадку, коли її перегляд завантажується в ім'я продуктивності.

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

Чи є спосіб завантажити CSS через об'єкт, переданий Angular's $routeProvider.when ?

Спасибі заздалегідь!


Я підтвердив ваше твердження про "швидкий спалах неформатоване вмісту". Я використовував <link>теги css у такому форматі , з останнім Chrome, сервером на моїй локальній машині (та "Відключити кеш", щоб імітувати умови "першого завантаження"). Я думаю, що попередня вставка <style>тегу в частину html на сервері дозволить уникнути цієї проблеми.
Останні

Відповіді:


150

Я знаю, що це питання вже давнє, але, провівши багато досліджень різних рішень цієї проблеми, я думаю, що я міг прийти до кращого рішення.

ОНОВЛЕННЯ 1: Після публікації цієї відповіді я додав увесь цей код до простої послуги, яку я опублікував у GitHub. Репо знаходиться тут . Не соромтеся перевірити це для отримання додаткової інформації.

ОНОВЛЕННЯ 2: Ця відповідь чудова, якщо все, що вам потрібно, - це легке рішення для складання таблиць стилів для ваших маршрутів. Якщо ви хочете отримати більш повне рішення для керування таблицями стилів за запитом у всій програмі, ви можете перевірити проект AngularCSS Door3 . Він забезпечує набагато більш дрібну функціональність.

Якщо хтось у майбутньому зацікавиться, ось що я придумав:

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об'єкт (маршрут, який користувач збирається покинути) та видаляє з <head>тегу його часткові файли css . Він також захоплює "наступний" $$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. Ви закінчили Ці дві речі налаштуйте все, що було потрібно, і це, на мою думку, робиться з найчистішим можливим кодом.

Сподіваюся, що це допоможе комусь іншому, хто, можливо, бореться з цим питанням стільки, скільки я.


2
Святий молі, дякую за це! Саме те, що я шукав :). Тільки тестував його зараз, і він працює чудово (плюс простий у виконанні). Можливо, вам слід створити для цього запит на витягнення і ввести його в основу. Я знаю, хлопці AngularJS шукали обшитий css, це може бути кроком у правильному напрямку?
smets.kevin

Ці хлопці набагато розумніші за мене. Я впевнений, що раніше вони б придумали це (або подібне) рішення і вирішили не втілювати його в основу з будь-якої причини.
тенісист

Яке правильне місце для файлу css? Чи означає css: 'css / part1.css' папку css у корені папки кутових додатків?
Cordle

Це відносно вашого index.htmlфайлу. Так у наведеному вище прикладі index.htmlбуло б у корені, а cssпапка - у корені, що містить усі файли css. але ви можете структурувати додаток як завгодно, якщо ви використовуєте правильні відносні шляхи.
тенісист

1
@Kappys, сценарій видаляє стиль для попереднього перегляду, коли ви переходите до нового перегляду. Якщо ви не хочете, щоб це сталося, просто видаліть наступний код з директиви: angular.forEach(current.$$route.css, function(sheet){ delete scope.routeStyles[sheet]; });.
тенісист

34

@ рішення тенісгенту чудово. Однак, я думаю, це трохи обмежено.

Модульність та інкапсуляція в кутовій куті виходить за рамки маршрутів. Виходячи з того, як Інтернет рухається до розробки на основі компонентів, важливо застосовувати це і в директивах.

Як ви вже знаєте, в Angular ми можемо включати шаблони (структура) та контролери (поведінка) на сторінки та компоненти. КутовийCSS дозволяє виконувати останній фрагмент: додавання таблиць стилів (презентація).

Для повного рішення я пропоную використовувати AngularCSS.

  1. Підтримує ngRoute, маршрутизатор інтерфейсу, Angular, директиви, контролери та послуги.
  2. Не потрібно , щоб ng-appв <html>тезі. Це важливо, коли на одній сторінці працює кілька додатків
  3. Ви можете налаштувати, куди вводяться таблиці стилів: голова, тіло, спеціальний селектор тощо ...
  4. Підтримує попереднє завантаження, зберігання та зменшення кешу
  5. Підтримує медіа-запити та оптимізує завантаження сторінки за допомогою 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


1
Мені дуже подобається ваш підхід, але мені було цікаво, як його можна використовувати у виробничому додатку, де всі стилі css потрібно об'єднати разом? Для html-шаблонів я використовую $ templateCache.put () для виробничого коду, і було б непогано зробити щось подібне для css.
Том Макін

Якщо вам потрібно отримати об'єднаний CSS з сервера, ви завжди можете зробити щось на зразок /getCss?files=file1(.css),file2,file3, і сервер відповів би на всі 3 файли в заданому порядку і об'єднав.
Петро Урбан

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


Невже це не зробить те ж саме, що просто включити <link>в <head>індекс.html статично?
Брендон

ні, якщо whenдля маршруту не було викликано. Можна поставити цей код у controllerзворотній виклик у whenмежах routeProvider, або, можливо, у resolveзворотній зв'язок, що, швидше за все, спрацьовує швидше
charlietfl

Ой гаразд, мій поганий, що клацає ні. Виглядає досить солідно, за винятком того, чи можете ви пояснити, як відбувається його попереднє завантаження, якщо я все-таки ввожу його?
Брендон

1
це не попереднє завантаження, якщо ви додасте його routeprovider... цей коментар
стосувався

-_- вибачте, мені не вистачає сну, якщо ви не можете сказати. У будь-якому випадку, я зараз такий. Спроба з’ясувати, чи накладніше завантаження всіх моїх таблиць стилів одразу краще, ніж мати деякий FOUC, коли користувач перемикає погляди. Я думаю, що це насправді не пов'язане з кутовим питанням, наскільки це стосується веб-додатків 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-роутером:

    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-роутера! Дякую :)
imsheth

1

Якщо вам потрібен тільки ваш CSS буде застосовуватися до однієї точки зору конкретного, я використовую цей зручний фрагмент коду всередині мого контролера:

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

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

Це додасть клас до мого bodyтегу, коли стан завантажується, і видалить його, коли стан зруйнований (тобто хтось змінює сторінки). Це вирішує мою пов’язану проблему - лише необхідність застосування CSS до одного стану в моєму додатку.


0

'використовувати суворо'; angular.module ('app') .run (['$ 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'
                                    ]
                                });
                            }
                        ]
                    }
                })
                 );

Простий приклад коду без контексту рідко є достатньою відповіддю на питання. Крім того, на це питання вже є досить прийнята відповідь.
AJ X.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.