diff --git a/packages/core/src/debug/debug_node.ts b/packages/core/src/debug/debug_node.ts
index bc15842731..61f138ee6c 100644
--- a/packages/core/src/debug/debug_node.ts
+++ b/packages/core/src/debug/debug_node.ts
@@ -276,13 +276,50 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
get attributes(): {[key: string]: string | null;} {
const attributes: {[key: string]: string | null;} = {};
const element = this.nativeElement;
- if (element) {
- const eAttrs = element.attributes;
- for (let i = 0; i < eAttrs.length; i++) {
- const attr = eAttrs[i];
+
+ if (!element) {
+ return attributes;
+ }
+
+ const context = loadLContext(element);
+ const lView = context.lView;
+ const tNodeAttrs = (lView[TVIEW].data[context.nodeIndex] as TNode).attrs;
+ const lowercaseTNodeAttrs: string[] = [];
+
+ // For debug nodes we take the element's attribute directly from the DOM since it allows us
+ // to account for ones that weren't set via bindings (e.g. ViewEngine keeps track of the ones
+ // that are set through `Renderer2`). The problem is that the browser will lowercase all names,
+ // however since we have the attributes already on the TNode, we can preserve the case by going
+ // through them once, adding them to the `attributes` map and putting their lower-cased name
+ // into an array. Afterwards when we're going through the native DOM attributes, we can check
+ // whether we haven't run into an attribute already through the TNode.
+ if (tNodeAttrs) {
+ let i = 0;
+ while (i < tNodeAttrs.length) {
+ const attrName = tNodeAttrs[i];
+
+ // Stop as soon as we hit a marker. We only care about the regular attributes. Everything
+ // else will be handled below when we read the final attributes off the DOM.
+ if (typeof attrName !== 'string') break;
+
+ const attrValue = tNodeAttrs[i + 1];
+ attributes[attrName] = attrValue as string;
+ lowercaseTNodeAttrs.push(attrName.toLowerCase());
+
+ i += 2;
+ }
+ }
+
+ const eAttrs = element.attributes;
+ for (let i = 0; i < eAttrs.length; i++) {
+ const attr = eAttrs[i];
+ // Make sure that we don't assign the same attribute both in its
+ // case-sensitive form and the lower-cased one from the browser.
+ if (lowercaseTNodeAttrs.indexOf(attr.name) === -1) {
attributes[attr.name] = attr.value;
}
}
+
return attributes;
}
diff --git a/packages/core/test/debug/debug_node_spec.ts b/packages/core/test/debug/debug_node_spec.ts
index cbec33a9ca..4f318f0b1a 100644
--- a/packages/core/test/debug/debug_node_spec.ts
+++ b/packages/core/test/debug/debug_node_spec.ts
@@ -7,7 +7,7 @@
*/
-import {Component, DebugNode, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
+import {Component, DebugNode, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, Renderer2, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
@@ -736,5 +736,58 @@ class TestCmptWithPropBindings {
]);
});
+ it('should preserve the attribute case in DebugNode.attributes', () => {
+ @Component({selector: 'my-icon', template: ''})
+ class Icon {
+ @Input() svgIcon: any = '';
+ }
+ @Component({template: `