diff --git a/src/ng/directive/ngBind.js b/src/ng/directive/ngBind.js
index 3c7c65d4cb56..807f6ab7b3c5 100644
--- a/src/ng/directive/ngBind.js
+++ b/src/ng/directive/ngBind.js
@@ -188,8 +188,9 @@ var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse,
restrict: 'A',
compile: function ngBindHtmlCompile(tElement, tAttrs) {
var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
- var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) {
- return (value || '').toString();
+ var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function sceValueOf(val) {
+ // Unwrap the value to compare the actual inner safe value, not the wrapper object.
+ return $sce.valueOf(val);
});
$compile.$$addBindingClass(tElement);
@@ -197,9 +198,9 @@ var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse,
$compile.$$addBindingInfo(element, attr.ngBindHtml);
scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
- // we re-evaluate the expr because we want a TrustedValueHolderType
- // for $sce, not a string
- element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || '');
+ // The watched value is the unwrapped value. To avoid re-escaping, use the direct getter.
+ var value = ngBindHtmlGetter(scope);
+ element.html($sce.getTrustedHtml(value) || '');
});
};
}
diff --git a/test/ng/directive/ngBindSpec.js b/test/ng/directive/ngBindSpec.js
index c03afa9123bb..39a35c15723a 100644
--- a/test/ng/directive/ngBindSpec.js
+++ b/test/ng/directive/ngBindSpec.js
@@ -142,6 +142,16 @@ describe('ngBind*', function() {
expect(angular.lowercase(element.html())).toEqual('
hello
');
}));
+ it('should update html', inject(function($rootScope, $compile, $sce) {
+ element = $compile('')($rootScope);
+ $rootScope.html = 'hello';
+ $rootScope.$digest();
+ expect(angular.lowercase(element.html())).toEqual('hello');
+ $rootScope.html = 'goodbye';
+ $rootScope.$digest();
+ expect(angular.lowercase(element.html())).toEqual('goodbye');
+ }));
+
it('should one-time bind if the expression starts with two colons', inject(function($rootScope, $compile) {
element = $compile('')($rootScope);
$rootScope.html = 'hello
';
@@ -176,7 +186,18 @@ describe('ngBind*', function() {
expect(angular.lowercase(element.html())).toEqual('hello
');
}));
- it('should watch the string value to avoid infinite recursion', inject(function($rootScope, $compile, $sce) {
+ it('should update html', inject(function($rootScope, $compile, $sce) {
+ element = $compile('')($rootScope);
+ $rootScope.html = $sce.trustAsHtml('hello');
+ $rootScope.$digest();
+ expect(angular.lowercase(element.html())).toEqual('hello');
+ $rootScope.html = $sce.trustAsHtml('goodbye');
+ $rootScope.$digest();
+ expect(angular.lowercase(element.html())).toEqual('goodbye');
+ }));
+
+ it('should not cause infinite recursion for trustAsHtml object watches',
+ inject(function($rootScope, $compile, $sce) {
// Ref: https://github.com/angular/angular.js/issues/3932
// If the binding is a function that creates a new value on every call via trustAs, we'll
// trigger an infinite digest if we don't take care of it.
@@ -188,6 +209,33 @@ describe('ngBind*', function() {
expect(angular.lowercase(element.html())).toEqual('hello
');
}));
+ it('should handle custom $sce objects', function() {
+ function MySafeHtml(val) { this.val = val; }
+
+ module(function($provide) {
+ $provide.decorator('$sce', function($delegate) {
+ $delegate.trustAsHtml = function(html) { return new MySafeHtml(html); };
+ $delegate.getTrustedHtml = function(mySafeHtml) { return mySafeHtml.val; };
+ $delegate.valueOf = function(v) { return v instanceof MySafeHtml ? v.val : v; };
+ return $delegate;
+ });
+ });
+
+ inject(function($rootScope, $compile, $sce) {
+ // Ref: https://github.com/angular/angular.js/issues/14526
+ // Previous code used toString for change detection, which fails for custom objects
+ // that don't override toString.
+ element = $compile('')($rootScope);
+ var html = 'hello';
+ $rootScope.getHtml = function() { return $sce.trustAsHtml(html); };
+ $rootScope.$digest();
+ expect(angular.lowercase(element.html())).toEqual('hello');
+ html = 'goodbye';
+ $rootScope.$digest();
+ expect(angular.lowercase(element.html())).toEqual('goodbye');
+ });
+ });
+
describe('when $sanitize is available', function() {
beforeEach(function() { module('ngSanitize'); });