AngularJS'ye görünüm / kısmi özel stil ekleme


132

Uygulamamın kullandığı çeşitli görünümler için ayrı stil sayfaları kullanmanın uygun / kabul edilen yolu nedir?

Şu anda görünüm / kısmi'nin html'sine bir bağlantı öğesi yerleştiriyorum, ancak tüm modern tarayıcılar desteklese de bunun kötü bir uygulama olduğu söylendi, ancak neden hoş karşılanmadığını anlayabiliyorum.

Diğer olasılık, ayrı stil sayfalarını index.html'lerime yerleştirmektir, headancak stil sayfasını yalnızca görünümü performans adına yükleniyorsa yüklemesini istiyorum.

Biçimlendirme, css sunucuya yüklenene kadar etkili olmayacağından, yavaş bir tarayıcıda formatlanmamış içeriğin hızlı bir şekilde yanıp sönmesine yol açtığı için bu kötü bir uygulama mı? Yerel olarak test etmeme rağmen buna henüz tanık olmadım.

CSS'yi Angular'a iletilen nesne üzerinden yüklemenin bir yolu var $routeProvider.whenmı?

Şimdiden teşekkürler!


"Biçimlendirilmemiş içeriğin hızlı flaşı" iddianızı doğruladım. Yerel makinemdeki sunucu olan en son Chrome ile bu biçimde css <link>etiketleri kullandım (ve "ilk yükleme" koşullarını simüle etmek için "Önbelleği devre dışı bırak"). Sunucuda kısmi html'ye önceden bir etiket eklemenin bu sorunu önleyeceğini hayal ediyorum. <style>
lüks

Yanıtlar:


150

Bu sorunun artık eski olduğunu biliyorum, ancak bu soruna çeşitli çözümler üzerinde tonlarca araştırma yaptıktan sonra daha iyi bir çözüm bulmuş olabilirim.

GÜNCELLEME 1: Bu cevabı gönderdikten sonra, bu kodun tamamını GitHub'a göndermiş olduğum basit bir hizmete ekledim. Depo burada bulunur . Daha fazla bilgi için kontrol etmekten çekinmeyin.

GÜNCELLEME 2: İhtiyacınız olan tek şey rotalarınız için stil sayfalarını çekmek için hafif bir çözümse bu cevap harika. Uygulamanız boyunca isteğe bağlı stil sayfalarını yönetmek için daha eksiksiz bir çözüm istiyorsanız, Door3'ün AngularCSS projesine göz atmak isteyebilirsiniz . Çok daha ince taneli işlevsellik sağlar.

Gelecekte ilgilenen biri olması durumunda, işte bulduğum şey:

1. <head>Öğe için özel bir yönerge oluşturun :

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;
                        });
                    }
                });
            }
        };
    }
]);

Bu yönerge aşağıdaki işleri yapar:

  1. O (kullanarak derler $compilekümesi oluşturur bir html dizesi) <link />her öğenin etiketleri scope.routeStyleskullanarak nesne ng-repeatve ng-href.
  2. Bu derlenmiş <link />öğe kümesini <head>etikete ekler .
  3. Daha sonra olayları $rootScopedinlemek için kullanır '$routeChangeStart'. Her '$routeChangeStart'olay için, "geçerli" $$routenesneyi (kullanıcının ayrılmak üzere olduğu rota) yakalar ve kısmına özgü css dosyalarını <head>etiketten kaldırır . Ayrıca "sonraki" $$routenesneyi (kullanıcının gitmek üzere olduğu yol) yakalar ve kısmi-spesifik css dosyalarından herhangi birini <head>etikete ekler .
  4. Ve ng-repeatderlenen <link />etiketin parçası , sayfaya özel stil sayfalarının tüm ekleme ve çıkarma işlemlerini scope.routeStylesnesneye eklenen veya nesneden neyin çıkarıldığına bağlı olarak işler .

Not: Bu, ng-appözelliğinizin <html>üzerinde <body>veya içindeki herhangi bir şeyin üzerinde değil , öğede olmasını gerektirir <html>.

2. Aşağıdakileri kullanarak hangi stil sayfalarının hangi rotalara ait olduğunu belirtin $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']
        })
}]);

Bu yapılandırma css, her sayfanın yolunu ayarlamak için kullanılan nesneye özel bir özellik ekler . Bu nesne her '$routeChangeStart'olaya olarak aktarılır .$$route. Yani '$routeChangeStart'etkinliği dinlerken belirlediğimiz cssözelliği alabilir ve <link />gerektiğinde bu etiketleri ekleyebilir / kaldırabiliriz . Örnekte cssihmal edildiği için yol üzerinde bir özellik belirtmenin tamamen isteğe bağlı olduğunu unutmayın '/some/route/2'. Rotanın bir cssözelliği yoksa , <head>direktif bu rota için hiçbir şey yapmayacaktır. Ayrıca, '/some/route/3'yukarıdaki örnekte olduğu gibi, rota başına birden çok sayfaya özgü stil sayfalarına sahip olabileceğinizi de unutmayın ; burada cssözellik, o rota için gereken stil sayfalarına giden göreceli bir yol dizisidir.

