diff --git a/modules/@angular/platform-server/src/server.ts b/modules/@angular/platform-server/src/server.ts index 764e31542e..780fc30a94 100644 --- a/modules/@angular/platform-server/src/server.ts +++ b/modules/@angular/platform-server/src/server.ts @@ -8,15 +8,15 @@ import {PlatformLocation} from '@angular/common'; import {platformCoreDynamic} from '@angular/compiler'; -import {InjectionToken, Injector, NgModule, PLATFORM_INITIALIZER, PlatformRef, Provider, RENDERER_V2_DIRECT, RendererV2, RootRenderer, createPlatformFactory, isDevMode, platformCore} from '@angular/core'; +import {APP_BOOTSTRAP_LISTENER, InjectionToken, Injector, NgModule, PLATFORM_INITIALIZER, PlatformRef, Provider, RENDERER_V2_DIRECT, RendererV2, RootRenderer, createPlatformFactory, isDevMode, platformCore} from '@angular/core'; import {BrowserModule, DOCUMENT} from '@angular/platform-browser'; - import {ServerPlatformLocation} from './location'; import {Parse5DomAdapter, parseDocument} from './parse5_adapter'; import {PlatformState} from './platform_state'; import {ALLOW_MULTIPLE_PLATFORMS, DebugDomRendererV2, DebugDomRootRenderer} from './private_import_core'; import {SharedStylesHost, getDOM} from './private_import_platform-browser'; import {ServerRendererV2, ServerRootRenderer} from './server_renderer'; +import {ServerStylesHost} from './styles_host'; function notSupported(feature: string): Error { throw new Error(`platform-server does not support '${feature}'.`); @@ -42,12 +42,24 @@ export function _createDebugRendererV2(renderer: RendererV2): RendererV2 { return isDevMode() ? new DebugDomRendererV2(renderer) : renderer; } +export function _addStylesToRootComponentFactory(stylesHost: ServerStylesHost) { + const initializer = () => stylesHost.rootComponentIsReady(); + return initializer; +} + export const SERVER_RENDER_PROVIDERS: Provider[] = [ - ServerRootRenderer, {provide: RENDERER_V2_DIRECT, useClass: ServerRendererV2}, + ServerRootRenderer, + {provide: RENDERER_V2_DIRECT, useClass: ServerRendererV2}, {provide: RendererV2, useFactory: _createDebugRendererV2, deps: [RENDERER_V2_DIRECT]}, {provide: RootRenderer, useFactory: _createConditionalRootRenderer, deps: [ServerRootRenderer]}, - // use plain SharedStylesHost, not the DomSharedStylesHost - SharedStylesHost + ServerStylesHost, + {provide: SharedStylesHost, useExisting: ServerStylesHost}, + { + provide: APP_BOOTSTRAP_LISTENER, + useFactory: _addStylesToRootComponentFactory, + deps: [ServerStylesHost], + multi: true + }, ]; /** diff --git a/modules/@angular/platform-server/src/server_renderer.ts b/modules/@angular/platform-server/src/server_renderer.ts index 4d2bd0aaf1..07994c860c 100644 --- a/modules/@angular/platform-server/src/server_renderer.ts +++ b/modules/@angular/platform-server/src/server_renderer.ts @@ -47,6 +47,7 @@ export class ServerRenderer implements Renderer { if (componentProto.encapsulation === ViewEncapsulation.Native) { throw new Error('Native encapsulation is not supported on the server!'); } + this._rootRenderer.sharedStylesHost.addStyles(this._styles); if (this.componentProto.encapsulation === ViewEncapsulation.Emulated) { this._contentAttr = shimContentAttribute(styleShimId); this._hostAttr = shimHostAttribute(styleShimId); diff --git a/modules/@angular/platform-server/src/styles_host.ts b/modules/@angular/platform-server/src/styles_host.ts new file mode 100644 index 0000000000..01f417b873 --- /dev/null +++ b/modules/@angular/platform-server/src/styles_host.ts @@ -0,0 +1,45 @@ +/** + * @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 {ApplicationRef, Inject, Injectable} from '@angular/core'; +import {DOCUMENT} from '@angular/platform-browser'; + +import {Parse5DomAdapter} from './parse5_adapter'; +import {SharedStylesHost, getDOM} from './private_import_platform-browser'; + +@Injectable() +export class ServerStylesHost extends SharedStylesHost { + private root: any = null; + private buffer: string[] = []; + + constructor(@Inject(DOCUMENT) private doc: any, private appRef: ApplicationRef) { super(); } + + private _addStyle(style: string): void { + let adapter: Parse5DomAdapter = getDOM() as Parse5DomAdapter; + const el = adapter.createElement('style'); + adapter.setText(el, style); + adapter.appendChild(this.root, el); + } + + onStylesAdded(additions: Set) { + if (!this.root) { + additions.forEach(style => this.buffer.push(style)); + } else { + additions.forEach(style => this._addStyle(style)); + } + } + + rootComponentIsReady(): void { + if (!!this.root) { + return; + } + this.root = this.appRef.components[0].location.nativeElement; + this.buffer.forEach(style => this._addStyle(style)); + this.buffer = null; + } +} diff --git a/modules/@angular/platform-server/test/integration_spec.ts b/modules/@angular/platform-server/test/integration_spec.ts index 645c72ac76..0cf4070c7a 100644 --- a/modules/@angular/platform-server/test/integration_spec.ts +++ b/modules/@angular/platform-server/test/integration_spec.ts @@ -47,6 +47,14 @@ class MyAsyncServerApp { class AsyncServerModule { } +@Component({selector: 'app', template: `Works!`, styles: [':host { color: red; }']}) +class MyStylesApp { +} + +@NgModule({declarations: [MyStylesApp], imports: [ServerModule], bootstrap: [MyStylesApp]}) +class ExampleStylesModule { +} + export function main() { if (getDOM().supportsDOMEvents()) return; // NODE only @@ -87,6 +95,19 @@ export function main() { }); })); + it('adds styles to the root component', async(() => { + const platform = platformDynamicServer( + [{provide: INITIAL_CONFIG, useValue: {document: ''}}]); + platform.bootstrapModule(ExampleStylesModule).then(ref => { + const appRef: ApplicationRef = ref.injector.get(ApplicationRef); + const app = appRef.components[0].location.nativeElement; + expect(app.children.length).toBe(2); + const style = app.children[1]; + expect(style.type).toBe('style'); + expect(style.children[0].data).toContain('color: red'); + }); + })); + describe('PlatformLocation', () => { it('is injectable', async(() => { const platform = platformDynamicServer(