diff --git a/aio/content/guide/component-styles.md b/aio/content/guide/component-styles.md
index aef31566b0..f7d564fca1 100644
--- a/aio/content/guide/component-styles.md
+++ b/aio/content/guide/component-styles.md
@@ -280,12 +280,14 @@ To control how this encapsulation happens on a *per
component* basis, you can set the *view encapsulation mode* in the component metadata.
Choose from the following modes:
-* `Native` view encapsulation uses the browser's native shadow DOM implementation (see
+* `ShadowDom` view encapsulation uses the browser's native shadow DOM implementation (see
[Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM)
on the [MDN](https://developer.mozilla.org) site)
to attach a shadow DOM to the component's host element, and then puts the component
view inside that shadow DOM. The component's styles are included within the shadow DOM.
+* `Native` view encapsulation uses a now deprecated version of the browser's native shadow DOM implementation - [learn about the changes](https://hayato.io/2016/shadowdomv1/).
+
* `Emulated` view encapsulation (the default) emulates the behavior of shadow DOM by preprocessing
(and renaming) the CSS code to effectively scope the CSS to the component's view.
For details, see [Appendix 1](guide/component-styles#inspect-generated-css).
@@ -300,8 +302,8 @@ To set the components encapsulation mode, use the `encapsulation` property in th
-`Native` view encapsulation only works on browsers that have native support
-for shadow DOM (see [Shadow DOM v0](http://caniuse.com/#feat=shadowdom) on the
+`ShadowDom` view encapsulation only works on browsers that have native support
+for shadow DOM (see [Shadow DOM v1](https://caniuse.com/#feat=shadowdomv1) on the
[Can I use](http://caniuse.com) site). The support is still limited,
which is why `Emulated` view encapsulation is the default mode and recommended
in most cases.
diff --git a/packages/compiler/src/core.ts b/packages/compiler/src/core.ts
index 2f4241e0c9..f8102452be 100644
--- a/packages/compiler/src/core.ts
+++ b/packages/compiler/src/core.ts
@@ -73,7 +73,8 @@ export interface Component extends Directive {
export enum ViewEncapsulation {
Emulated = 0,
Native = 1,
- None = 2
+ None = 2,
+ ShadowDom = 3
}
export enum ChangeDetectionStrategy {
diff --git a/packages/core/src/metadata/view.ts b/packages/core/src/metadata/view.ts
index 1874257075..84efefcd5e 100644
--- a/packages/core/src/metadata/view.ts
+++ b/packages/core/src/metadata/view.ts
@@ -23,14 +23,28 @@ export enum ViewEncapsulation {
*/
Emulated = 0,
/**
+ * @deprecated v6.1.0 - use {ViewEncapsulation.ShadowDom} instead.
* Use the native encapsulation mechanism of the renderer.
*
- * For the DOM this means using [Shadow DOM](https://w3c.github.io/webcomponents/spec/shadow/) and
+ * For the DOM this means using the deprecated [Shadow DOM
+ * v0](https://w3c.github.io/webcomponents/spec/shadow/) and
* creating a ShadowRoot for Component's Host Element.
*/
Native = 1,
/**
* Don't provide any template or style encapsulation.
*/
- None = 2
+ None = 2,
+
+ /**
+ * Use Shadow DOM to encapsulate styles.
+ *
+ * For the DOM this means using modern [Shadow
+ * DOM](https://w3c.github.io/webcomponents/spec/shadow/) and
+ * creating a ShadowRoot for Component's Host Element.
+ *
+ * ### Example
+ * {@example core/ts/metadata/encapsulation.ts region='longform'}
+ */
+ ShadowDom = 3
}
diff --git a/packages/examples/core/ts/metadata/encapsulation.ts b/packages/examples/core/ts/metadata/encapsulation.ts
new file mode 100644
index 0000000000..04ac6928cc
--- /dev/null
+++ b/packages/examples/core/ts/metadata/encapsulation.ts
@@ -0,0 +1,35 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {Component, ViewEncapsulation} from '@angular/core';
+
+// #docregion longform
+@Component({
+ selector: 'my-app',
+ template: `
+
Hello World!
+ Shadow DOM Rocks!
+ `,
+ styles: [`
+ :host {
+ display: block;
+ border: 1px solid black;
+ }
+ h1 {
+ color: blue;
+ }
+ .red {
+ background-color: red;
+ }
+
+ `],
+ encapsulation: ViewEncapsulation.ShadowDom
+})
+class MyApp {
+}
+// #enddocregion
diff --git a/packages/platform-browser/src/dom/dom_renderer.ts b/packages/platform-browser/src/dom/dom_renderer.ts
index 6715d20513..9b583692ce 100644
--- a/packages/platform-browser/src/dom/dom_renderer.ts
+++ b/packages/platform-browser/src/dom/dom_renderer.ts
@@ -83,6 +83,7 @@ export class DomRendererFactory2 implements RendererFactory2 {
return renderer;
}
case ViewEncapsulation.Native:
+ case ViewEncapsulation.ShadowDom:
return new ShadowDomRenderer(this.eventManager, this.sharedStylesHost, element, type);
default: {
if (!this.rendererByCompId.has(type.id)) {
@@ -256,7 +257,11 @@ class ShadowDomRenderer extends DefaultDomRenderer2 {
eventManager: EventManager, private sharedStylesHost: DomSharedStylesHost,
private hostEl: any, private component: RendererType2) {
super(eventManager);
- this.shadowRoot = (hostEl as any).createShadowRoot();
+ if (component.encapsulation === ViewEncapsulation.ShadowDom) {
+ this.shadowRoot = (hostEl as any).attachShadow({mode: 'open'});
+ } else {
+ this.shadowRoot = (hostEl as any).createShadowRoot();
+ }
this.sharedStylesHost.addHost(this.shadowRoot);
const styles = flattenStyles(component.id, component.styles, []);
for (let i = 0; i < styles.length; i++) {
diff --git a/packages/platform-browser/test/dom/dom_renderer_spec.ts b/packages/platform-browser/test/dom/dom_renderer_spec.ts
index 1386e8b617..9e69d3d5ad 100644
--- a/packages/platform-browser/test/dom/dom_renderer_spec.ts
+++ b/packages/platform-browser/test/dom/dom_renderer_spec.ts
@@ -20,7 +20,8 @@ import {NAMESPACE_URIS} from '../../src/dom/dom_renderer';
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
- TestCmp, SomeApp, CmpEncapsulationEmulated, CmpEncapsulationNative, CmpEncapsulationNone
+ TestCmp, SomeApp, CmpEncapsulationEmulated, CmpEncapsulationNative, CmpEncapsulationNone,
+ CmpEncapsulationNative
]
});
renderer = TestBed.createComponent(TestCmp).componentInstance.renderer;
@@ -135,6 +136,15 @@ class CmpEncapsulationEmulated {
class CmpEncapsulationNone {
}
+@Component({
+ selector: 'cmp-shadow',
+ template: ``,
+ styles: [`.native { color: red; }`],
+ encapsulation: ViewEncapsulation.ShadowDom
+})
+class CmpEncapsulationShadow {
+}
+
@Component({
selector: 'some-app',
template: `
diff --git a/packages/platform-browser/test/dom/shadow_dom_spec.ts b/packages/platform-browser/test/dom/shadow_dom_spec.ts
new file mode 100644
index 0000000000..b7a1b8f1e5
--- /dev/null
+++ b/packages/platform-browser/test/dom/shadow_dom_spec.ts
@@ -0,0 +1,122 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {Component, EventEmitter, Injector, Input, NgModule, Output, Renderer2, ViewEncapsulation, destroyPlatform} from '@angular/core';
+import {TestBed} from '@angular/core/testing';
+import {BrowserModule} from '@angular/platform-browser';
+import {expect} from '@angular/platform-browser/testing/src/matchers';
+
+function supportsShadowDOMV1() {
+ const testEl = document.createElement('div');
+ return (typeof customElements !== 'undefined') && (typeof testEl.attachShadow !== 'undefined');
+}
+
+if (supportsShadowDOMV1()) {
+ describe('ShadowDOM Support', () => {
+
+ let testContainer: HTMLDivElement;
+
+ beforeEach(() => { TestBed.configureTestingModule({imports: [TestModule]}); });
+
+ it('should attach and use a shadowRoot when ViewEncapsulation.Native is set', () => {
+ const compEl = TestBed.createComponent(ShadowComponent).nativeElement;
+ expect(compEl.shadowRoot !.textContent).toEqual('Hello World');
+ });
+
+ it('should use the shadow root to encapsulate styles', () => {
+ const compEl = TestBed.createComponent(StyledShadowComponent).nativeElement;
+ expect(window.getComputedStyle(compEl).border).toEqual('1px solid rgb(0, 0, 0)');
+ const redDiv = compEl.shadowRoot.querySelector('div.red');
+ expect(window.getComputedStyle(redDiv).border).toEqual('1px solid rgb(255, 0, 0)');
+ });
+
+ it('should allow the usage of elements', () => {
+ const el = TestBed.createComponent(ShadowSlotComponent).nativeElement;
+ const projectedContent = document.createTextNode('Hello Slot!');
+ el.appendChild(projectedContent);
+ const slot = el.shadowRoot !.querySelector('slot');
+
+ expect(slot !.assignedNodes().length).toBe(1);
+ expect(slot !.assignedNodes()[0].textContent).toBe('Hello Slot!');
+ });
+
+ it('should allow the usage of named elements', () => {
+ const el = TestBed.createComponent(ShadowSlotsComponent).nativeElement;
+
+ const headerContent = document.createElement('h1');
+ headerContent.setAttribute('slot', 'header');
+ headerContent.textContent = 'Header Text!';
+
+ const articleContent = document.createElement('span');
+ articleContent.setAttribute('slot', 'article');
+ articleContent.textContent = 'Article Text!';
+
+ const articleSubcontent = document.createElement('span');
+ articleSubcontent.setAttribute('slot', 'article');
+ articleSubcontent.textContent = 'Article Subtext!';
+
+ el.appendChild(headerContent);
+ el.appendChild(articleContent);
+ el.appendChild(articleSubcontent);
+
+ const headerSlot = el.shadowRoot !.querySelector('slot[name=header]') as HTMLSlotElement;
+ const articleSlot = el.shadowRoot !.querySelector('slot[name=article]') as HTMLSlotElement;
+
+ expect(headerSlot !.assignedNodes().length).toBe(1);
+ expect(headerSlot !.assignedNodes()[0].textContent).toBe('Header Text!');
+ expect(headerContent.assignedSlot).toBe(headerSlot);
+
+ expect(articleSlot !.assignedNodes().length).toBe(2);
+ expect(articleSlot !.assignedNodes()[0].textContent).toBe('Article Text!');
+ expect(articleSlot !.assignedNodes()[1].textContent).toBe('Article Subtext!');
+ expect(articleContent.assignedSlot).toBe(articleSlot);
+ expect(articleSubcontent.assignedSlot).toBe(articleSlot);
+ });
+ });
+}
+
+@Component(
+ {selector: 'shadow-comp', template: 'Hello World', encapsulation: ViewEncapsulation.ShadowDom})
+class ShadowComponent {
+}
+
+@Component({
+ selector: 'styled-shadow-comp',
+ template: '',
+ encapsulation: ViewEncapsulation.ShadowDom,
+ styles: [`:host { border: 1px solid black; } .red { border: 1px solid red; }`]
+})
+class StyledShadowComponent {
+}
+
+@Component({
+ selector: 'shadow-slot-comp',
+ template: '',
+ encapsulation: ViewEncapsulation.ShadowDom
+})
+class ShadowSlotComponent {
+}
+
+@Component({
+ selector: 'shadow-slots-comp',
+ template:
+ '',
+ encapsulation: ViewEncapsulation.ShadowDom
+})
+class ShadowSlotsComponent {
+}
+
+@NgModule({
+ imports: [BrowserModule],
+ declarations: [ShadowComponent, ShadowSlotComponent, ShadowSlotsComponent, StyledShadowComponent],
+ entryComponents:
+ [ShadowComponent, ShadowSlotComponent, ShadowSlotsComponent, StyledShadowComponent],
+})
+class TestModule {
+ ngDoBootstrap() {}
+}
diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts
index d781017237..74c91b19eb 100644
--- a/tools/public_api_guard/core/core.d.ts
+++ b/tools/public_api_guard/core/core.d.ts
@@ -932,6 +932,7 @@ export declare enum ViewEncapsulation {
Emulated = 0,
Native = 1,
None = 2,
+ ShadowDom = 3,
}
export declare abstract class ViewRef extends ChangeDetectorRef {