fix(platform-server): reflect properties to attributes for known elements, for serialization
This commit is contained in:
parent
9559d3e949
commit
047cda5b3c
|
@ -367,7 +367,7 @@ export class Parse5DomAdapter extends DomAdapter {
|
||||||
return this.querySelectorAll(element, '.' + name);
|
return this.querySelectorAll(element, '.' + name);
|
||||||
}
|
}
|
||||||
getElementsByTagName(element: any, name: string): HTMLElement[] {
|
getElementsByTagName(element: any, name: string): HTMLElement[] {
|
||||||
throw _notImplemented('getElementsByTagName');
|
return this.querySelectorAll(element, name);
|
||||||
}
|
}
|
||||||
classList(element: any): string[] {
|
classList(element: any): string[] {
|
||||||
let classAttrValue: any = null;
|
let classAttrValue: any = null;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {APP_ID, Inject, Injectable, NgZone, RenderComponentType, Renderer, RendererV2, RootRenderer, ViewEncapsulation} from '@angular/core';
|
||||||
import {AnimationDriver, DOCUMENT} from '@angular/platform-browser';
|
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_COMMENT_TEXT = 'template bindings={}';
|
||||||
const TEMPLATE_BINDINGS_EXP = /^template bindings=(.*)$/;
|
const TEMPLATE_BINDINGS_EXP = /^template bindings=(.*)$/;
|
||||||
|
|
||||||
|
const EMPTY_ARRAY: any[] = [];
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ServerRootRenderer implements RootRenderer {
|
export class ServerRootRenderer implements RootRenderer {
|
||||||
protected registeredComponents: Map<string, ServerRenderer> = new Map<string, ServerRenderer>();
|
protected registeredComponents: Map<string, ServerRenderer> = new Map<string, ServerRenderer>();
|
||||||
|
private _schema = new DomElementSchemaRegistry();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DOCUMENT) public document: any, public sharedStylesHost: SharedStylesHost,
|
@Inject(DOCUMENT) public document: any, public sharedStylesHost: SharedStylesHost,
|
||||||
public animationDriver: AnimationDriver, @Inject(APP_ID) public appId: string,
|
public animationDriver: AnimationDriver, @Inject(APP_ID) public appId: string,
|
||||||
|
@ -28,7 +33,7 @@ export class ServerRootRenderer implements RootRenderer {
|
||||||
if (!renderer) {
|
if (!renderer) {
|
||||||
renderer = new ServerRenderer(
|
renderer = new ServerRenderer(
|
||||||
this, componentProto, this.animationDriver, `${this.appId}-${componentProto.id}`,
|
this, componentProto, this.animationDriver, `${this.appId}-${componentProto.id}`,
|
||||||
this._zone);
|
this._zone, this._schema);
|
||||||
this.registeredComponents.set(componentProto.id, renderer);
|
this.registeredComponents.set(componentProto.id, renderer);
|
||||||
}
|
}
|
||||||
return renderer;
|
return renderer;
|
||||||
|
@ -42,7 +47,8 @@ export class ServerRenderer implements Renderer {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _rootRenderer: ServerRootRenderer, private componentProto: RenderComponentType,
|
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, []);
|
this._styles = flattenStyles(styleShimId, componentProto.styles, []);
|
||||||
if (componentProto.encapsulation === ViewEncapsulation.Native) {
|
if (componentProto.encapsulation === ViewEncapsulation.Native) {
|
||||||
throw new Error('Native encapsulation is not supported on the server!');
|
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);
|
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 {
|
setElementProperty(renderElement: any, propertyName: string, propertyValue: any): void {
|
||||||
getDOM().setProperty(renderElement, propertyName, propertyValue);
|
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 {
|
setElementAttribute(renderElement: any, attributeName: string, attributeValue: string): void {
|
||||||
|
|
|
@ -90,6 +90,14 @@ export class HttpBeforeExampleModule {
|
||||||
export class HttpAfterExampleModule {
|
export class HttpAfterExampleModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'app', template: `<img [src]="'link'">`})
|
||||||
|
class ImageApp {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({declarations: [ImageApp], imports: [ServerModule], bootstrap: [ImageApp]})
|
||||||
|
class ImageExampleModule {
|
||||||
|
}
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
if (getDOM().supportsDOMEvents()) return; // NODE only
|
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', () => {
|
describe('PlatformLocation', () => {
|
||||||
it('is injectable', async(() => {
|
it('is injectable', async(() => {
|
||||||
const platform = platformDynamicServer(
|
const platform = platformDynamicServer(
|
||||||
|
|
Loading…
Reference in New Issue