Come includere la vista / lo stile specifico parziale in AngularJS


132

Qual è il modo corretto / accettato di utilizzare fogli di stile separati per le varie visualizzazioni utilizzate dalla mia applicazione?

Attualmente sto posizionando un elemento di collegamento nell'html della vista / parziale in alto, ma mi è stato detto che questa è una cattiva pratica anche se tutti i browser moderni lo supportano, ma posso vedere perché è disapprovato.

L'altra possibilità è quella di inserire fogli di stile separati nei miei indici.html, headma vorrei che caricasse il foglio di stile solo se la sua vista veniva caricata in nome della performance.

È una cattiva pratica poiché lo styling non avrà effetto fino a quando il css non viene caricato dal server, portando a un rapido flash di contenuti non formattati in un browser lento? Devo ancora testimoniare questo anche se lo sto testando localmente.

Esiste un modo per caricare il CSS attraverso l'oggetto passato a Angular $routeProvider.when ?

Grazie in anticipo!


Ho convalidato la tua affermazione "flash rapido di contenuti non formattati". Ho usato i <link>tag CSS in questo formato , con l'ultimo Chrome, il server sul mio computer locale (e "Disabilita cache" per simulare le condizioni di "primo caricamento"). Immagino che pre-inserire un <style>tag nel parziale html sul server eviterebbe questo problema.
più elegante il

Risposte:


150

So che questa domanda è vecchia ora, ma dopo aver fatto un sacco di ricerche su varie soluzioni a questo problema, penso che potrei aver trovato una soluzione migliore.

AGGIORNAMENTO 1: Da quando ho pubblicato questa risposta, ho aggiunto tutto questo codice a un semplice servizio che ho pubblicato su GitHub. Il repository si trova qui . Sentiti libero di provarlo per maggiori informazioni.

AGGIORNAMENTO 2: questa risposta è ottima se tutto ciò di cui hai bisogno è una soluzione leggera per inserire fogli di stile per i tuoi percorsi. Se si desidera una soluzione più completa per la gestione dei fogli di stile su richiesta in tutta l'applicazione, è possibile verificare il progetto AngularCSS di Door3 . Fornisce funzionalità molto più dettagliate.

Nel caso qualcuno in futuro fosse interessato, ecco cosa mi è venuto in mente:

1. Creare una direttiva personalizzata per l' <head>elemento:

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

Questa direttiva fa le seguenti cose:

  1. Compila (usando $compile) una stringa html che crea un set di <link />tag per ogni elemento scope.routeStylesnell'oggetto usando ng-repeateng-href .
  2. Aggiunge il set di <link />elementi compilato al <head>tag.
  3. Quindi utilizza il $rootScopeper ascoltare gli '$routeChangeStart'eventi. Per ogni '$routeChangeStart'evento, prende l' $$routeoggetto "corrente" (la rotta che l'utente sta per lasciare) e rimuove i suoi file CSS specifici parziale dal <head>tag. Afferra anche l' $$routeoggetto "successivo" (la rotta verso cui l'utente sta per andare) e aggiunge uno dei suoi file css specifici al parziale al <head>tag.
  4. E la ng-repeatparte del <link />tag compilato gestisce tutta l'aggiunta e la rimozione dei fogli di stile specifici della pagina in base a ciò che viene aggiunto o rimosso scope.routeStylesdall'oggetto.

Nota: ciò richiede che l' ng-appattributo sia <html>sull'elemento, non su <body>o all'interno di esso <html>.

2. Specificare quali fogli di stile appartengono a quali percorsi utilizzando $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']
        })
}]);

Questa configurazione aggiunge una cssproprietà personalizzata all'oggetto che viene utilizzata per impostare il percorso di ciascuna pagina. Quell'oggetto viene passato a ciascun '$routeChangeStart'evento come .$$route. Quindi, quando ascoltiamo l' '$routeChangeStart'evento, possiamo prendere la cssproprietà che abbiamo specificato e aggiungere / rimuovere quei <link />tag secondo necessità. Si noti che specificare una cssproprietà sul percorso è completamente facoltativo, poiché è stato omesso '/some/route/2'dall'esempio. Se la route non ha una cssproprietà, la <head>direttiva semplicemente non farà nulla per quella route. Nota anche che puoi anche avere più fogli di stile specifici per pagina per rotta, come '/some/route/3'nell'esempio sopra, dove ilcss proprietà è una matrice di percorsi relativi ai fogli di stile necessari per quella rotta.

