From 0299d4af0013308f1a303b5df16a99e61b6fabda Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Mon, 5 Oct 2015 09:43:00 -0700 Subject: [PATCH] fix(core): keep styles for `ViewEncapsulation.Native` isolated per component BREAKING CHANGE: - `Renderer.registerComponent` now takes an additional argument. Fixes #4513 Closes #4524 --- .../src/core/linker/proto_view_factory.ts | 2 +- modules/angular2/src/core/render/api.ts | 3 +- .../src/core/render/dom/dom_renderer.ts | 19 +++++++++-- .../angular2/src/core/render/view_factory.ts | 4 +-- .../angular2/src/web_workers/ui/renderer.ts | 3 +- .../src/web_workers/worker/renderer.ts | 6 ++-- .../linker/projection_integration_spec.ts | 33 ++++++++++++------- .../test/core/render/view_factory_spec.ts | 2 +- 8 files changed, 50 insertions(+), 22 deletions(-) diff --git a/modules/angular2/src/core/linker/proto_view_factory.ts b/modules/angular2/src/core/linker/proto_view_factory.ts index 1b62d87f1d..0caa431199 100644 --- a/modules/angular2/src/core/linker/proto_view_factory.ts +++ b/modules/angular2/src/core/linker/proto_view_factory.ts @@ -71,7 +71,7 @@ export class ProtoViewFactory { var compiledTemplateData = cmd.template.getData(this._appId); this._renderer.registerComponentTemplate(cmd.templateId, compiledTemplateData.commands, - compiledTemplateData.styles); + compiledTemplateData.styles, cmd.nativeShadow); var boundPipes = this._flattenPipes(view).map(pipe => this._bindPipe(pipe)); nestedProtoView = new AppProtoView(compiledTemplateData.commands, ViewType.COMPONENT, true, diff --git a/modules/angular2/src/core/render/api.ts b/modules/angular2/src/core/render/api.ts index dde8ec21ea..84c33e1f14 100644 --- a/modules/angular2/src/core/render/api.ts +++ b/modules/angular2/src/core/render/api.ts @@ -230,7 +230,8 @@ export class Renderer { * Once a template is registered it can be referenced via {@link RenderBeginComponentCmd} when * {@link #createProtoView creating Render ProtoView}. */ - registerComponentTemplate(templateId: number, commands: RenderTemplateCmd[], styles: string[]) {} + registerComponentTemplate(templateId: number, commands: RenderTemplateCmd[], styles: string[], + nativeShadow: boolean) {} /** * Creates a {@link RenderProtoViewRef} from an array of {@link RenderTemplateCmd}`s. diff --git a/modules/angular2/src/core/render/dom/dom_renderer.ts b/modules/angular2/src/core/render/dom/dom_renderer.ts index 42b7024a0e..c26215a8bb 100644 --- a/modules/angular2/src/core/render/dom/dom_renderer.ts +++ b/modules/angular2/src/core/render/dom/dom_renderer.ts @@ -35,6 +35,7 @@ import {camelCaseToDashCase} from './util'; @Injectable() export class DomRenderer implements Renderer, NodeFactory { private _componentCmds: Map = new Map(); + private _nativeShadowStyles: Map = new Map(); private _document; /** @@ -46,9 +47,14 @@ export class DomRenderer implements Renderer, NodeFactory { this._document = document; } - registerComponentTemplate(templateId: number, commands: RenderTemplateCmd[], styles: string[]) { + registerComponentTemplate(templateId: number, commands: RenderTemplateCmd[], styles: string[], + nativeShadow: boolean) { this._componentCmds.set(templateId, commands); - this._domSharedStylesHost.addStyles(styles); + if (nativeShadow) { + this._nativeShadowStyles.set(templateId, styles); + } else { + this._domSharedStylesHost.addStyles(styles); + } } resolveComponentTemplate(templateId: number): RenderTemplateCmd[] { @@ -193,7 +199,14 @@ export class DomRenderer implements Renderer, NodeFactory { DOM.setAttribute(node, attrNameAndValues[attrIdx], attrNameAndValues[attrIdx + 1]); } } - createShadowRoot(host: Node): Node { return DOM.createShadowRoot(host); } + createShadowRoot(host: Node, templateId: number): Node { + var sr = DOM.createShadowRoot(host); + var styles = this._nativeShadowStyles.get(templateId); + for (var i = 0; i < styles.length; i++) { + DOM.appendChild(sr, DOM.createStyleElement(styles[i])); + } + return sr; + } createText(value: string): Node { return DOM.createTextNode(isPresent(value) ? value : ''); } appendChild(parent: Node, child: Node) { DOM.appendChild(parent, child); } on(element: Node, eventName: string, callback: Function) { diff --git a/modules/angular2/src/core/render/view_factory.ts b/modules/angular2/src/core/render/view_factory.ts index d8f7ed59d4..a59845d6f7 100644 --- a/modules/angular2/src/core/render/view_factory.ts +++ b/modules/angular2/src/core/render/view_factory.ts @@ -70,7 +70,7 @@ export interface NodeFactory { createTemplateAnchor(attrNameAndValues: string[]): N; createElement(name: string, attrNameAndValues: string[]): N; mergeElement(existing: N, attrNameAndValues: string[]); - createShadowRoot(host: N): N; + createShadowRoot(host: N, templateId: number): N; createText(value: string): N; appendChild(parent: N, child: N); on(element: N, eventName: string, callback: Function); @@ -125,7 +125,7 @@ class RenderViewBuilder implements RenderCommandVisitor { var el = this._beginElement(cmd); var root = el; if (cmd.nativeShadow) { - root = this.factory.createShadowRoot(el); + root = this.factory.createShadowRoot(el, cmd.templateId); this.nativeShadowRoots.push(root); } var component = new Component(el, root, cmd, this.factory, this.allBuilders); diff --git a/modules/angular2/src/web_workers/ui/renderer.ts b/modules/angular2/src/web_workers/ui/renderer.ts index 023e81131f..7a149cfa38 100644 --- a/modules/angular2/src/web_workers/ui/renderer.ts +++ b/modules/angular2/src/web_workers/ui/renderer.ts @@ -31,7 +31,8 @@ export class MessageBasedRenderer { var broker = this._brokerFactory.createMessageBroker(RENDERER_CHANNEL); this._bus.initChannel(EVENT_CHANNEL); - broker.registerMethod("registerComponentTemplate", [PRIMITIVE, WebWorkerTemplateCmd, PRIMITIVE], + broker.registerMethod("registerComponentTemplate", + [PRIMITIVE, WebWorkerTemplateCmd, PRIMITIVE, PRIMITIVE], bind(this._renderer.registerComponentTemplate, this._renderer)); broker.registerMethod("createProtoView", [WebWorkerTemplateCmd, PRIMITIVE], bind(this._createProtoView, this)); diff --git a/modules/angular2/src/web_workers/worker/renderer.ts b/modules/angular2/src/web_workers/worker/renderer.ts index 9defff4368..86a050fc42 100644 --- a/modules/angular2/src/web_workers/worker/renderer.ts +++ b/modules/angular2/src/web_workers/worker/renderer.ts @@ -35,11 +35,13 @@ export class WebWorkerRenderer implements Renderer { this._messageBroker = messageBrokerFactory.createMessageBroker(RENDERER_CHANNEL); } - registerComponentTemplate(templateId: number, commands: RenderTemplateCmd[], styles: string[]) { + registerComponentTemplate(templateId: number, commands: RenderTemplateCmd[], styles: string[], + nativeShadow: boolean) { var fnArgs = [ new FnArg(templateId, null), new FnArg(commands, WebWorkerTemplateCmd), - new FnArg(styles, null) + new FnArg(styles, null), + new FnArg(nativeShadow, null) ]; var args = new UiArguments("registerComponentTemplate", fnArgs); this._messageBroker.runOnService(args, null); diff --git a/modules/angular2/test/core/linker/projection_integration_spec.ts b/modules/angular2/test/core/linker/projection_integration_spec.ts index e1a443d348..e293352924 100644 --- a/modules/angular2/test/core/linker/projection_integration_spec.ts +++ b/modules/angular2/test/core/linker/projection_integration_spec.ts @@ -423,18 +423,18 @@ export function main() { })); if (DOM.supportsNativeShadowDOM()) { - it('should support native content projection', + it('should support native content projection and isolate styles per component', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { tcb.overrideView(MainComp, new ViewMetadata({ - template: '' + - '
A
' + - '
', - directives: [SimpleNative] + template: '
A
' + + '
B
', + directives: [SimpleNative1, SimpleNative2] })) .createAsync(MainComp) .then((main) => { - - expect(main.debugElement.nativeElement).toHaveText('SIMPLE(A)'); + var childNodes = DOM.childNodes(main.debugElement.nativeElement); + expect(childNodes[0]).toHaveText('div {color: red}SIMPLE1(A)'); + expect(childNodes[1]).toHaveText('div {color: blue}SIMPLE2(B)'); async.done(); }); })); @@ -485,13 +485,24 @@ class Simple { stringProp: string = ''; } -@Component({selector: 'simple-native'}) +@Component({selector: 'simple-native1'}) @View({ - template: 'SIMPLE()', + template: 'SIMPLE1()', directives: [], - encapsulation: ViewEncapsulation.Native + encapsulation: ViewEncapsulation.Native, + styles: ['div {color: red}'] }) -class SimpleNative { +class SimpleNative1 { +} + +@Component({selector: 'simple-native2'}) +@View({ + template: 'SIMPLE2()', + directives: [], + encapsulation: ViewEncapsulation.Native, + styles: ['div {color: blue}'] +}) +class SimpleNative2 { } @Component({selector: 'empty'}) diff --git a/modules/angular2/test/core/render/view_factory_spec.ts b/modules/angular2/test/core/render/view_factory_spec.ts index 6de5731d9e..0c18ac5c73 100644 --- a/modules/angular2/test/core/render/view_factory_spec.ts +++ b/modules/angular2/test/core/render/view_factory_spec.ts @@ -521,7 +521,7 @@ class DomNodeFactory implements NodeFactory { DOM.setAttribute(el, attrNameAndValues[attrIdx], attrNameAndValues[attrIdx + 1]); } } - createShadowRoot(host: Node): Node { + createShadowRoot(host: Node, templateId: number): Node { var root = DOM.createElement('shadow-root'); DOM.appendChild(host, root); return root;