AngularJS에 뷰 / 부분 특정 스타일을 포함하는 방법


132

응용 프로그램에서 사용하는 다양한 뷰에 대해 별도의 스타일 시트를 사용하는 올바른 방법은 무엇입니까?

현재 상단의 view / partial html에 링크 요소를 배치하고 있지만 모든 최신 브라우저가 지원하지만 나쁜 인상을받는 이유는 알 수 있지만 나쁜 습관이라고 들었습니다.

다른 가능성은 index.html에 별도의 스타일 시트를 배치하는 head것이지만 성능 이름으로 뷰가로드되는 경우에만 스타일 시트를로드하고 싶습니다.

CSS가 서버에서로드 될 때까지 스타일링이 적용되지 않아 느린 브라우저에서 형식화되지 않은 컨텐츠가 빠르게 깜박이기 때문에이 나쁜 습관입니까? 로컬에서 테스트하고 있지만 아직 이것을 목격하지 않았습니다.

Angular에 전달 된 객체를 통해 CSS를로드하는 방법이 $routeProvider.when있습니까?

미리 감사드립니다!


"포맷되지 않은 콘텐츠의 빠른 플래시"주장을 확인했습니다. 로컬 시스템의 서버 인 최신 Chrome과 함께이 형식의 CSS <link>태그를 사용했습니다 ( "캐시 사용 안함"을 사용하여 "첫 번째로드"조건을 시뮬레이션). 서버의 HTML 부분에 태그 를 미리 삽입하면 이 문제를 피할 수 있다고 생각합니다 . <style>
poshest

답변:


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-repeatng-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
신성 모독 님 감사합니다! 내가 찾던 것 :). 지금 테스트하고 완벽하게 작동합니다 (더 쉽게 구현할 수 있음). 어쩌면 당신은 이것을 위해 pull 요청을 작성하여 코어로 가져와야합니다. AngularJS의 사람들이 범위가 지정된 CSS를 조사하고 있다는 것을 알고 있습니다.이 올바른 방향으로 나아가는 단계 일 수 있습니까?
smets.kevin

저 사람들은 나보다 똑똑해 나는 그들이 이전 에이 (또는 비슷한) 솔루션을 생각했을 것이고 어떤 이유로 든 핵심으로 구현하지 않기로 결정했을 것이라고 확신합니다.
tennisgent

CSS 파일의 올바른 위치는 무엇입니까? css : 'css / partial1.css'는 각도 앱 폴더의 루트에있는 css 폴더를 의미합니까?
Cordle

index.html파일과 관련이 있습니다 . 따라서 위의 예 index.html에서 루트에 있고 css폴더는 모든 CSS 파일을 포함하는 루트에 있습니다. 그러나 올바른 상대 경로를 사용하는 한 원하는대로 앱을 구성 할 수 있습니다.
tennisgent

1
@Kappys, 새보기로 이동하면 스크립트가 이전보기의 스타일을 제거합니다. 그렇게하지 않으려면 지시문에서 다음 코드를 제거하십시오 angular.forEach(current.$$route.css, function(sheet){ delete scope.routeStyles[sheet]; });.
tennisgent

34

@tennisgent의 솔루션은 훌륭합니다. 그러나 나는 조금 제한적이라고 생각합니다.

Angular의 모듈화 및 캡슐화는 경로를 뛰어 넘습니다. 웹이 컴포넌트 기반 개발로 이동하는 방식에 따라이를 지시문에도 적용하는 것이 중요합니다.

이미 알고 있듯이 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


그래도 index.html에 <link>in을 <head>정적으로 포함하는 것과 동일한 결과를 얻지 못 합니까?
Brandon

when경로가 호출되지 않은 경우 에는 그렇지 않습니다. 이 코드를 넣을 수 controller의 콜백 when내에서 routeProvider, 혹은 내 resolve콜백있는 가능성이 트리거 빨리
charlietfl

오케이, 나쁘지 않아. 어쨌든 내가 그것을 주입 할 때 어떻게 사전로드를 설명 할 수있는 것을 제외하고는 상당히 견고합니다.
Brandon

1
당신이 그것을 추가하면 그것은 미리로드되지 않습니다 routeprovider... 그 의견은 페이지가 제공 될 때 메인 페이지의 머리에 그것을 포함하는 것에 관한 것입니다
charlietfl

-_- 죄송합니다. 말할 수 없으면 잠이 부족합니다. 어쨌든, 그것은 지금 내가있는 곳입니다. 모든 스타일 시트를 한 번에로드하는 오버 헤드가 사용자가 뷰를 전환 할 때 FOUC를 사용하는 것보다 낫다면 알아 내려고합니다. 웹 응용 프로그램 UX에 관한 것만 큼 Angular 관련 질문은 아닙니다. 그래도 미리로드하지 않기로 결정하면 제안과 함께 갈 것입니다.
Brandon

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

당신이 원하는대로 ' '의 이름을 지정할 수 있습니다 -제 경우에는 ' 스타일 ' 이라고 불렀습니다 .

그런 다음 값에 대해 두 가지 옵션이 있습니다.

  • 그것은 만약 문자열 , 다음은 서비스의 별칭입니다.

  • 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를해야하는 경우 적용되는 하나 개의 특정보기에, 나는 내 컨트롤러 내부의이 편리한 조각을 사용하고 있습니다 :

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

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

body상태가로드되면 내 태그에 클래스가 추가 되고 상태가 파괴되면 클래스가 제거됩니다 (예 : 누군가가 페이지를 변경 함). 이렇게하면 응용 프로그램에서 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'
                                    ]
                                });
                            }
                        ]
                    }
                })
                 );

문맥이없는 간단한 코드 예제는 질문에 대한 충분한 답변이 아닙니다. 또한이 질문에는 이미 널리 인정 된 답변이 있습니다.
AJ X.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.