AngularJSにビュー/部分的な特定のスタイリングを含める方法


132

アプリケーションが使用するさまざまなビューに個別のスタイルシートを使用する適切な/受け入れられる方法は何ですか?

現在、view / partialのhtmlの上部にlink要素を配置していますが、最近のすべてのブラウザーがそれをサポートしているにもかかわらず、これは悪い習慣であると言われていますが、それが不快な理由がわかります。

他の可能性は、index.htmlに個別のスタイルシートを配置するheadことですが、パフォーマンスの名前でビューがロードされている場合にのみ、スタイルシートをロードします。

CSSがサーバーから読み込まれるまでスタイリングが有効にならず、遅いブラウザーでフォーマットされていないコンテンツがすばやくフラッシュするので、これは悪い習慣ですか?ローカルでテストしているのですが、これはまだ見ていません。

Angularに渡されたオブジェクトを介してCSSをロードする方法はあり$routeProvider.whenますか?

前もって感謝します!


「フォーマットされていないコンテンツの迅速なフラッシュ」アサーションを検証しました。私<link>この形式でcss タグを使用しました。最新のChrome、ローカルマシン上のサーバー(および「最初の読み込み」条件をシミュレートするために「キャッシュを無効にする」)を使用しました。<style>サーバーのhtmlパーシャルにタグを事前に挿入すると、この問題を回避できると思います。
2015年

回答:


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オブジェクト(ユーザーが出ようとしているルート)を取得し、その<head>タグの部分固有のcssファイルを削除します。また、「次の」$$routeオブジェクト(ユーザーが移動しようとしているルート)を取得し、その部分固有のcssファイルを<head>タグに追加します。
  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つの作業により、必要なものがすべてセットアップされ、私の考えでは、可能な限り最もクリーンなコードでそれが実行されます。

私と同じくらいこの問題に苦労している可能性がある他の誰かを助けることを願っています。


2
聖なるモリー、これをありがとう!まさに私が探していたもの:)。今それをテストしただけで、完全に動作します(簡単に実装できます)。たぶん、あなたはこれのプルリクエストを作成してコアに入れるべきです。AngularJSの担当者がスコープ付きCSSを検討していたことを知っていますが、これは正しい方向への一歩かもしれませんか?
smets.kevin

それらの人たちは私よりもずっと賢いです。彼らは以前にこの(または同様の)ソリューションを考え、何らかの理由でコアに実装しないことを選択したと思います。
Tennisgent

cssファイルの正しい場所はどこですか?css: 'css / partial1.css'は、角度付きアプリフォルダーのルートにあるcssフォルダーを意味しますか?
Cordle、2014年

それはあなたのindex.htmlファイルに関連しています。したがって、上記の例index.htmlでは、ルートにあり、cssフォルダはすべてのcssファイルを含むルートにあります。ただし、正しい相対パスを使用している限り、アプリは好きなように構成できます。
Tennisgent 14年

1
@Kappys、新しいビューに移動すると、スクリプトは前のビューのスタイルを削除します。これを実行したくない場合は、ディレクティブから次のコードを削除してくださいangular.forEach(current.$$route.css, function(sheet){ delete scope.routeStyles[sheet]; });
テニスジェント2015

34

@tennisgentのソリューションは素晴らしいです。ただし、少し限定的だと思います。

Angularのモジュール性とカプセル化はルートを超えています。Webがコンポーネントベースの開発に移行する方法に基づいて、ディレクティブでこれを適用することも重要です。

すでにご存じのように、Angularでは、ページとコンポーネントにテンプレート(構造)とコントローラー(動作)を含めることができます。AngularCSSは、最後に欠けている要素であるスタイルシート(プレゼンテーション)の添付を可能にします。

完全なソリューションについては、AngularCSSの使用をお勧めします。

  1. AngularのngRoute、UIルーター、ディレクティブ、コントローラー、サービスをサポートします。
  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


1
私はここであなたのアプローチが本当に好きですが、すべてのcssスタイルを一緒に連結する必要があるプロダクションアプリでそれをどのように使用できるのか疑問に思いましたか?HTMLテンプレートの場合、本番コードには$ templateCache.put()を使用します。CSSでも同様の処理を行うとよいでしょう。
Tom Makin

サーバーから連結されたCSSを取得する必要がある場合、/ getCss?files = file1(.css)、file2、file3のようなことをいつでも行うことができ、サーバーは指定された順序で連結された3つのファイルすべてで応答します。
Petr Urban

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>も、index.htmlをの静的には?
ブランドン

whenルートのが呼び出されていない場合は例外です。このコードを置くことができるcontrollerのコールバックwhen内でrouteProvider、あるいはおそらく内部resolve早くそうなコールバックのトリガー
charlietfl

ああ大丈夫、私の悪い、それはクリックしない。とにかくそれを注入している場合にプリロードがどのように行われるかを説明してもらえますか?
ブランドン、

1
追加した場合はプリロードされませんrouteprovider...そのコメントは、ページが提供されるときにメインページの先頭に含めることに関するもの
でし

-_-申し訳ありません、あなたが言うことができない場合、私は睡眠不足です。とにかく、それが今の私です。すべてのスタイルシートを一度にロードするオーバーヘッドが、ユーザーがビューを切り替えるときにFOUCを使用するよりも優れているかどうかを把握しようとします。WebアプリのUXの場合と同じように、Angularに関連する問題ではないようです。おかげで、もし私がプリロードをしないと決めたなら、私はおそらくあなたの提案に行くでしょう。
ブランドン

5

@ sz3、おもしろい今日、私はあなたが達成しようとしていたことを正確に行わなければなりませんでした: ' ユーザーが特定のページにアクセスしたときにのみ特定のCSSファイルをロードします。したがって、上記のソリューションを使用しました。

しかし、私はあなたの最後の質問に答えるためにここにいます: ' 正確にどこにコードを置くべきですか?何かアイデアは?」

コードをresolveに含めるのは正しかったですが、形式を少し変更する必要があります。

以下のコードを見てください。

.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」ルートに到達したときにのみ「home.css」をロードします。

完全な説明はここにありますが、基本的に解決します:オブジェクトをフォーマットで取得する必要があります

{
  'key' : string or function()
} 

' key 'には好きな名前を付けることができます-私の場合は ' style ' と呼びます。

次に、値に対して2つのオプションがあります。

  • 文字列の場合は、サービスのエイリアスです。

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

私のcssがめちゃくちゃになっていたので、すべての場所で削除と追加を正確に行う必要はありませんでしたが、これはui-routerに非常に役立ちました。ありがとう:)
imsheth

1

CSSを特定の1つのビューにのみ適用する必要がある場合は、コントローラー内で次の便利なスニペットを使用しています。

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

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

これによりbody、状態が読み込まれたときにクラスがタグに追加され、状態が破棄されたときに(つまり、誰かがページを変更したときに)クラスが削除されます。これにより、アプリケーションの1つの状態にCSSを適用するだけで済むという関連する問題が解決されます。


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

コンテキストのない単純なコード例が、質問に対する十分な回答になることはほとんどありません。さらに、この質問はすでに非常に受け入れられた答えを持っています。
AJX。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.