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);
|
||||
}
|
||||
getElementsByTagName(element: any, name: string): HTMLElement[] {
|
||||
throw _notImplemented('getElementsByTagName');
|
||||
return this.querySelectorAll(element, name);
|
||||
}
|
||||
classList(element: any): string[] {
|
||||
let classAttrValue: any = null;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue