Cara menyertakan tampilan / penataan khusus sebagian dalam AngularJS

132

Apa cara yang tepat / diterima untuk menggunakan stylesheet terpisah untuk berbagai tampilan yang digunakan aplikasi saya?

Saat ini saya menempatkan elemen tautan di tampilan / html parsial di bagian atas tetapi saya telah diberitahu ini adalah praktik yang buruk meskipun semua browser modern mendukungnya tetapi saya dapat melihat mengapa itu disukai.

Kemungkinan lain adalah menempatkan stylesheet yang terpisah di index.html saya, headtetapi saya ingin itu hanya memuat stylesheet jika pandangannya dimuat atas nama kinerja.

Apakah praktik buruk ini karena penataan tidak akan berlaku hingga setelah css dimuat dari server, yang mengarah ke flash cepat konten yang tidak diformat dalam browser yang lambat? Saya belum menyaksikan ini meskipun saya sedang mengujinya secara lokal.

Apakah ada cara untuk memuat CSS melalui objek yang diteruskan ke Angular $routeProvider.when?

Terima kasih sebelumnya!

Brandon
sumber
Saya memvalidasi pernyataan "flash cepat konten yang tidak diformat". Saya menggunakan <link>tag css dalam format ini , dengan Chrome terbaru, server di mesin lokal saya (dan "Nonaktifkan cache" untuk mensimulasikan kondisi "memuat pertama"). Saya membayangkan bahwa pra-menyisipkan <style>tag di parsial html di server akan menghindari masalah ini.
poshest

Jawaban:

150

Saya tahu pertanyaan ini sudah tua sekarang, tetapi setelah melakukan banyak penelitian tentang berbagai solusi untuk masalah ini, saya pikir saya mungkin telah menemukan solusi yang lebih baik.

UPDATE 1: Sejak memposting jawaban ini, saya telah menambahkan semua kode ini ke layanan sederhana yang telah saya posting ke GitHub. Repo itu berada di sini . Jangan ragu untuk memeriksanya untuk info lebih lanjut.

UPDATE 2: Jawaban ini bagus jika yang Anda butuhkan adalah solusi ringan untuk menarik stylesheet untuk rute Anda. Jika Anda ingin solusi yang lebih lengkap untuk mengelola stylesheet on-demand di seluruh aplikasi Anda, Anda mungkin ingin checkout proyek AngularCSS Door3 . Ini memberikan fungsionalitas yang lebih halus.

Jika ada orang di masa depan yang tertarik, inilah yang saya temukan:

1. Buat arahan khusus untuk <head>elemen:

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

Arahan ini melakukan hal-hal berikut:

  1. Ini mengkompilasi (menggunakan $compile) string html yang membuat satu set <link />tag untuk setiap item dalam scope.routeStylesobjek menggunakan ng-repeatdan ng-href.
  2. Ini menambahkan set <link />elemen yang dikompilasi ke <head>tag.
  3. Ini kemudian menggunakan $rootScopeuntuk mendengarkan '$routeChangeStart'acara. Untuk setiap '$routeChangeStart'peristiwa, ia mengambil objek "saat ini" $$route(rute yang akan ditinggalkan pengguna) dan menghapus file css parsial-spesifik dari <head>tag. Ia juga mengambil objek "berikutnya" $$route(rute yang akan dituju pengguna) dan menambahkan file css parsial-spesifik ke <head>tag.
  4. Dan ng-repeatbagian dari <link />tag yang dikompilasi menangani semua penambahan dan penghapusan stylesheet khusus halaman berdasarkan apa yang ditambahkan atau dihapus dari scope.routeStylesobjek.

Catatan: ini mensyaratkan bahwa ng-appatribut Anda ada pada <html>elemen, bukan pada <body>atau apa pun di dalamnya <html>.

2. Tentukan stylesheet mana yang termasuk dalam rute yang menggunakan $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']
        })
}]);

Konfigurasi ini menambahkan cssproperti kustom ke objek yang digunakan untuk mengatur rute setiap halaman. Objek itu diteruskan ke setiap '$routeChangeStart'peristiwa sebagai .$$route. Jadi saat mendengarkan '$routeChangeStart'acara, kita dapat mengambil cssproperti yang telah kita tentukan dan menambahkan / menghapus <link />tag tersebut sesuai kebutuhan. Perhatikan bahwa menentukan cssproperti pada rute sepenuhnya opsional, karena dihilangkan dari '/some/route/2'contoh. Jika rute tidak memiliki cssproperti, <head>arahan hanya akan melakukan apa pun untuk rute itu. Perhatikan juga bahwa Anda bahkan dapat memiliki beberapa stylesheet khusus halaman per rute, seperti pada '/some/route/3'contoh di atas, di mana cssproperti adalah array jalur relatif ke stylesheet yang diperlukan untuk rute itu.

3. Anda selesai Kedua hal mengatur semua yang diperlukan dan itu melakukannya, menurut saya, dengan kode terbersih mungkin.

Semoga itu bisa membantu orang lain yang mungkin berjuang dengan masalah ini sebanyak saya.

tennisgent
sumber
2
Astaga, terima kasih untuk ini! Persis apa yang saya cari :). Hanya mengujinya sekarang dan berfungsi dengan baik (ditambah mudah diimplementasikan). Mungkin Anda harus membuat permintaan tarik untuk ini dan membawanya ke inti. Saya tahu orang-orang dari AngularJS sedang mencari css, ini bisa menjadi langkah ke arah yang benar?
smets.kevin
Orang-orang itu jauh lebih pintar daripada aku. Saya yakin mereka akan memikirkan solusi ini (atau yang serupa) sebelumnya dan memilih untuk tidak mengimplementasikannya ke dalam inti untuk alasan apa pun.
tennisgent
Apa tempat yang benar untuk file css? Apakah css: 'css / partial1.css' menyiratkan folder css di root folder app sudut?
Cordle
Ini relatif terhadap index.htmlfile Anda . Jadi pada contoh di atas, index.htmlakan berada di root dan cssfolder akan di root, berisi semua file css. tetapi Anda dapat menyusun aplikasi sesuai keinginan Anda, selama Anda menggunakan jalur relatif yang benar.
tennisgent
1
@Kappys, skrip menghapus gaya untuk tampilan sebelumnya ketika Anda pindah ke tampilan baru. Jika Anda tidak ingin itu terjadi, cukup menghapus kode berikut dari direktif: angular.forEach(current.$$route.css, function(sheet){ delete scope.routeStyles[sheet]; });.
Lapangan tenis
34

Solusi @ tennisgent sangat bagus. Namun, saya pikir agak terbatas.

Modularitas dan Enkapsulasi di Angular melampaui rute. Berdasarkan cara web bergerak menuju pengembangan berbasis komponen, penting untuk menerapkan ini dalam arahan juga.

Seperti yang sudah Anda ketahui, di Angular kami dapat menyertakan templat (struktur) dan pengontrol (perilaku) di halaman dan komponen. AngularCSS memungkinkan bagian yang hilang terakhir: melampirkan stylesheet (presentasi).

Untuk solusi lengkap saya sarankan menggunakan AngularCSS.

  1. Mendukung ngRoute, Router UI, arahan, pengontrol, dan layanan Angular.
  2. Tidak perlu ada ng-appdalam <html>tag. Ini penting ketika Anda memiliki beberapa aplikasi yang berjalan di halaman yang sama
  3. Anda dapat menyesuaikan di mana stylesheet disuntikkan: kepala, tubuh, pemilih kustom, dll ...
  4. Mendukung preloading, persisting, dan cache cache
  5. Mendukung kueri media dan mengoptimalkan pemuatan laman melalui matchMedia API

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

Berikut ini beberapa contohnya:

Rute

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

Arahan

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

Selain itu, Anda dapat menggunakan $csslayanan untuk kasus tepi:

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();

});

Anda dapat membaca lebih lanjut tentang AngularCSS di sini:

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

castillo.io
sumber
1
Saya sangat suka pendekatan Anda di sini, tetapi bertanya-tanya bagaimana itu bisa digunakan dalam aplikasi produksi di mana semua gaya css perlu digabungkan bersama? Untuk templat html saya menggunakan $ templateCache.put () untuk kode produksi dan alangkah baiknya melakukan sesuatu yang serupa untuk css.
Tom Makin
Jika Anda perlu mendapatkan Rangkaian CSS dari server, Anda selalu dapat melakukan sesuatu seperti /getCss?files=file1(.css),file2,file3 dan server akan merespons dengan semua 3 file dalam urutan yang diberikan dan digabungkan.
Petr Urban
13

Bisa menambahkan stylesheet baru ke dalam $routeProvider. Untuk kesederhanaan saya menggunakan string tetapi bisa membuat elemen tautan baru juga, atau membuat layanan untuk stylesheet

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

