examples/weather/angular.js
function(key, value, writeAttr, attrName) {
        //special case for class attribute addition + removal
        //so that class changes can tap into the animation
        //hooks provided by the $animate service
        if(key == 'class') {
          value = value || '';
          var current = this.$$element.attr('class') || '';
          this.$removeClass(tokenDifference(current, value).join(' '));
          this.$addClass(tokenDifference(value, current).join(' '));
        } else {
          var booleanKey = getBooleanAttrName(this.$$element[0], key),
              normalizedVal,
              nodeName;

          if (booleanKey) {
            this.$$element.prop(key, value);
            attrName = booleanKey;
          }

          this[key] = value;

          // translate normalized key to actual key
          if (attrName) {
            this.$attr[key] = attrName;
          } else {
            attrName = this.$attr[key];
            if (!attrName) {
              this.$attr[key] = attrName = snake_case(key, '-');
            }
          }

          nodeName = nodeName_(this.$$element);

          // sanitize a[href] and img[src] values
          if ((nodeName === 'A' && key === 'href') ||
              (nodeName === 'IMG' && key === 'src')) {
            // NOTE: $$urlUtils.resolve() doesn't support IE < 8 so we don't sanitize for that case.
            if (!msie || msie >= 8 ) {
              normalizedVal = $$urlUtils.resolve(value);
              if (normalizedVal !== '') {
                if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
                    (key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
                  this[key] = value = 'unsafe:' + normalizedVal;
                }
              }
            }
          }

          if (writeAttr !== false) {
            if (value === null || value === undefined) {
              this.$$element.removeAttr(attrName);
            } else {
              this.$$element.attr(attrName, value);
            }
          }
        }

        // fire observers
        var $$observers = this.$$observers;
        $$observers && forEach($$observers[key], function(fn) {
          try {
            fn(value);
          } catch (e) {
            $exceptionHandler(e);
          }
        });

        function tokenDifference(str1, str2) {
          var values = [],
              tokens1 = str1.split(/\s+/),
              tokens2 = str2.split(/\s+/);

          outer:
          for(var i=0;i<tokens1.length;i++) {
            var token = tokens1[i];
            for(var j=0;j<tokens2.length;j++) {
              if(token == tokens2[j]) continue outer;
            }
            values.push(token);
          }
          return values;
        };
      }
function(key, value, writeAttr, attrName) {
        //special case for class attribute addition + removal
        //so that class changes can tap into the animation
        //hooks provided by the $animate service
        if(key == 'class') {
          value = value || '';
          var current = this.$$element.attr('class') || '';
          this.$removeClass(tokenDifference(current, value).join(' '));
          this.$addClass(tokenDifference(value, current).join(' '));
        } else {
          var booleanKey = getBooleanAttrName(this.$$element[0], key),
              normalizedVal,
              nodeName;

          if (booleanKey) {
            this.$$element.prop(key, value);
            attrName = booleanKey;
          }

          this[key] = value;

          // translate normalized key to actual key
          if (attrName) {
            this.$attr[key] = attrName;
          } else {
            attrName = this.$attr[key];
            if (!attrName) {
              this.$attr[key] = attrName = snake_case(key, '-');
            }
          }

          nodeName = nodeName_(this.$$element);

          // sanitize a[href] and img[src] values
          if ((nodeName === 'A' && key === 'href') ||
              (nodeName === 'IMG' && key === 'src')) {
            // NOTE: $$urlUtils.resolve() doesn't support IE < 8 so we don't sanitize for that case.
            if (!msie || msie >= 8 ) {
              normalizedVal = $$urlUtils.resolve(value);
              if (normalizedVal !== '') {
                if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
                    (key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
                  this[key] = value = 'unsafe:' + normalizedVal;
                }
              }
            }
          }

          if (writeAttr !== false) {
            if (value === null || value === undefined) {
              this.$$element.removeAttr(attrName);
            } else {
              this.$$element.attr(attrName, value);
            }
          }
        }

        // fire observers
        var $$observers = this.$$observers;
        $$observers && forEach($$observers[key], function(fn) {
          try {
            fn(value);
          } catch (e) {
            $exceptionHandler(e);
          }
        });

        function tokenDifference(str1, str2) {
          var values = [],
              tokens1 = str1.split(/\s+/),
              tokens2 = str2.split(/\s+/);

          outer:
          for(var i=0;i<tokens1.length;i++) {
            var token = tokens1[i];
            for(var j=0;j<tokens2.length;j++) {
              if(token == tokens2[j]) continue outer;
            }
            values.push(token);
          }
          return values;
        };
      }
Set a normalized attribute on the element in a way such that all directives
can share the attribute. This function properly handles boolean attributes.
@param {string} key Normalized key. (ie ngAttribute)
@param {string|boolean} value The value to set. If `null` attribute will be deleted.
@param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
Defaults to true.
@param {string=} attrName Optional none normalized name. Defaults to key.