The ngProp
directive binds an expression to a DOM element property.
ngProp
allows writing to arbitrary properties by including
the property name in the attribute, e.g. ng-prop-value="'my value'"
binds 'my value' to
the value
property.
Usually, it's not necessary to write to properties in AngularJS, as the built-in directives
handle the most common use cases (instead of the above example, you would use ngValue
).
However, custom elements
often use custom properties to hold data, and ngProp
can be used to provide input to these
custom elements.
Since HTML attributes are case-insensitive, camelCase properties like innerHTML
must be escaped.
AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so
innerHTML
must be written as ng-prop-inner_h_t_m_l="expression"
(Note that this is just an
example, and for binding HTML ngBindHtml
should be used.
Binding expressions to arbitrary properties poses a security risk, as properties like innerHTML
can insert potentially dangerous HTML into the application, e.g. script tags that execute
malicious code.
For this reason, ngProp
applies Strict Contextual Escaping with the $sce service.
This means vulnerable properties require their content to be "trusted", based on the
context of the property. For example, the innerHTML
is in the HTML
context, and the
iframe.src
property is in the RESOURCE_URL
context, which requires that values written to
this property are trusted as a RESOURCE_URL
.
This can be set explicitly by calling $sce.trustAs(type, value) on the value that is
trusted before passing it to the ng-prop-*
directive. There are exist shorthand methods for
each context type in the form of $sce.trustAsResourceUrl() et al.
In some cases you can also rely upon automatic sanitization of untrusted values - see below.
Based on the context, other options may exist to mark a value as trusted / configure the behavior
of $sce
. For example, to restrict the RESOURCE_URL
context to specific origins, use
the resourceUrlWhitelist()
and resourceUrlBlacklist().
Find out more about the different context types.
By default, $sce
will throw an error if it detects untrusted HTML content, and will not bind the
content.
However, if you include the ngSanitize module, it will try to sanitize the
potentially dangerous HTML, e.g. strip non-whitelisted tags and attributes when binding to
innerHTML
.
<ANY ng-prop-propname="expression">
</ANY>
or with uppercase letters in property (e.g. "propName"):
<ANY ng-prop-prop_name="expression">
</ANY>
angular.module('exampleNgProp', [])
.component('main', {
templateUrl: 'main.html',
controller: function($sce) {
this.safeContent = '<strong>Safe content</strong>';
this.unsafeContent = '<button onclick="alert(\'Hello XSS!\')">Click for XSS</button>';
this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent);
}
});
<div>
<div class="prop-unit">
Binding to a property without security context:
<div class="prop-binding" ng-prop-inner_text="$ctrl.safeContent"></div>
<span class="prop-note">innerText</span> (safeContent)
</div>
<div class="prop-unit">
"Safe" content that requires a security context will throw because the contents could potentially be dangerous ...
<div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.safeContent"></div>
<span class="prop-note">innerHTML</span> (safeContent)
</div>
<div class="prop-unit">
... so that actually dangerous content cannot be executed:
<div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.unsafeContent"></div>
<span class="prop-note">innerHTML</span> (unsafeContent)
</div>
<div class="prop-unit">
... but unsafe Content that has been trusted explicitly works - only do this if you are 100% sure!
<div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.trustedUnsafeContent"></div>
<span class="prop-note">innerHTML</span> (trustedUnsafeContent)
</div>
</div>
<main></main>
.prop-unit {
margin-bottom: 10px;
}
.prop-binding {
min-height: 30px;
border: 1px solid blue;
}
.prop-note {
font-family: Monospace;
}
angular.module('exampleNgProp', ['ngSanitize'])
.component('main', {
templateUrl: 'main.html',
controller: function($sce) {
this.safeContent = '<strong>Safe content</strong>';
this.unsafeContent = '<button onclick="alert(\'Hello XSS!\')">Click for XSS</button>';
this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent);
}
});
<div>
<div class="prop-unit">
"Safe" content will be sanitized ...
<div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.safeContent"></div>
<span class="prop-note">innerHTML</span> (safeContent)
</div>
<div class="prop-unit">
... as will dangerous content:
<div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.unsafeContent"></div>
<span class="prop-note">innerHTML</span> (unsafeContent)
</div>
<div class="prop-unit">
... and content that has been trusted explicitly works the same as without ngSanitize:
<div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.trustedUnsafeContent"></div>
<span class="prop-note">innerHTML</span> (trustedUnsafeContent)
</div>
</div>
<main></main>
.prop-unit {
margin-bottom: 10px;
}
.prop-binding {
min-height: 30px;
border: 1px solid blue;
}
.prop-note {
font-family: Monospace;
}