简体   繁体   中英

How do I access the ngStyle key and values in decorator?

I have a list of colour names in my application.

let colours = {
  mango: '#e59c09',
  midnight: '#1476a0'
};

I want to extend the ngStyle directive to be able to understand my custom colour names. I'm doing this by decorating the ngStyle directive. However, I've hit an uphill battle on the decorator's compile function. I can access the elements' ngStyle attribute, but it comes up as a string (understandably). JSON.parse() doesn't work on it as it isn't always a valid JSON string due to bind once etc...

I simply want to step-in, iterate over all style keys, and if it contains color , I want to check for the value - and replace with hex if it is one of the above custom colour.

I can't seem to be able to access any ngStyle internal functions, and the source code is confusing and short; it seems to just set element CSS - where does the $parse do its job? for example, when ng-style="{color: ctrl.textColor}" - there is nothing in ngStyle source code that is pulling the value of ctrl.textColour . Am I looking at the wrong place?

Anyway, how do I access ng-style key values so that I can change custom colours to its hex codes please?

This is what I've got so far in my decorator:

$provide.decorator('ngStyleDirective', function($delegate) {

    let directive = $delegate[0];
    let link = directive.link;

    directive.compile = function(element, attrs) {

        // Expression here is a string property
        let expression = attrs.ngStyle;

        return function(scope, elem, attr) {

          // How do I iterate over and update style values here?

          // Run original function
          link.apply(this, arguments);

        }

      }

      return $delegate;

});

I tried using regex to pull out patterns etc. and inspect elements, but, it seems like the wrong way to approach the problem to me as I then have to manually update string and pass it on to base link function.

Here's a plnkr example .

IF there is a better way to do what I'm trying to do, please let me know.

Anyway, how do I access ng-style key values so that I can change custom colours to its hex codes please?

The ngStyle property can be re-written within the compile function:

directive.compile = function(element, attrs) {
  let expression = getExpressions(attrs.ngStyle);
  attrs.ngStyle = expression;

  return function(scope, elem, attr) {
    // Run original function
    link.apply(this, arguments);  
  }
}

JSON.parse()

JSON.parse() can be used if the HTML is updated so that the keys are surrounded by double quotes, which means the ng-style attribute needs to be delimited with single-quotes (though if one really wanted, one could try to escape the double quotes...)

<p ng-style='{ "color": "#e59c09" }'>Hello {{name}}!</p>
<p ng-style='{ "padding": "20px 10px", "background-color": "#1476a0", "color": "#ddd" }'>It is dark here</p>

Then parsing that string should yield a valid object, and Object.keys() can be used to iterate over the keys, checking for the word color . If the key contains color , Array.indexOf can be used to check if the value exists in the colors array. If it does exist in the array, then String.replace() can be used to substitute the value for the variable (ie the key in colours ).

function getExpressions(str) {
    var parsed = JSON.parse(str);
    Object.keys(parsed).forEach(function(key) {
        if (key.indexOf('color') > -1) {
            if (Object.keys(colours).indexOf(parsed[key]) > -1) {
                str = str.replace(parsed[key], colours[parsed[key]])
            }
         }
    });
    return str;
}

See it demonstrated in the example below. By the way, I had to remove the unused variable colours declared within the scope of the function getExpressions() , as it was hiding access to the variable defined above on line 3. Here is an updated plunker .

 let app = angular.module('plunker', []); let colours = { mango: '#e59c09', midnight: '#1476a0' }; app.controller('MainCtrl', function($scope) { $scope.name = 'World'; }); app.config(function($provide) { // Extract colour values from the string function getExpressions(str) { var parsed = JSON.parse(str); Object.keys(parsed).forEach(function(key) { if (key.indexOf('color') > -1) { if (Object.keys(colours).indexOf(parsed[key]) > -1) { str = str.replace(parsed[key], colours[parsed[key]]) } } }); return str; } $provide.decorator('ngStyleDirective', function($delegate) { let directive = $delegate[0]; let link = directive.link; directive.compile = function(element, attrs) { let expression = getExpressions(attrs.ngStyle); attrs.ngStyle = expression; return function(scope, elem, attr) { // Run original function link.apply(this, arguments); } } return $delegate; }); }); 
 div + div { margin-top: 60px; } .comment { font-family: courier; font-size: 12px; margin: 15px 0; } 
 <script src="https://code.angularjs.org/1.4.12/angular.js"></script> <div ng-app="plunker" ng-controller="MainCtrl"> <div> <p class="comment">--- with hex --</p> <p ng-style='{ "color": "#e59c09" }'>Hello {{name}}!</p> <p ng-style='{ "padding": "20px 10px", "background-color": "#1476a0", "color": "#ddd" }'>It is dark here</p> </div> <div> <p class="comment">--- with custom colours --</p> <p ng-style='{ "color": "mango" }'>Hello {{name}}!</p> <p ng-style='{ "padding": "20px 10px", "background-color": "midnight", "color": "#ddd" }'>It is dark here</p> </div> </div> 

Actually, if you want to use parse - and you should - you can use it to parse the expression, replace attributes, and then transform the attribute back to json.

You should use $parse because if your code looks like

// in the HTML
<p ng-style="{ padding: '20px 10px', 'background-color': myController.color, color: '#ddd' }">It is dark here</p>
// in the JS
myController.color = 'midnight';

Then parsing JSON will not work. You should parse the expression using $parse and call the resulting function with your directive's scope object.

That's why your provider should look like this :

$provide.decorator('ngStyleDirective', function($delegate, $parse) {
  let directive = $delegate[0];
  let link = directive.link;

  directive.compile = function(element, attrs) {
    return function(scope, elem, attrs) {
      let ngStyleObject = $parse(attrs.ngStyle)(scope, {});

      Object.keys(ngStyleObject).forEach(function(key) {
        if (key.indexOf('color') > -1 && Object.keys(colours).indexOf(ngStyleObject[key]) > -1) {
          ngStyleObject[key] = colours[ngStyleObject[key]];
        }
      });

      attrs.ngStyle = JSON.stringify(ngStyleObject); 
      // Run original function
      link.apply(this, arguments); 
    }
  }
  return $delegate;
});

You could also copy the original ngStyle function (instead of calling its link function) as it's pretty simple to add the behavior you expected:

$provide.decorator('ngStyleDirective', function($delegate) {
  let directive = $delegate[0];

  directive.compile = function(element, attrs) {
    return function(scope, elem, attrs) {
      // here, watch will do the $parse(attrs.ngStyle)(scope) and call the callback when values change
      scope.$watch(attrs.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
        if (oldStyles && (newStyles !== oldStyles)) {
          oldStyles.forEach(function(val, style) {  element.css(style, ''); });
        }
        if (newStyles) {
          // instead of just setting the new styles, replace colors with their values
          Object.keys(newStyles).forEach(function(key) { 
            if (key.indexOf('color') > -1 && Object.keys(colours).indexOf(newStyles[key]) > -1) {
              newStyles[key] = colours[newStyles[key]];
            }
          });
          element.css(newStyles);
        }
      }, true);

    }
  }
  return $delegate;
});

You can find the plunker (two versions) here

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM