feat(ivy): implement host bindings in JIT mode (#24479)

PR Close #24479
This commit is contained in:
Alex Rickabaugh
2018-06-12 16:58:09 -07:00
committed by Miško Hevery
parent 6d246d6c72
commit f00ae516eb
5 changed files with 117 additions and 19 deletions

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ConstantPool, R3DirectiveMetadata, WrappedNodeExpr, compileComponentFromMetadata as compileR3Component, compileDirectiveFromMetadata as compileR3Directive, jitExpression, makeBindingParser, parseTemplate} from '@angular/compiler';
import {ConstantPool, R3DirectiveMetadata, WrappedNodeExpr, compileComponentFromMetadata as compileR3Component, compileDirectiveFromMetadata as compileR3Directive, jitExpression, makeBindingParser, parseHostBindings, parseTemplate} from '@angular/compiler';
import {Component, Directive, HostBinding, Input, Output} from '../../metadata/directives';
import {Component, Directive, HostBinding, HostListener, Input, Output} from '../../metadata/directives';
import {ReflectionCapabilities} from '../../reflection/reflection_capabilities';
import {Type} from '../../type';
@ -19,6 +19,10 @@ import {getReflect, reflectDependencies} from './util';
let _pendingPromises: Promise<void>[] = [];
type StringMap = {
[key: string]: string
};
/**
* Compile an Angular component according to its decorator metadata, and patch the resulting
* ngComponentDef onto the component type.
@ -137,12 +141,14 @@ export function awaitCurrentlyCompilingComponents(): Promise<void> {
*/
function directiveMetadata(type: Type<any>, metadata: Directive): R3DirectiveMetadata {
// Reflect inputs and outputs.
const props = getReflect().propMetadata(type);
const inputs: {[key: string]: string} = {};
const outputs: {[key: string]: string} = {};
const propMetadata = getReflect().propMetadata(type);
const inputs: StringMap = {};
const outputs: StringMap = {};
for (let field in props) {
props[field].forEach(ann => {
const host = extractHostBindings(metadata, propMetadata);
for (let field in propMetadata) {
propMetadata[field].forEach(ann => {
if (isInput(ann)) {
inputs[field] = ann.bindingPropertyName || field;
} else if (isOutput(ann)) {
@ -155,14 +161,7 @@ function directiveMetadata(type: Type<any>, metadata: Directive): R3DirectiveMet
name: type.name,
type: new WrappedNodeExpr(type),
selector: metadata.selector !,
deps: reflectDependencies(type),
host: {
attributes: {},
listeners: {},
properties: {},
},
inputs,
outputs,
deps: reflectDependencies(type), host, inputs, outputs,
queries: [],
lifecycle: {
usesOnChanges: type.prototype.ngOnChanges !== undefined,
@ -171,6 +170,32 @@ function directiveMetadata(type: Type<any>, metadata: Directive): R3DirectiveMet
};
}
function extractHostBindings(metadata: Directive, propMetadata: {[key: string]: any[]}): {
attributes: StringMap,
listeners: StringMap,
properties: StringMap,
} {
// First parse the declarations from the metadata.
const {attributes, listeners, properties, animations} = parseHostBindings(metadata.host || {});
if (Object.keys(animations).length > 0) {
throw new Error(`Animation bindings are as-of-yet unsupported in Ivy`);
}
// Next, loop over the properties of the object, looking for @HostBinding and @HostListener.
for (let field in propMetadata) {
propMetadata[field].forEach(ann => {
if (isHostBinding(ann)) {
properties[ann.hostPropertyName || field] = field;
} else if (isHostListener(ann)) {
listeners[ann.eventName || field] = `${field}(${(ann.args || []).join(',')})`;
}
});
}
return {attributes, listeners, properties};
}
function isInput(value: any): value is Input {
return value.ngMetadataName === 'Input';
}
@ -178,3 +203,11 @@ function isInput(value: any): value is Input {
function isOutput(value: any): value is Output {
return value.ngMetadataName === 'Output';
}
function isHostBinding(value: any): value is HostBinding {
return value.ngMetadataName === 'HostBinding';
}
function isHostListener(value: any): value is HostListener {
return value.ngMetadataName === 'HostListener';
}

View File

@ -9,7 +9,7 @@
import {Injectable} from '@angular/core/src/di/injectable';
import {inject, setCurrentInjector} from '@angular/core/src/di/injector';
import {ivyEnabled} from '@angular/core/src/ivy_switch';
import {Component} from '@angular/core/src/metadata/directives';
import {Component, HostBinding, HostListener} from '@angular/core/src/metadata/directives';
import {NgModule, NgModuleDef} from '@angular/core/src/metadata/ng_module';
import {ComponentDef} from '@angular/core/src/render3/interfaces/definition';
@ -161,6 +161,29 @@ ivyEnabled && describe('render3 jit', () => {
expect(cmpDef.directiveDefs instanceof Function).toBe(true);
expect((cmpDef.directiveDefs as Function)()).toEqual([cmpDef]);
});
it('should add hostbindings and hostlisteners', () => {
@Component({
template: 'foo',
selector: 'foo',
host: {
'[class.red]': 'isRed',
'(click)': 'onClick()',
},
})
class Cmp {
@HostBinding('class.green')
green: boolean = false;
@HostListener('change', ['$event'])
onChange(event: any): void {}
}
const cmpDef = (Cmp as any).ngComponentDef as ComponentDef<Cmp>;
expect(cmpDef.hostBindings).toBeDefined();
expect(cmpDef.hostBindings !.length).toBe(2);
});
});
it('ensure at least one spec exists', () => {});