From 4dae001a4b710c463f8ac802e08b5ff3d30f1c2c Mon Sep 17 00:00:00 2001 From: Jason Jean Date: Mon, 13 Mar 2017 16:22:03 -0400 Subject: [PATCH] fix(platform-server): fix get/set title in parse5 adapter (#14965) --- .../platform-server/src/parse5_adapter.ts | 16 +++++++++-- .../platform-server/test/integration_spec.ts | 27 ++++++++++++++++++- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/packages/platform-server/src/parse5_adapter.ts b/packages/platform-server/src/parse5_adapter.ts index a612086404c64..9d909bff2260c 100644 --- a/packages/platform-server/src/parse5_adapter.ts +++ b/packages/platform-server/src/parse5_adapter.ts @@ -491,8 +491,10 @@ export class Parse5DomAdapter extends DomAdapter { return newDoc; } getBoundingClientRect(el: any): any { return {left: 0, top: 0, width: 0, height: 0}; } - getTitle(doc: Document): string { return doc.title || ''; } - setTitle(doc: Document, newTitle: string) { doc.title = newTitle; } + getTitle(doc: Document): string { return this.getText(this.getTitleNode(doc)) || ''; } + setTitle(doc: Document, newTitle: string) { + this.setText(this.getTitleNode(doc), newTitle || ''); + } isTemplateElement(el: any): boolean { return this.isElementNode(el) && this.tagName(el) === 'template'; } @@ -591,6 +593,16 @@ export class Parse5DomAdapter extends DomAdapter { getCookie(name: string): string { throw new Error('not implemented'); } setCookie(name: string, value: string) { throw new Error('not implemented'); } animate(element: any, keyframes: any[], options: any): any { throw new Error('not implemented'); } + private getTitleNode(doc: Document) { + let title = this.querySelector(doc, 'title'); + + if (!title) { + title = this.createElement('title'); + this.appendChild(this.querySelector(doc, 'head'), title); + } + + return title; + } } // TODO: build a proper list, this one is all the keys of a HTMLInputElement diff --git a/packages/platform-server/test/integration_spec.ts b/packages/platform-server/test/integration_spec.ts index 92a3d2d158f68..07a16e72d58a4 100644 --- a/packages/platform-server/test/integration_spec.ts +++ b/packages/platform-server/test/integration_spec.ts @@ -11,7 +11,7 @@ import {ApplicationRef, CompilerFactory, Component, NgModule, NgModuleRef, NgZon import {TestBed, async, inject} from '@angular/core/testing'; import {Http, HttpModule, Response, ResponseOptions, XHRBackend} from '@angular/http'; import {MockBackend, MockConnection} from '@angular/http/testing'; -import {BrowserModule, DOCUMENT} from '@angular/platform-browser'; +import {BrowserModule, DOCUMENT, Title} from '@angular/platform-browser'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {INITIAL_CONFIG, PlatformState, ServerModule, platformDynamicServer, renderModule, renderModuleFactory} from '@angular/platform-server'; import {Subscription} from 'rxjs/Subscription'; @@ -43,6 +43,16 @@ class MyServerApp2 { class ExampleModule2 { } +@Component({selector: 'app', template: ``}) +class TitleApp { + constructor(private title: Title) {} + ngOnInit() { this.title.setTitle('Test App Title'); } +} + +@NgModule({declarations: [TitleApp], imports: [ServerModule], bootstrap: [TitleApp]}) +class TitleAppModule { +} + @Component({selector: 'app', template: '{{text}}'}) class MyAsyncServerApp { text = ''; @@ -145,6 +155,21 @@ export function main() { }); })); + it('adds title to the document using Title service', async(() => { + const platform = platformDynamicServer([{ + provide: INITIAL_CONFIG, + useValue: + {document: ''} + }]); + platform.bootstrapModule(TitleAppModule).then(ref => { + const state = ref.injector.get(PlatformState); + const doc = ref.injector.get(DOCUMENT); + const title = getDOM().querySelector(doc, 'title'); + expect(getDOM().getText(title)).toBe('Test App Title'); + expect(state.renderToString()).toContain('Test App Title'); + }); + })); + it('adds styles with ng-transition attribute', async(() => { const platform = platformDynamicServer([{ provide: INITIAL_CONFIG,