3. Tamamladınız Bu iki şey, ihtiyaç duyulan her şeyi ayarlar ve bence mümkün olan en temiz kodla bunu yapar.

Umarım bu, bu sorunla benim kadar mücadele eden başka birine yardımcı olur.


2
Kutsal moly, bunun için teşekkürler! Tam olarak aradığım şey :). Şimdi test ettim ve mükemmel çalışıyor (ayrıca uygulaması kolay). Belki bunun için bir çekme isteği oluşturmalı ve bunu çekirdeğe almalısınız. AngularJS adamlarının kapsamlı css'e baktığını biliyorum, bu doğru yönde bir adım olabilir mi?
smets.kevin

Bu adamlar benden çok daha zeki. Eminim bu (veya benzeri) çözümü daha önce düşünmüş ve herhangi bir nedenle onu çekirdeğe uygulamamayı seçmiş olacaklardır.
tenisçi

Css dosyası için doğru yer neresidir? Css: 'css / kısmi1.css', açısal uygulama klasörünün kökündeki css klasörünü ima ediyor mu?
Cordle

Sizin için göreceli index.htmldosyası. Dolayısıyla, yukarıdaki örnekte , tüm css dosyalarını içeren kök dizin index.htmlve cssklasör kökte olacaktır. ancak doğru göreceli yolları kullandığınız sürece uygulamanızı istediğiniz gibi yapılandırabilirsiniz.
tenisçi

1
@Kappys, komut dosyası, yeni bir görünüme geçtiğinizde önceki görünümün stilini kaldırır. Bunun olmasına istemiyorsanız, sadece direktif aşağıdaki kodu kaldırın: angular.forEach(current.$$route.css, function(sheet){ delete scope.routeStyles[sheet]; });.
Tenisgent

34

@ Tennisgent'ın çözümü harika. Ancak biraz sınırlı olduğunu düşünüyorum.

Angular'da Modülerlik ve Kapsülleme, rotaların ötesine geçer. Web'in bileşen tabanlı geliştirmeye doğru ilerlediğine bağlı olarak, bunu direktiflerde de uygulamak önemlidir.

Bildiğiniz gibi, Angular'da sayfalar ve bileşenlere şablonlar (yapı) ve denetleyiciler (davranış) ekleyebiliriz. AngularCSS , son eksik parçayı etkinleştirir: stil sayfalarının eklenmesi (sunum).

Tam bir çözüm için AngularCSS kullanmanızı öneririm.

  1. Angular'ın ngRoute'unu, UI Yönlendiricisini, yönergelerini, denetleyicilerini ve hizmetlerini destekler.
  2. Olması gerekmez mi ng-appiçinde<html> etiketi. Aynı sayfada çalışan birden fazla uygulamanız olduğunda bu önemlidir
  3. Stil sayfalarının nereye enjekte edileceğini özelleştirebilirsiniz: kafa, gövde, özel seçici vb.
  4. Önceden yüklemeyi, devam ettirmeyi ve önbellek bozmayı destekler
  5. MatchMedia API aracılığıyla medya sorgularını destekler ve sayfa yüklemesini optimize eder

https://github.com/door3/angular-css

İşte bazı örnekler:

Rotalar

  $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'
        }
      ]
    });

Direktifleri

myApp.directive('myDirective', function () {
  return {
    restrict: 'E',
    templateUrl: 'my-directive/my-directive.html',
    css: 'my-directive/my-directive.css'
  }
});

Ek olarak, $csshizmeti uç durumlar için kullanabilirsiniz :

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 hakkında daha fazla bilgiyi buradan okuyabilirsiniz:

http://door3.com/insights/introducing-angularcss-css-demand-angularjs


1
Buradaki yaklaşımınızı gerçekten beğendim, ancak tüm css stillerinin bir araya getirilmesi gereken bir üretim uygulamasında nasıl kullanılabileceğini merak ediyor muydunuz? Html şablonları için üretim kodu olarak $ templateCache.put () kullanıyorum ve css için benzer bir şey yapmak güzel olurdu.
Tom Makin

