fix(platform-server): reflect properties to attributes for known elements, for serialization

This commit is contained in:
Alex Rickabaugh 2017-02-13 15:17:40 -08:00 committed by Igor Minar
parent 9559d3e949
commit 047cda5b3c
3 changed files with 48 additions and 3 deletions

View File

@ -367,7 +367,7 @@ export class Parse5DomAdapter extends DomAdapter {
return this.querySelectorAll(element, '.' + name);
}
getElementsByTagName(element: any, name: string): HTMLElement[] {
throw _notImplemented('getElementsByTagName');
return this.querySelectorAll(element, name);
}
classList(element: any): string[] {
let classAttrValue: any = null;

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {DomElementSchemaRegistry} from '@angular/compiler';
import {APP_ID, Inject, Injectable, NgZone, RenderComponentType, Renderer, RendererV2, RootRenderer, ViewEncapsulation} from '@angular/core';
import {AnimationDriver, DOCUMENT} from '@angular/platform-browser';
@ -16,9 +17,13 @@ import {NAMESPACE_URIS, SharedStylesHost, flattenStyles, getDOM, isNamespaced, s
const TEMPLATE_COMMENT_TEXT = 'template bindings={}';
const TEMPLATE_BINDINGS_EXP = /^template bindings=(.*)$/;
const EMPTY_ARRAY: any[] = [];
@Injectable()
export class ServerRootRenderer implements RootRenderer {
protected registeredComponents: Map<string, ServerRenderer> = new Map<string, ServerRenderer>();
private _schema = new DomElementSchemaRegistry();
constructor(
@Inject(DOCUMENT) public document: any, public sharedStylesHost: SharedStylesHost,
public animationDriver: AnimationDriver, @Inject(APP_ID) public appId: string,
@ -28,7 +33,7 @@ export class ServerRootRenderer implements RootRenderer {
if (!renderer) {
renderer = new ServerRenderer(
this, componentProto, this.animationDriver, `${this.appId}-${componentProto.id}`,
this._zone);
this._zone, this._schema);
this.registeredComponents.set(componentProto.id, renderer);
}
return renderer;
@ -42,7 +47,8 @@ export class ServerRenderer implements Renderer {
constructor(
private _rootRenderer: ServerRootRenderer, private componentProto: RenderComponentType,
private _animationDriver: AnimationDriver, styleShimId: string, private _zone: NgZone) {
private _animationDriver: AnimationDriver, styleShimId: string, private _zone: NgZone,
private _schema: DomElementSchemaRegistry) {
this._styles = flattenStyles(styleShimId, componentProto.styles, []);
if (componentProto.encapsulation === ViewEncapsulation.Native) {
throw new Error('Native encapsulation is not supported on the server!');
@ -141,8 +147,27 @@ export class ServerRenderer implements Renderer {
return this.listen(renderElement, name, callback);
}
// The value was validated already as a property binding, against the property name.
// To know this value is safe to use as an attribute, the security context of the
// attribute with the given name is checked against that security context of the
// property.
private _isSafeToReflectProperty(tagName: string, propertyName: string): boolean {
return this._schema.securityContext(tagName, propertyName, true) ===
this._schema.securityContext(tagName, propertyName, false);
}
setElementProperty(renderElement: any, propertyName: string, propertyValue: any): void {
getDOM().setProperty(renderElement, propertyName, propertyValue);
// Mirror property values for known HTML element properties in the attributes.
const tagName = (renderElement.tagName as string).toLowerCase();
if (isPresent(propertyValue) &&
(typeof propertyValue === 'number' || typeof propertyValue == 'string') &&
this._schema.hasElement(tagName, EMPTY_ARRAY) &&
this._schema.hasProperty(tagName, propertyName, EMPTY_ARRAY) &&
this._isSafeToReflectProperty(tagName, propertyName)) {
this.setElementAttribute(renderElement, propertyName, propertyValue.toString());
}
}
setElementAttribute(renderElement: any, attributeName: string, attributeValue: string): void {

View File

@ -90,6 +90,14 @@ export class HttpBeforeExampleModule {
export class HttpAfterExampleModule {
}
@Component({selector: 'app', template: `<img [src]="'link'">`})
class ImageApp {
}
@NgModule({declarations: [ImageApp], imports: [ServerModule], bootstrap: [ImageApp]})
class ImageExampleModule {
}
export function main() {
if (getDOM().supportsDOMEvents()) return; // NODE only
@ -143,6 +151,18 @@ export function main() {
});
}));
it('copies known properties to attributes', async(() => {
const platform = platformDynamicServer(
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
platform.bootstrapModule(ImageExampleModule).then(ref => {
const appRef: ApplicationRef = ref.injector.get(ApplicationRef);
const app = appRef.components[0].location.nativeElement;
const img = getDOM().getElementsByTagName(app, 'img')[0] as any;
expect(img.attribs['src']).toEqual('link');
});
}));
describe('PlatformLocation', () => {
it('is injectable', async(() => {
const platform = platformDynamicServer(