web-dev-qa-db-fra.com

Comment inclure un style spécifique partiel ou de vue dans AngularJS

Quelle est la manière appropriée/acceptée d’utiliser des feuilles de style distinctes pour les différentes vues utilisées par mon application?

Actuellement, je place un élément de lien dans le code HTML de view/partial en haut, mais on m'a dit que c'était une mauvaise pratique, même si tous les navigateurs modernes le supportent, mais je comprends pourquoi il est mal vu.

L'autre possibilité consiste à placer les différentes feuilles de style dans le fichier head de mon index.html, mais j'aimerais qu'il ne charge la feuille de style que si sa vue est chargée au nom de performance.

Est-ce une mauvaise pratique puisque le style ne prendra effet qu'après le chargement du fichier css sur le serveur, ce qui entraînera un flash rapide de contenu non formaté dans un navigateur lent? Je n'ai pas encore été témoin de cela bien que je le teste localement.

Existe-t-il un moyen de charger le code CSS via l'objet transmis à $routeProvider.when d'Angular?

Merci d'avance!

131
Brandon

Je sais que cette question est ancienne maintenant, mais après avoir fait une tonne de recherches sur diverses solutions à ce problème, je pense avoir trouvé une meilleure solution.

UPDATE 1: Depuis la publication de cette réponse, j'ai ajouté l'ensemble de ce code à un service simple que j'ai publié pour GitHub. Le repo est situé ici . N'hésitez pas à y jeter un coup d'œil pour plus d'informations.

UPDATE 2: Cette réponse est excellente si tout ce dont vous avez besoin est une solution légère pour extraire des feuilles de style pour vos itinéraires. Si vous souhaitez une solution plus complète pour la gestion des feuilles de style à la demande tout au long de votre application, vous pouvez souhaiter utiliser projet AngularCSS de Door . Il fournit une fonctionnalité beaucoup plus fine.

Au cas où quelqu'un dans le futur serait intéressé, voici ce que j'ai proposé:

1. Créez une directive personnalisée pour l'élément _<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;
                        });
                    }
                });
            }
        };
    }
]);
_

Cette directive fait les choses suivantes:

  1. Il compile (à l'aide de $compile ) une chaîne HTML créant un ensemble de balises _<link />_ pour chaque élément de l'objet _scope.routeStyles_ à l'aide de _ng-repeat_ et _ng-href_.
  2. Il ajoute cet ensemble compilé d'éléments _<link />_ à la balise _<head>_.
  3. Il utilise ensuite le _$rootScope_ pour écouter les événements _'$routeChangeStart'_. Pour chaque événement _'$routeChangeStart'_, il saisit l'objet "en cours" _$$route_ (la route que l'utilisateur est sur le point de quitter) et supprime le ou les fichiers css spécifiques partiels de _<head>_. étiquette. Il saisit également le "prochain" objet _$$route_ (l'itinéraire auquel l'utilisateur est sur le point de se rendre) et ajoute l'un de ses fichiers css spécifiques partiels à la balise _<head>_.
  4. Et la partie _ng-repeat_ de la balise compilée _<link />_ gère l’ajout et la suppression des feuilles de style spécifiques à la page en fonction des éléments ajoutés ou supprimés de l’objet _scope.routeStyles_.

Remarque: pour que votre attribut _ng-app_ soit présent sur l'élément _<html>_ et non sur _<body>_ ou quelque chose à l'intérieur de _<html>_.

2. Spécifiez les feuilles de style appartenant à quels itinéraires à l'aide de _$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']
        })
}]);
_

Cette configuration ajoute une propriété personnalisée css à l'objet utilisé pour configurer l'itinéraire de chaque page. Cet objet est transmis à chaque événement _'$routeChangeStart'_ en tant que _.$$route_. Ainsi, lors de l'écoute de l'événement _'$routeChangeStart'_, nous pouvons récupérer la propriété css que nous avons spécifiée et ajouter/supprimer ces balises _<link />_ selon les besoins. Notez que la spécification d'une propriété css sur l'itinéraire est totalement facultative, car elle a été omise de l'exemple _'/some/route/2'_. Si la route n'a pas de propriété css, la directive _<head>_ ne fera tout simplement rien pour cette route. Notez également que vous pouvez même avoir plusieurs feuilles de style spécifiques à une page par route, comme dans l'exemple _'/some/route/3'_ ci-dessus, où la propriété css est un tableau de chemins d'accès relatifs aux feuilles de style nécessaires pour cette route.

