So fügen Sie Ansicht / teilspezifisches Styling in AngularJS ein

132

Was ist die richtige / akzeptierte Methode, um separate Stylesheets für die verschiedenen Ansichten zu verwenden, die meine Anwendung verwendet?

Momentan platziere ich ein Link-Element im HTML-Code der Ansicht / des Teils oben, aber mir wurde gesagt, dass dies eine schlechte Praxis ist, obwohl alle modernen Browser dies unterstützen, aber ich kann sehen, warum es verpönt ist.

Die andere Möglichkeit besteht darin, die separaten Stylesheets in meine index.html zu platzieren. headIch möchte jedoch, dass das Stylesheet nur geladen wird, wenn seine Ansicht im Namen der Leistung geladen wird.

Ist dies eine schlechte Vorgehensweise, da das Styling erst nach dem Laden des CSS vom Server wirksam wird und in einem langsamen Browser schnell unformatierte Inhalte blinken? Ich habe dies noch nicht gesehen, obwohl ich es lokal teste.

Gibt es eine Möglichkeit, das CSS über das an Angular übergebene Objekt zu laden $routeProvider.when?

Danke im Voraus!

Brandon
quelle
Ich habe Ihre Behauptung "Schneller Blitz unformatierter Inhalte" bestätigt. Ich habe CSS- <link>Tags in diesem Format verwendet , mit dem neuesten Chrome, dem Server auf meinem lokalen Computer (und "Cache deaktivieren", um die Bedingungen für das erste Laden zu simulieren). Ich stelle mir vor, dass das Vorabfügen eines <style>Tags in den HTML-Teil auf dem Server dieses Problem vermeiden würde.
vornehmster

Antworten:

150

Ich weiß, dass diese Frage jetzt alt ist, aber nachdem ich eine Menge Nachforschungen über verschiedene Lösungen für dieses Problem angestellt habe, denke ich, dass ich vielleicht eine bessere Lösung gefunden habe.

UPDATE 1: Seit ich diese Antwort gepostet habe, habe ich den gesamten Code zu einem einfachen Dienst hinzugefügt, den ich auf GitHub gepostet habe. Das Repo befindet sich hier . Fühlen Sie sich frei, es für weitere Informationen zu überprüfen.

UPDATE 2: Diese Antwort ist großartig, wenn Sie nur eine leichte Lösung zum Einziehen von Stylesheets für Ihre Routen benötigen. Wenn Sie eine umfassendere Lösung für die Verwaltung von On-Demand-Stylesheets in Ihrer gesamten Anwendung wünschen, können Sie das AngularCSS-Projekt von Door3 ausprobieren . Es bietet viel feinkörnigere Funktionen.

Für den Fall, dass jemand in der Zukunft interessiert ist, habe ich mir Folgendes ausgedacht:

1. Erstellen Sie eine benutzerdefinierte Direktive für das <head>Element:

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

Diese Richtlinie bewirkt Folgendes:

  1. Es kompiliert (mit $compile) einer HTML - Zeichenfolge , die eine Reihe von erstellt <link />Tags für jedes Element in dem scope.routeStylesObjekt verwendet ng-repeatund ng-href.
  2. Dieser kompilierte Satz von <link />Elementen wird an das <head>Tag angehängt .
  3. Anschließend wird das verwendet $rootScope, um auf '$routeChangeStart'Ereignisse zu warten . Bei jedem '$routeChangeStart'Ereignis wird das "aktuelle" $$routeObjekt (die Route, die der Benutzer gerade verlassen wird) erfasst und seine partiellspezifische CSS-Datei (en) aus dem <head>Tag entfernt. Es erfasst auch das "nächste" $$routeObjekt (die Route, zu der der Benutzer gehen wird) und fügt dem <head>Tag eine seiner teilspezifischen CSS-Dateien hinzu .
  4. Der ng-repeatTeil des kompilierten <link />Tags übernimmt das Hinzufügen und Entfernen der seitenspezifischen Stylesheets basierend auf dem, was dem scope.routeStylesObjekt hinzugefügt oder daraus entfernt wird.

Hinweis: Dies setzt voraus, dass sich Ihr ng-appAttribut auf dem <html>Element befindet, nicht auf <body>oder irgendetwas innerhalb von <html>.

2. Geben Sie Folgendes an, welche Stylesheets zu welchen Routen gehören $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']
        })
}]);

Diese Konfiguration fügt cssdem Objekt eine benutzerdefinierte Eigenschaft hinzu, mit der die Route jeder Seite eingerichtet wird. Dieses Objekt wird an jedes '$routeChangeStart'Ereignis als übergeben .$$route. Wenn '$routeChangeStart'wir also das Ereignis abhören css, können wir die von uns angegebene Eigenschaft abrufen und diese <link />Tags nach Bedarf anhängen / entfernen . Beachten Sie, dass die Angabe einer cssEigenschaft auf der Route völlig optional ist, da sie im '/some/route/2'Beispiel weggelassen wurde . Wenn die Route keine cssEigenschaft hat, wird die <head>Direktive einfach nichts für diese Route tun. Beachten Sie auch, dass Sie sogar mehrere seitenspezifische Stylesheets pro Route haben können, wie im '/some/route/3'obigen Beispiel, wobei die cssEigenschaft ein Array relativer Pfade zu den für diese Route benötigten Stylesheets ist.

3. Sie sind fertig Diese beiden Dinge richten alles ein, was benötigt wurde, und meiner Meinung nach mit dem saubersten Code, der möglich ist.

Ich hoffe, das hilft jemand anderem, der mit diesem Problem genauso zu kämpfen hat wie ich.

Tennisagent
quelle
2
Holy Moly, danke dafür! Genau das, wonach ich gesucht habe :). Habe es gerade getestet und es funktioniert perfekt (plus einfach zu implementieren). Vielleicht sollten Sie eine Pull-Anfrage dafür erstellen und in den Kern bekommen. Ich weiß, dass die Jungs von AngularJS sich mit CSS befasst haben. Könnte dies ein Schritt in die richtige Richtung sein?
smets.kevin
Diese Jungs sind viel schlauer als ich. Ich bin sicher, sie hätten sich diese (oder eine ähnliche) Lösung schon einmal ausgedacht und beschlossen, sie aus irgendeinem Grund nicht in den Kern zu implementieren.
Tennisgent
Was ist der richtige Ort für die CSS-Datei? Bedeutet css: 'css / Partial1.css' den CSS-Ordner im Stammverzeichnis des eckigen App-Ordners?
Cordle
Es ist relativ zu Ihrer index.htmlDatei. Im obigen Beispiel befindet index.htmlsich also der cssOrdner im Stammverzeichnis und der Ordner im Stammverzeichnis, der alle CSS-Dateien enthält. Sie können Ihre App jedoch beliebig strukturieren, sofern Sie die richtigen relativen Pfade verwenden.
Tennisgent
1
@Kappys, das Skript entfernt den Stil für die vorherige Ansicht, wenn Sie zu einer neuen Ansicht wechseln. Wenn Sie dies nicht möchten, entfernen Sie einfach den folgenden Code aus der Direktive : angular.forEach(current.$$route.css, function(sheet){ delete scope.routeStyles[sheet]; });.
Tennisgent
34

Die Lösung von @ Tennisgent ist großartig. Ich denke jedoch, dass dies etwas eingeschränkt ist.

Modularität und Kapselung in Angular gehen über Routen hinaus. Aufgrund der Art und Weise, wie sich das Web in Richtung komponentenbasierter Entwicklung bewegt, ist es wichtig, dies auch in Richtlinien anzuwenden.

Wie Sie bereits wissen, können wir in Angular Vorlagen (Struktur) und Controller (Verhalten) in Seiten und Komponenten einfügen. AngularCSS aktiviert das letzte fehlende Teil: Anhängen von Stylesheets (Präsentation).

Für eine vollständige Lösung empfehle ich die Verwendung von AngularCSS.

  1. Unterstützt Angulars ngRoute, UI Router, Direktiven, Controller und Dienste.
  2. Muss nicht ng-appim <html>Tag enthalten sein. Dies ist wichtig, wenn mehrere Apps auf derselben Seite ausgeführt werden
  3. Sie können anpassen, wo die Stylesheets eingefügt werden: Kopf, Körper, benutzerdefinierte Auswahl usw.
  4. Unterstützt das Vorladen, Fortbestehen und Cache-Busting
  5. Unterstützt Medienabfragen und optimiert das Laden von Seiten über die matchMedia-API

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

Hier sind einige Beispiele:

Routen

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

Richtlinien

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

Darüber hinaus können Sie den $cssDienst für Randfälle verwenden:

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

});

Weitere Informationen zu AngularCSS finden Sie hier:

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

castillo.io
quelle
1
Ihr Ansatz hier gefällt mir sehr gut, aber ich habe mich gefragt, wie er in einer Produktions-App verwendet werden kann, in der alle CSS-Stile miteinander verknüpft werden müssen. Für HTML-Vorlagen verwende ich $ templateCache.put () für den Produktionscode und es wäre schön, etwas Ähnliches für CSS zu tun.
Tom Makin
Wenn Sie verkettetes CSS vom Server erhalten müssen, können Sie immer so etwas wie /getCss?files=file1(.css),file2,file3 ausführen, und der Server antwortet mit allen 3 Dateien in der angegebenen Reihenfolge und verkettet.
Petr Urban
13

Könnte ein neues Stylesheet anhängen, um darin zu gehen $routeProvider. Der Einfachheit halber verwende ich eine Zeichenfolge, kann aber auch ein neues Verknüpfungselement erstellen oder einen Dienst für Stylesheets erstellen

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

Der größte Vorteil des Prelaodierens in der Seite besteht darin, dass bereits Hintergrundbilder vorhanden sind und weniger Lügen vorhanden sind FOUC

charlietfl
quelle
Würde dies nicht das Gleiche bewirken, als würde man das nur statisch <link>in <head>die index.html aufnehmen?
Brandon
nicht, wenn die whenfür die Route nicht aufgerufen wurde. Kann diesen Code in einen controllerRückruf wheninnerhalb des routeProvideroder vielleicht innerhalb eines resolveRückrufs
einfügen,
Oh okay, mein Schlechtes, das klickt nein. Sieht ziemlich solide aus, außer könnten Sie erklären, wie es vorgeladen wird, wenn ich es sowieso einspritze?
Brandon
1
Es wird nicht vorgeladen, wenn Sie es anhängen routeprovider... In diesem Kommentar ging es darum, es in den Kopf der Hauptseite aufzunehmen, wenn die Seite
zugestellt
-_- Entschuldigung, mir fehlt der Schlaf, wenn Sie nicht sagen können. Jedenfalls bin ich jetzt dort. Der Versuch herauszufinden, ob der Aufwand für das gleichzeitige Laden aller meiner Stylesheets besser ist als ein FOUC, wenn der Benutzer die Ansicht wechselt. Ich denke, das ist wirklich keine Angular-bezogene Frage, sondern vielmehr die Web-App UX. Vielen Dank, ich werde wahrscheinlich Ihrem Vorschlag folgen, wenn ich mich entscheide, das Vorladen nicht durchzuführen.
Brandon
5

@ sz3, lustig genug heute musste ich genau das tun, was du erreichen wolltest: ' eine bestimmte CSS-Datei nur laden, wenn ein Benutzer auf eine bestimmte Seite zugreift '. Also habe ich die obige Lösung verwendet.

Aber ich bin hier, um Ihre letzte Frage zu beantworten: „ Wo genau soll ich den Code einfügen? Irgendwelche Ideen ? '

Sie hatten Recht, den Code in die Auflösung aufzunehmen , aber Sie müssen das Format ein wenig ändern.

Schauen Sie sich den folgenden Code an:

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

Ich habe gerade getestet und es funktioniert gut , es injiziert das HTML und es lädt meine 'home.css' nur, wenn ich die '/ home'-Route treffe.

Eine vollständige Erklärung finden Sie hier , aber im Grunde genommen lösen: sollte ein Objekt im Format erhalten

{
  'key' : string or function()
} 

Sie können den ' Schlüssel ' beliebig benennen - in meinem Fall habe ich ' Stil ' genannt.

Dann haben Sie für den Wert zwei Möglichkeiten:

  • Wenn es sich um eine Zeichenfolge handelt , handelt es sich um einen Alias ​​für einen Dienst.

  • Wenn es funktioniert , wird es injiziert und der Rückgabewert wird als Abhängigkeit behandelt.

Der Hauptpunkt hierbei ist, dass der Code in der Funktion ausgeführt wird, bevor der Controller instanziiert und das Ereignis $ routeChangeSuccess ausgelöst wird.

Hoffentlich hilft das.

Denison Luz
quelle
2

Geil, danke!! Ich musste nur ein paar Anpassungen vornehmen, damit es mit dem UI-Router funktioniert:

    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
quelle
Ich brauchte das Entfernen und Hinzufügen nicht unbedingt überall, da mein CSS durcheinander war, aber dies war eine große Hilfe beim UI-Router! Danke :)
imsheth
1

Wenn Ihr CSS nur auf eine bestimmte Ansicht angewendet werden soll , verwende ich dieses praktische Snippet in meinem Controller:

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

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

Dadurch wird meinem bodyTag eine Klasse hinzugefügt, wenn der Status geladen wird, und diese wird entfernt, wenn der Status zerstört wird (dh jemand wechselt die Seiten). Dies löst mein damit verbundenes Problem, dass nur CSS in meiner Anwendung auf einen Status angewendet werden muss.

Matt
quelle
0

'use strict'; angle.module ('app') .run (['$ rootScope', '$ state', '$ stateParams', Funktion ($ rootScope, $ state, $ stateParams) {$ rootScope. $ state = $ state; $ rootScope . $ stateParams = $ stateParams;}]) .config (['$ stateProvider', '$ urlRouterProvider', Funktion ($ 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
quelle
Ein einfaches Codebeispiel ohne Kontext ist selten eine ausreichende Antwort auf eine Frage. Darüber hinaus hat diese Frage bereits eine sehr akzeptierte Antwort.
AJ X.