Skip to content

Commit

Permalink
fix(platform-server): avoid clash between server and client style enc…
Browse files Browse the repository at this point in the history
…apsulation 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
  • Loading branch information
vikerman authored and vicb committed May 30, 2018
1 parent 906b3ec commit e9f2203
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 8 deletions.
13 changes: 10 additions & 3 deletions packages/platform-browser/src/dom/events/dom_events.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -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() {
Expand Down
Expand Up @@ -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',
Expand Down
8 changes: 5 additions & 3 deletions packages/platform-server/src/server_renderer.ts
Expand Up @@ -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, ''); }
Expand Down
15 changes: 14 additions & 1 deletion packages/platform-server/test/integration_spec.ts
Expand Up @@ -154,7 +154,11 @@ class MyAnimationApp {
class AnimationServerModule {
}

@Component({selector: 'app', template: `Works!`, styles: [':host { color: red; }']})
@Component({
selector: 'app',
template: `<div>Works!</div>`,
styles: ['div {color: blue; } :host { color: red; }']
})
class MyStylesApp {
}

Expand Down Expand Up @@ -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(
/<html><head><style ng-transition="example-styles">div\[_ngcontent-sc\d+\] {color: blue; } \[_nghost-sc\d+\] { color: red; }<\/style><\/head><body><app _nghost-sc\d+="" ng-version="0.0.0-PLACEHOLDER"><div _ngcontent-sc\d+="">Works!<\/div><\/app><\/body><\/html>/);
called = true;
});
}));

it('should handle false values on attributes', async(() => {
renderModule(FalseAttributesModule, {document: doc}).then(output => {
expect(output).toBe(
Expand Down

0 comments on commit e9f2203

Please sign in to comment.