Як включити перегляд / частково специфічний стиль у 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.