From b96a3c8def377ce393341ecea8bfd7bbd61d3e47 Mon Sep 17 00:00:00 2001 From: Vikram Subramanian Date: Mon, 28 May 2018 00:20:53 -0700 Subject: [PATCH] fix(platform-server): avoid clash between server and client style encapsulation attributes (#24158) Previously the style encapsulation attributes(_nghost-* and _ngcontent-*) created on the server could overlap with the attributes and styles created by the client side app when it botstraps. In case the client is bootstrapping a lazy route, the client side styles are added before the server-side styles are removed. If the components on the client are bootstrapped in a different order than on the server, the styles generated by the client will cause the elements on the server to have the wrong styles. The fix puts the styles and attributes generated on the server in a completely differemt space so that they are not affected by the client generated styles. The client generated styles will only affect elements bootstrapped on the client. PR Close #24158 --- .../platform-browser/src/dom/events/dom_events.ts | 13 ++++++++++--- .../test/dom/events/event_manager_spec.ts | 2 +- packages/platform-server/src/server_renderer.ts | 8 +++++--- packages/platform-server/test/integration_spec.ts | 15 ++++++++++++++- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/packages/platform-browser/src/dom/events/dom_events.ts b/packages/platform-browser/src/dom/events/dom_events.ts index 6c1138d30e..5b4c6acaac 100644 --- a/packages/platform-browser/src/dom/events/dom_events.ts +++ b/packages/platform-browser/src/dom/events/dom_events.ts @@ -6,7 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {Inject, Injectable, NgZone} from '@angular/core'; +import {isPlatformServer} from '@angular/common'; +import {Inject, Injectable, NgZone, Optional, PLATFORM_ID} from '@angular/core'; + + // Import zero symbols from zone.js. This causes the zone ambient type to be // added to the type-checker, without emitting any runtime module load statement import {} from 'zone.js'; @@ -103,10 +106,14 @@ const globalListener = function(event: Event) { @Injectable() export class DomEventsPlugin extends EventManagerPlugin { - constructor(@Inject(DOCUMENT) doc: any, private ngZone: NgZone) { + constructor( + @Inject(DOCUMENT) doc: any, private ngZone: NgZone, + @Optional() @Inject(PLATFORM_ID) platformId: {}|null) { super(doc); - this.patchEvent(); + if (!platformId || !isPlatformServer(platformId)) { + this.patchEvent(); + } } private patchEvent() { diff --git a/packages/platform-browser/test/dom/events/event_manager_spec.ts b/packages/platform-browser/test/dom/events/event_manager_spec.ts index 185bf98822..b294e4e5c0 100644 --- a/packages/platform-browser/test/dom/events/event_manager_spec.ts +++ b/packages/platform-browser/test/dom/events/event_manager_spec.ts @@ -24,7 +24,7 @@ import {el} from '../../../testing/src/browser_util'; beforeEach(() => { doc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument(); zone = new NgZone({}); - domEventPlugin = new DomEventsPlugin(doc, zone); + domEventPlugin = new DomEventsPlugin(doc, zone, null); }); it('should delegate event bindings to plugins that are passed in from the most generic one to the most specific one', diff --git a/packages/platform-server/src/server_renderer.ts b/packages/platform-server/src/server_renderer.ts index 67c48be90a..6b8d1dfc34 100644 --- a/packages/platform-server/src/server_renderer.ts +++ b/packages/platform-server/src/server_renderer.ts @@ -206,11 +206,13 @@ class EmulatedEncapsulationServerRenderer2 extends DefaultServerRenderer2 { eventManager: EventManager, document: any, ngZone: NgZone, sharedStylesHost: SharedStylesHost, schema: DomElementSchemaRegistry, private component: RendererType2) { super(eventManager, document, ngZone, schema); - const styles = flattenStyles(component.id, component.styles, []); + // Add a 's' prefix to style attributes to indicate server. + const componentId = 's' + component.id; + const styles = flattenStyles(componentId, component.styles, []); sharedStylesHost.addStyles(styles); - this.contentAttr = shimContentAttribute(component.id); - this.hostAttr = shimHostAttribute(component.id); + this.contentAttr = shimContentAttribute(componentId); + this.hostAttr = shimHostAttribute(componentId); } applyToHost(element: any) { super.setAttribute(element, this.hostAttr, ''); } diff --git a/packages/platform-server/test/integration_spec.ts b/packages/platform-server/test/integration_spec.ts index bc27c9d856..435f2a5765 100644 --- a/packages/platform-server/test/integration_spec.ts +++ b/packages/platform-server/test/integration_spec.ts @@ -154,7 +154,11 @@ class MyAnimationApp { class AnimationServerModule { } -@Component({selector: 'app', template: `Works!`, styles: [':host { color: red; }']}) +@Component({ + selector: 'app', + template: `
Works!
`, + styles: ['div {color: blue; } :host { color: red; }'] +}) class MyStylesApp { } @@ -548,6 +552,15 @@ class EscapedTransferStoreModule { }); })); + + it('sets a prefix for the _nghost and _ngcontent attributes', async(() => { + renderModule(ExampleStylesModule, {document: doc}).then(output => { + expect(output).toMatch( + /