3. Il gioco è fatto Queste due cose hanno impostato tutto ciò che era necessario e lo fa, secondo me, con il codice più pulito possibile.

Spero che possa aiutare qualcun altro che potrebbe avere problemi con questo problema tanto quanto me.


2
Santo cielo, grazie per questo! Esattamente quello che stavo cercando :). L'ho appena testato e funziona perfettamente (più facile da implementare). Forse dovresti creare una richiesta pull per questo e metterlo nel core. So che i ragazzi di AngularJS stavano esaminando il css con ambito, questo potrebbe essere un passo nella giusta direzione?
smets.kevin

Quei ragazzi sono molto più intelligenti di me. Sono sicuro che avrebbero già pensato a questa (o una soluzione simile) prima e avrebbero scelto di non implementarla nel nucleo per qualsiasi motivo.
tennisgent,

Qual è il posto corretto per il file css? Css: 'css / partial1.css' implica la cartella css nella radice della cartella dell'app angolare?
Cordle,

È relativo al tuo index.htmlfile. Quindi nell'esempio sopra, index.htmlsarebbe nella radice e la csscartella nella radice, contenente tutti i file CSS. ma puoi strutturare la tua app come desideri, purché utilizzi i percorsi relativi corretti.
tennisgent

1
@Kappys, lo script rimuove lo stile per la vista precedente quando si passa a una nuova vista. Se non si desidera che ciò accada, è sufficiente rimuovere il codice seguente dalla direttiva: angular.forEach(current.$$route.css, function(sheet){ delete scope.routeStyles[sheet]; });.
tennisgent,

34

La soluzione di @ tennisgent è fantastica. Tuttavia, penso che sia un po 'limitato.

La modularità e l'incapsulamento in angolare vanno oltre le rotte. In base al modo in cui il Web si sta muovendo verso lo sviluppo basato su componenti, è importante applicare anche questo nelle direttive.

Come già sapete, in Angolare possiamo includere modelli (struttura) e controller (comportamento) in pagine e componenti. AngularCSS abilita l'ultimo pezzo mancante: allegare fogli di stile (presentazione).

Per una soluzione completa suggerisco di usare AngularCSS.

  1. Supporta ngRoute, router UI, direttive, controller e servizi di Angular.
  2. Non è necessario avere ng-appnel <html>tag. Questo è importante quando hai più app in esecuzione sulla stessa pagina
  3. Puoi personalizzare il punto in cui vengono iniettati i fogli di stile: testa, corpo, selettore personalizzato, ecc ...
  4. Supporta il precaricamento, la persistenza e il busting della cache
  5. Supporta query multimediali e ottimizza il caricamento della pagina tramite l'API matchMedia

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

Ecco alcuni esempi:

Itinerari

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

direttive

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

Inoltre, è possibile utilizzare il $cssservizio per casi limite:

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

});

Puoi leggere di più su AngularCSS qui:

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


1
Mi piace molto il tuo approccio qui, ma mi chiedevo come potesse essere utilizzato in un'app di produzione in cui tutti gli stili CSS devono essere concatenati insieme? Per i modelli HTML utilizzo $ templateCache.put () per il codice di produzione e sarebbe bello fare qualcosa di simile per css.
Tom Makin,

Se hai bisogno di ottenere CSS concatenati dal server, puoi sempre fare qualcosa come /getCss?files=file1(.css),file2,file3 e il server risponderebbe con tutti e 3 i file in un determinato ordine e concatenato.
Petr Urban,

13

Potrebbe aggiungere un nuovo foglio di stile all'interno $routeProvider. Per semplicità sto usando una stringa ma potrebbe anche creare un nuovo elemento di collegamento o creare un servizio per fogli di stile

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