Manfaat terbesar prelaoding di halaman adalah gambar latar belakang apa pun sudah ada, dan kurang lieklyhood FOUC

charlietfl
sumber
Bukankah ini akan mencapai hal yang sama dengan hanya memasukkan <link>di <head>dalam index.html secara statis?
Brandon
tidak jika whenuntuk rute belum dipanggil. Dapat memasukkan kode ini dalam controllerpanggilan balik whendalam routeProvider, atau mungkin dalam resolvepanggilan balik yang kemungkinan akan memicu lebih cepat
charlietfl
Oh oke, salahku, itu klik tidak. Terlihat cukup solid kecuali bisakah Anda menjelaskan bagaimana preloadingnya jika saya menyuntikkannya?
Brandon
1
itu tidak preloading jika Anda menambahkannya di routeprovider... komentar itu tentang memasukkannya di kepala halaman utama ketika halaman disajikan
charlietfl
-_- maaf, saya kurang tidur jika Anda tidak tahu. Ngomong-ngomong, di situlah aku berada sekarang. Mencoba mencari tahu apakah overhead pemuatan semua stylesheet saya sekaligus lebih baik daripada memiliki beberapa FOUC ketika pengguna beralih tampilan. Saya kira itu bukan pertanyaan terkait sudut seperti tentang aplikasi web UX. Terima kasih, saya mungkin akan pergi dengan saran Anda jika saya memutuskan untuk tidak melakukan preloading.
Brandon
5

@ sz3, cukup lucu hari ini saya harus melakukan persis apa yang Anda coba capai: ' memuat file CSS tertentu hanya ketika pengguna mengakses ' halaman tertentu. Jadi saya menggunakan solusi di atas.

Tetapi saya di sini untuk menjawab pertanyaan terakhir Anda: 'di mana tepatnya saya harus meletakkan kodenya. Ada ide ? '

Anda benar memasukkan kode ke tekad , tetapi Anda perlu mengubah sedikit format.

Lihatlah kode di bawah ini:

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

Saya baru saja menguji dan berfungsi dengan baik , menyuntikkan html dan memuat 'home.css' saya hanya ketika saya menekan rute '/ home'.

Penjelasan lengkap dapat ditemukan di sini , tetapi pada dasarnya menyelesaikan: harus mendapatkan objek dalam format

{
  'key' : string or function()
} 

Anda dapat memberi nama ' kunci ' apa pun yang Anda suka - dalam kasus saya, saya menyebutnya ' gaya '.

Kemudian untuk nilai Anda memiliki dua opsi:

  • Jika sebuah string , maka itu adalah alias untuk layanan.

  • Jika fungsinya , maka itu disuntikkan dan nilai kembali diperlakukan sebagai ketergantungan.

Poin utama di sini adalah bahwa kode di dalam fungsi akan dieksekusi sebelum sebelum controller dipakai dan event $ routeChangeSuccess dipecat.

Semoga itu bisa membantu.

Denison Luz
sumber
2

Luarbiasa, terimakasih!! Hanya perlu membuat beberapa penyesuaian agar bisa berfungsi dengan 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
sumber
Saya tidak benar-benar perlu menghapus dan menambahkan di mana-mana karena css saya kacau, tapi ini sangat membantu dengan ui-router! Terima kasih :)
imsheth
1

Jika Anda hanya perlu CSS untuk diterapkan pada satu tampilan tertentu, saya menggunakan cuplikan praktis ini di dalam controller saya:

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

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

Ini akan menambahkan kelas ke bodytag saya ketika status dimuat, dan menghapusnya ketika status dihancurkan (yaitu seseorang mengubah halaman). Ini memecahkan masalah terkait saya hanya membutuhkan CSS untuk diterapkan ke satu negara dalam aplikasi saya.

Mat
sumber
0

'gunakan yang ketat'; angular.module ('app') .run (['$ rootScope', '$ state', '$ stateParams', fungsi ($ rootScope, $ state, $ stateParams) {$ rootScope. $ state = $ state; $ rootScope . $ stateParams = $ stateParams;}]) .config (['$ stateProvider', '$ urlRouterProvider', fungsi ($ 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
sumber
Contoh kode sederhana tanpa konteks jarang merupakan jawaban yang memadai untuk suatu pertanyaan. Selanjutnya, pertanyaan ini sudah memiliki jawaban yang sangat diterima.
AJ X.