3. Vous avez terminé Ces deux choses configurent tout ce qui était nécessaire et le font, à mon avis, avec le code le plus propre possible.

J'espère que cela aidera quelqu'un d'autre qui pourrait être aux prises avec ce problème autant que moi.

149
tennisgent

La solution de @ tennisgent est excellente. Cependant, je pense que c'est un peu limité.

La modularité et l’encapsulation dans Angular vont au-delà des routes. Compte tenu de l'évolution du Web vers le développement par composant, il est également important de l'appliquer également dans les directives.

Comme vous le savez déjà, dans Angular, nous pouvons inclure des modèles (structure) et des contrôleurs (comportement) dans des pages et des composants. AngularCSS active la dernière pièce manquante: attacher des feuilles de style (présentation).

Pour une solution complète, je suggère d'utiliser AngularCSS.

  1. Prise en charge de ngRoute, du routeur d'interface utilisateur, des directives, des contrôleurs et des services d'Angular.
  2. N'a pas besoin d'avoir ng-app dans la balise <html>. Ceci est important lorsque plusieurs applications s'exécutent sur la même page.
  3. Vous pouvez personnaliser l'endroit où les feuilles de style sont injectées: tête, corps, sélecteur personnalisé, etc.
  4. Prend en charge le préchargement, la persistance et le contournement du cache
  5. Prise en charge des requêtes multimédia et optimisation du chargement des pages via l'API matchMedia

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

Voici quelques exemples:

Itinéraires

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

Directives

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

De plus, vous pouvez utiliser le service $css pour les cas Edge:

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

});

Vous pouvez en savoir plus sur AngularCSS ici:

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

34
castillo.io

Pourrait ajouter une nouvelle feuille de style en tête dans $routeProvider. Pour plus de simplicité, utilisez une chaîne, mais vous pouvez également créer un nouvel élément de lien ou créer un service pour les feuilles de style.

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

Le plus grand avantage de prelaoding dans la page est que toutes les images de fond existeront déjà, et moins lieklyhood of FOUC

13
charlietfl

@ sz3, assez drôle aujourd'hui, je devais faire exactement ce que vous essayiez de faire: ' charge un fichier CSS spécifique uniquement lorsqu'un utilisateur accède 'une page spécifique. J'ai donc utilisé la solution ci-dessus.

Mais je suis ici pour répondre à votre dernière question: ' où exactement dois-je mettre le code. Des idées ? '

Vous aviez raison d'inclure le code dans la résolution , mais vous devez changer un peu le format.

Regardez le code ci-dessous:

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

Je viens de tester et cela fonctionne bien , il injecte le code HTML et charge mon fichier "home.css" uniquement lorsque je clique sur "/ home" route.

Une explication complète peut être trouvée ici , mais fondamentalement résoudre: devrait obtenir un objet au format

{
  'key' : string or function()
} 

Vous pouvez nommer la touche '' comme vous le souhaitez - dans mon cas, j'ai appelé le style ' '.

Ensuite, pour la valeur, vous avez deux options:

  • Si c'est une chaîne , alors c'est un alias pour un service.

  • Si c'est la fonction , elle est injectée et la valeur de retour est traitée comme une dépendance.

Le point principal ici est que le code dans la fonction va être exécuté avant l'instanciation du contrôleur et que l'événement $ routeChangeSuccess ne soit déclenché.

J'espère que ça t'as aidé.

4
Denison Luz

Super merci!! Il suffit de faire quelques ajustements pour que cela fonctionne avec 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);
                });
            });
        }
    }
}]);
2
CraigM

Si vous avez seulement besoin que votre CSS soit appliqué à une vue spécifique, j'utilise cet extrait pratique dans mon contrôleur:

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

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

Cela ajoutera une classe à ma balise body lors du chargement de l’état et la supprimera lorsque l’état sera détruit (c’est-à-dire que quelqu'un change de page). Cela résout le problème lié à la nécessité de n’appliquer les CSS que dans un seul état de mon application.

1
Matt

'use strict'; angular.module ('app') .run (['$ rootScope', '$ state', '$ stateParams', fonction ($ rootScope, $ state, $ stateParams) {$ rootScope. $ state = $ state; $ rootScope . $ stateParams = $ stateParams;}]) .config (['$ StateProvider', '$ urlRouterProvider', fonction ($ 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'
                                    ]
                                });
                            }
                        ]
                    }
                })
                 );
0
rambaburoja