Il più grande vantaggio del precampionamento nella pagina è che qualsiasi immagine di sfondo esisterà già, e meno verosimiglianza FOUC


Non sarebbe questo ottenere la stessa cosa come solo incluso il <link>nel <head>del index.html staticamente, però?
Brandon

non se il whenper il percorso non è stato chiamato. Può mettere questo codice nel controllercallback whenall'interno routeProvidero forse nel resolvecallback che probabilmente si innescherà prima
charlietfl

Oh okay, mio ​​male, che fa clic su no. Sembra piuttosto solido tranne che potresti spiegare come è precaricato se lo sto iniettando quando comunque?
Brandon

1
non è precaricato se lo aggiungi in routeprovider... quel commento riguardava l'inclusione in testa alla pagina principale quando la pagina viene pubblicata
charlietfl

-_- scusa, mi manca il sonno se non puoi dirlo. Comunque, è un po 'dove sono adesso. Cercare di capire se il sovraccarico di caricare tutti i miei fogli di stile in una volta è meglio che avere un FOUC quando l'utente cambia visualizzazione. Immagino che in realtà non sia una domanda relativa ad Angular tanto quanto riguarda la UX dell'app web. Grazie comunque, probabilmente seguirò il tuo suggerimento se decido di non effettuare il precarico.
Brandon

5

@ sz3, oggi abbastanza divertente dovevo fare esattamente quello che stavi cercando di ottenere: " caricare un file CSS specifico solo quando un utente accede " a una pagina specifica. Quindi ho usato la soluzione sopra.

Ma sono qui per rispondere alla tua ultima domanda: ' dove esattamente dovrei inserire il codice. Qualche idea ?

Avevi ragione includendo il codice nella risoluzione , ma devi cambiare un po 'il formato.

Dai un'occhiata al codice qui sotto:

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

Ho appena testato e funziona bene , inietta l'html e carica il mio 'home.css' solo quando ho colpito il percorso '/ home'.

La spiegazione completa può essere trovata qui , ma sostanzialmente risolve: dovrebbe ottenere un oggetto nel formato

{
  'key' : string or function()
} 

Puoi nominare la ' chiave ' come preferisci - nel mio caso ho chiamato ' stile '.

Quindi per il valore hai due opzioni:

  • Se è una stringa , allora è un alias per un servizio.

  • Se è una funzione , viene iniettato e il valore restituito viene trattato come dipendenza.

Il punto principale qui è che il codice all'interno della funzione verrà eseguito prima dell'istanza del controller e dell'evento $ routeChangeSuccess.

Spero che aiuti.


2

Fantastico grazie!! Ho dovuto solo apportare alcune modifiche per farlo funzionare con 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);
                });
            });
        }
    }
}]);

Non ho esattamente bisogno di rimuovere e aggiungere ovunque poiché il mio CSS era incasinato, ma questo è stato di grande aiuto con l'interfaccia utente! Grazie :)
imsheth

1

Se hai solo bisogno che il tuo CSS sia applicato a una vista specifica, sto usando questo pratico frammento all'interno del mio controller:

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

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

Ciò aggiungerà una classe al mio bodytag quando lo stato viene caricato e rimossa quando lo stato viene distrutto (ovvero se qualcuno cambia pagina). Questo risolve il mio problema relativo alla necessità di applicare solo CSS a uno stato nella mia applicazione.


0

'usa rigoroso'; angular.module ('app') .run (['$ rootScope', '$ state', '$ stateParams', funzione ($ rootScope, $ state, $ stateParams) {$ rootScope. $ state = $ state; $ rootScope . $ stateParams = $ stateParams;}]) .config (['$ stateProvider', '$ urlRouterProvider', funzione ($ 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'
                                    ]
                                });
                            }
                        ]
                    }
                })
                 );

Un semplice esempio di codice senza contesto è raramente una risposta sufficiente a una domanda. Inoltre, questa domanda ha già una risposta molto accettata.
AJ X.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.