feat(vars): assignment of component or element instance to vars.

This commit is contained in:
Jeremy Elbourn
2015-02-06 12:17:34 -08:00
parent ab733bd80e
commit 6dbfe0dc2e
10 changed files with 224 additions and 21 deletions

View File

@ -211,10 +211,22 @@ export class ProtoElementInjector {
index:int;
view:View;
distanceToParent:number;
/** Whether the element is exported as $implicit. */
exportElement:boolean;
/** Whether the component instance is exported as $implicit. */
exportComponent:boolean;
/** The variable name that will be set to $implicit for the element. */
exportImplicitName:string;
constructor(parent:ProtoElementInjector, index:int, bindings:List, firstBindingIsComponent:boolean = false, distanceToParent:number = 0) {
this.parent = parent;
this.index = index;
this.distanceToParent = distanceToParent;
this.exportComponent = false;
this.exportElement = false;
this._binding0IsComponent = firstBindingIsComponent;
this._binding0 = null; this._keyId0 = null;
@ -405,6 +417,11 @@ export class ElementInjector extends TreeNode {
return this._preBuiltObjects.element.domElement === el;
}
/** Gets the NgElement associated with this ElementInjector */
getNgElement() {
return this._preBuiltObjects.element;
}
getComponent() {
if (this._proto._binding0IsComponent) {
return this._obj0;
@ -603,6 +620,21 @@ export class ElementInjector extends TreeNode {
hasEventEmitter(eventName: string) {
return this._proto.hasEventEmitter(eventName);
}
/** Gets whether this element is exporting a component instance as $implicit. */
isExportingComponent() {
return this._proto.exportComponent;
}
/** Gets whether this element is exporting its element as $implicit. */
isExportingElement() {
return this._proto.exportElement;
}
/** Get the name to which this element's $implicit is to be assigned. */
getExportImplicitName() {
return this._proto.exportImplicitName;
}
}
class OutOfBoundsAccess extends Error {

View File

@ -106,11 +106,17 @@ export class CompileElement {
MapWrapper.set(this.propertyBindings, property, expression);
}
addVariableBinding(directiveName:string, templateName:string) {
addVariableBinding(variableName:string, variableValue:string) {
if (isBlank(this.variableBindings)) {
this.variableBindings = MapWrapper.create();
}
MapWrapper.set(this.variableBindings, templateName, directiveName);
// Store the variable map from value to variable, reflecting how it will be used later by
// View. When a local is set to the view, a lookup for the variable name will take place keyed
// by the "value", or exported identifier. For example, ng-repeat sets a view local of "index".
// When this occurs, a lookup keyed by "index" must occur to find if there is a var referencing
// it.
MapWrapper.set(this.variableBindings, variableValue, variableName);
}
addEventBinding(eventName:string, expression:AST) {

View File

@ -9,7 +9,16 @@ import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
// TODO(tbosch): Cannot make this const/final right now because of the transpiler...
var BIND_NAME_REGEXP = RegExpWrapper.create('^(?:(?:(bind)|(var)|(on))-(.+))|\\[([^\\]]+)\\]|\\(([^\\)]+)\\)');
// Group 1 = "bind"
// Group 2 = "var"
// Group 3 = "on"
// Group 4 = the identifier after "bind", "var", or "on"
// Group 5 = idenitifer inside square braces
// Group 6 = identifier inside parenthesis
// Group 7 = "#"
// Group 8 = identifier after "#"
var BIND_NAME_REGEXP = RegExpWrapper.create(
'^(?:(?:(bind)|(var)|(on))-(.+))|\\[([^\\]]+)\\]|\\(([^\\)]+)\\)|(#)(.+)');
/**
* Parses the property bindings on a single element.
@ -35,14 +44,12 @@ export class PropertyBindingParser extends CompileStep {
if (isPresent(bindParts[1])) {
// match: bind-prop
current.addPropertyBinding(bindParts[4], this._parseBinding(attrValue));
} else if (isPresent(bindParts[2])) {
// match: let-prop
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
// only be present on <template> elements any more!
if (!(current.element instanceof TemplateElement)) {
throw new BaseException('var-* is only allowed on <template> elements!');
}
current.addVariableBinding(bindParts[4], attrValue);
} else if (isPresent(bindParts[2]) || isPresent(bindParts[7])) {
// match: var-name / var-name="iden" / #name / #name="iden"
var identifier = (isPresent(bindParts[4]) && bindParts[4] !== '') ?
bindParts[4] : bindParts[8];
var value = attrValue == '' ? '\$implicit' : attrValue;
current.addVariableBinding(identifier, value);
} else if (isPresent(bindParts[3])) {
// match: on-prop
current.addEventBinding(bindParts[4], this._parseAction(attrValue));

View File

@ -1,5 +1,5 @@
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
import {Key} from 'angular2/di';
import {ProtoElementInjector, ComponentKeyMetaData, DirectiveBinding} from '../element_injector';
@ -39,7 +39,10 @@ export class ProtoElementInjectorBuilder extends CompileStep {
// but after the directives as we rely on that order
// in the element_binder_builder.
if (injectorBindings.length > 0) {
// Create a protoElementInjector for any element that either has bindings *or* has one
// or more var- defined. Elements with a var- defined need a their own element injector
// so that, when hydrating, $implicit can be set to the element.
if (injectorBindings.length > 0 || isPresent(current.variableBindings)) {
var protoView = current.inheritedProtoView;
var hasComponent = isPresent(current.componentDirective);
@ -49,6 +52,18 @@ export class ProtoElementInjectorBuilder extends CompileStep {
);
current.distanceToParentInjector = 0;
// Template directives are treated differently than other element with var- definitions.
if (isPresent(current.variableBindings) && !isPresent(current.templateDirective)) {
current.inheritedProtoElementInjector.exportComponent = hasComponent;
current.inheritedProtoElementInjector.exportElement = !hasComponent;
// experiment
var exportImplicitName = MapWrapper.get(current.variableBindings, '\$implicit');
if (isPresent(exportImplicitName)) {
current.inheritedProtoElementInjector.exportImplicitName = exportImplicitName;
}
}
} else {
current.inheritedProtoElementInjector = parentProtoElementInjector;
current.distanceToParentInjector = distanceToParentInjector;

View File

@ -40,6 +40,10 @@ export class ProtoViewBuilder extends CompileStep {
throw new BaseException('Only one nested view per element is allowed');
}
parent.inheritedElementBinder.nestedProtoView = inheritedProtoView;
// When current is a view root, the variable bindings are set to the *nested* proto view.
// The root view conceptually signifies a new "block scope" (the nested view), to which
// the variables are bound.
if (isPresent(parent.variableBindings)) {
MapWrapper.forEach(parent.variableBindings, (mappedName, varName) => {
inheritedProtoView.bindVariable(varName, mappedName);
@ -49,6 +53,17 @@ export class ProtoViewBuilder extends CompileStep {
} else if (isPresent(parent)) {
inheritedProtoView = parent.inheritedProtoView;
}
// The view's contextWithLocals needs to have a full set of variable names at construction time
// in order to prevent new variables from being set later in the lifecycle. Since we don't want
// to actually create variable bindings for the $implicit bindings, add to the
// protoContextLocals manually.
if (isPresent(current.variableBindings)) {
MapWrapper.forEach(current.variableBindings, (mappedName, varName) => {
MapWrapper.set(inheritedProtoView.protoContextLocals, mappedName, null);
});
}
current.inheritedProtoView = inheritedProtoView;
}
}

View File

@ -150,6 +150,16 @@ export class View {
var elementInjector = this.elementInjectors[i];
if (isPresent(elementInjector)) {
elementInjector.instantiateDirectives(appInjector, shadowDomAppInjector, this.preBuiltObjects[i]);
// The exporting of $implicit is a special case. Since multiple elements will all export
// the different values as $implicit, directly assign $implicit bindings to the variable
// name.
var exportImplicitName = elementInjector.getExportImplicitName();
if (elementInjector.isExportingComponent()) {
this.context.set(exportImplicitName, elementInjector.getComponent());
} else if (elementInjector.isExportingElement()) {
this.context.set(exportImplicitName, elementInjector.getNgElement().domElement);
}
}
if (isPresent(componentDirective)) {