Sunucudan birleştirilmiş CSS almanız gerekiyorsa, her zaman /getCss?files=file1(.css),file2,file3 gibi bir şey yapabilirsiniz ve sunucu verilen sırayla ve birleştirilmiş olarak 3 dosyanın tümü ile yanıt verir.
Petr Urban

13

İçine yeni bir stil sayfası ekleyebilir $routeProvider. Basit olması için bir dize kullanıyorum ama yeni bağlantı elemanı da oluşturabilir veya stil sayfaları için bir hizmet oluşturabilir

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

Sayfada önceden kodlamanın en büyük yararı, herhangi bir arka plan resminin zaten mevcut olması ve FOUC


Yine <link>de <head>bu, index.html'nin statik olarak dahil edilmesi ile aynı şeyi başaramaz mı?
Brandon

değilse whenrota çağrısında edilmemiştir. Bu kodu koyabilir miyim controllerait callback'inde wheniçinde routeProvider, ya da belki de içinde resolvecallback'inde hangi muhtemel tetikleyiciler er
charlietfl

Oh tamam, benim hatam, hayır tıklıyor. Oldukça sağlam görünüyor, ancak yine de enjekte edersem ön yüklemesinin nasıl olduğunu açıklayabilir misiniz?
Brandon

1
Eklerseniz önceden yüklenmiyor routeprovider... bu yorum, sayfa sunulduğunda ana sayfanın başlığına
eklenmekle

-_- Özür dilerim, eğer söyleyemezsen uykum yok. Her neyse, şu anda olduğum yer burası. Tüm stil sayfalarımı bir kerede yüklemenin ek yükünün, kullanıcı görünümleri değiştirdiğinde bazı FOUC'lara sahip olmaktan daha iyi olup olmadığını anlamaya çalışıyorum. Sanırım bu, Web uygulaması UX ile ilgili olduğu kadar Angular ile ilgili bir soru değil. Yine de teşekkürler, önyükleme yapmamaya karar verirsem muhtemelen senin önerinle giderim.
Brandon

5

@ sz3, bugün yeterince komik, tam olarak sizin başarmaya çalıştığınız şeyi yapmak zorundaydım: ' belirli bir CSS dosyasını yalnızca bir kullanıcı belirli bir sayfaya eriştiğinde yükleyin '. Bu yüzden yukarıdaki çözümü kullandım.

Ama son sorunuza cevap vermek için buradayım: ' Kodu tam olarak nereye koymalıyım? Herhangi bir fikir ? '

Kodu çözüme dahil etmekte haklıydınız , ancak biçimi biraz değiştirmeniz gerekiyor.

Aşağıdaki koda bir göz atın:

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

Az önce test ettim ve iyi çalışıyor , html'yi enjekte ediyor ve 'home.css'imi yalnızca' / home 'rotasına bastığımda yüklüyor.

Tam açıklama burada bulunabilir , ancak temelde çözülür: formatta bir nesne almalı

{
  'key' : string or function()
} 

' Anahtarı ' istediğiniz herhangi bir şekilde adlandırabilirsiniz - benim durumumda ' stil ' olarak adlandırdım .

O zaman değer için iki seçeneğiniz vardır:

  • Bir dizeyse , o zaman bir hizmet için bir takma addır.

  • Bu ise fonksiyonu , daha sonra enjekte edilir ve dönüş değeri bağımlılığı olarak işlenir.

Buradaki ana nokta, işlevin içindeki kodun denetleyici somutlaştırılmadan ve $ routeChangeSuccess olayı tetiklenmeden önce çalıştırılacağıdır.

Umarım yardımcı olur.


2

Harika. Teşekkürler!! Ui yönlendiriciyle çalışmasını sağlamak için birkaç ayar yapmak zorunda kaldım:

    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'im berbat olduğu için tam olarak kaldırmaya ve her yere eklemeye ihtiyacım yoktu, ancak bu, ui yönlendiriciye çok yardımcı oldu! Teşekkürler :)
imsheth

1

CSS'nizin yalnızca belirli bir görünüme uygulanmasına ihtiyacınız varsa , denetleyicimin içindeki şu kullanışlı parçacığı kullanıyorum:

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

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

Bu body, durum yüklendiğinde etiketime bir sınıf ekleyecek ve durum yok edildiğinde (yani birisi sayfaları değiştirdiğinde) onu kaldıracak. Bu, benim uygulamamda yalnızca CSS'nin tek bir duruma uygulanmasıyla ilgili sorunumu çözer.


0

'sıkı kullanın'; 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'
                                    ]
                                });
                            }
                        ]
                    }
                })
                 );

Bağlam içermeyen basit bir kod örneği, bir soruya nadiren yeterli bir cevaptır. Dahası, bu sorunun zaten oldukça kabul gören bir cevabı var.
AJ X.
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.