fix(change_detection): fixed reflect properties as attributes

Closes #3761
This commit is contained in:
vsavkin 2015-08-20 15:11:12 -07:00 committed by Victor Savkin
parent b6146394ae
commit a9ce454b21
28 changed files with 168 additions and 142 deletions

View File

@ -28,4 +28,5 @@ export {
KeyValueDiffers,
KeyValueDiffer,
KeyValueDifferFactory
} from 'angular2/src/change_detection/change_detection';

View File

@ -17,6 +17,5 @@ export {
ViewDefinition,
DOCUMENT,
APP_ID,
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES,
MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE
} from './src/render/render';

View File

@ -211,6 +211,10 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
this.dispatcher.notifyOnBinding(this._currentBinding(), value);
}
protected logBindingUpdate(value: any): void {
this.dispatcher.logBindingUpdate(this._currentBinding(), value);
}
protected addChange(changes: StringMap<string, any>, oldValue: any,
newValue: any): StringMap<string, any> {
if (isBlank(changes)) {

View File

@ -64,7 +64,8 @@ export class BindingRecord {
static createForDirective(ast: AST, propertyName: string, setter: SetterFn,
directiveRecord: DirectiveRecord): BindingRecord {
var t = new BindingTarget(DIRECTIVE, null, propertyName, null, ast.toString());
var elementIndex = directiveRecord.directiveIndex.elementIndex;
var t = new BindingTarget(DIRECTIVE, elementIndex, propertyName, null, ast.toString());
return new BindingRecord(DIRECTIVE, t, 0, ast, setter, null, directiveRecord);
}

View File

@ -84,8 +84,6 @@ export const defaultKeyValueDiffers = CONST_EXPR(new KeyValueDiffers(keyValDiff)
// dart2js. See https://github.com/dart-lang/sdk/issues/23630 for details.
export var preGeneratedProtoDetectors: StringMap<string, Function> = {};
export const PROTO_CHANGE_DETECTOR = CONST_EXPR(new OpaqueToken('ProtoChangeDetectors'));
/**
* Implements change detection using a map of pregenerated proto detectors.
*/
@ -93,14 +91,19 @@ export const PROTO_CHANGE_DETECTOR = CONST_EXPR(new OpaqueToken('ProtoChangeDete
export class PreGeneratedChangeDetection extends ChangeDetection {
_dynamicChangeDetection: ChangeDetection;
_protoChangeDetectorFactories: StringMap<string, Function>;
_genConfig: ChangeDetectorGenConfig;
constructor(@Inject(PROTO_CHANGE_DETECTOR) @Optional() protoChangeDetectorsForTest?:
StringMap<string, Function>) {
constructor(config?: ChangeDetectorGenConfig,
protoChangeDetectorsForTest?: StringMap<string, Function>) {
super();
this._dynamicChangeDetection = new DynamicChangeDetection();
this._protoChangeDetectorFactories = isPresent(protoChangeDetectorsForTest) ?
protoChangeDetectorsForTest :
preGeneratedProtoDetectors;
this._genConfig =
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
assertionsEnabled(), false);
}
static isSupported(): boolean { return PregenProtoChangeDetector.isSupported(); }
@ -112,11 +115,8 @@ export class PreGeneratedChangeDetection extends ChangeDetection {
return this._dynamicChangeDetection.getProtoChangeDetector(id, definition);
}
get genConfig(): ChangeDetectorGenConfig { return this._genConfig; }
get generateDetectors(): boolean { return true; }
get genConfig(): ChangeDetectorGenConfig {
return new ChangeDetectorGenConfig(assertionsEnabled(), assertionsEnabled());
}
}
@ -127,15 +127,21 @@ export class PreGeneratedChangeDetection extends ChangeDetection {
*/
@Injectable()
export class DynamicChangeDetection extends ChangeDetection {
_genConfig: ChangeDetectorGenConfig;
constructor(config?: ChangeDetectorGenConfig) {
super();
this._genConfig =
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
assertionsEnabled(), false);
}
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
return new DynamicProtoChangeDetector(definition);
}
get genConfig(): ChangeDetectorGenConfig { return this._genConfig; }
get generateDetectors(): boolean { return true; }
get genConfig(): ChangeDetectorGenConfig {
return new ChangeDetectorGenConfig(assertionsEnabled(), assertionsEnabled());
}
}
/**
@ -145,17 +151,21 @@ export class DynamicChangeDetection extends ChangeDetection {
* {@link DynamicChangeDetection} and {@link PreGeneratedChangeDetection}.
*/
@Injectable()
@CONST()
export class JitChangeDetection extends ChangeDetection {
_genConfig: ChangeDetectorGenConfig;
constructor(config?: ChangeDetectorGenConfig) {
super();
this._genConfig =
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
assertionsEnabled(), false);
}
static isSupported(): boolean { return JitProtoChangeDetector.isSupported(); }
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
return new JitProtoChangeDetector(definition);
}
get genConfig(): ChangeDetectorGenConfig { return this._genConfig; }
get generateDetectors(): boolean { return true; }
get genConfig(): ChangeDetectorGenConfig {
return new ChangeDetectorGenConfig(assertionsEnabled(), assertionsEnabled());
}
}

View File

@ -82,7 +82,6 @@ export class ChangeDetectorJITGenerator {
return new ${this._typeName}(dispatcher);
}
`;
return new Function(ABSTRACT_CHANGE_DETECTOR, UTIL, classDefinition)(AbstractChangeDetector,
ChangeDetectionUtil);
}
@ -301,6 +300,7 @@ export class ChangeDetectorJITGenerator {
var newValue = this._names.getLocalName(r.selfIndex);
var oldValue = this._names.getFieldName(r.selfIndex);
var notifyDebug = this.genConfig.logBindingUpdate ? `this.logBindingUpdate(${newValue});` : "";
var br = r.bindingRecord;
if (br.target.isDirective()) {
@ -309,12 +309,14 @@ export class ChangeDetectorJITGenerator {
return `
${this._genThrowOnChangeCheck(oldValue, newValue)}
${directiveProperty} = ${newValue};
${notifyDebug}
${IS_CHANGED_LOCAL} = true;
`;
} else {
return `
${this._genThrowOnChangeCheck(oldValue, newValue)}
this.notifyDispatcher(${newValue});
${notifyDebug}
`;
}
}

View File

@ -179,6 +179,10 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
var directiveIndex = bindingRecord.directiveRecord.directiveIndex;
bindingRecord.setter(this._getDirectiveFor(directiveIndex), change.currentValue);
}
if (this.genConfig.logBindingUpdate) {
super.logBindingUpdate(change.currentValue);
}
}
_addChange(bindingRecord: BindingRecord, change, changes) {

View File

@ -27,7 +27,7 @@ import {ChangeDetectorRef} from './change_detector_ref';
*
* # Example
* ```javascript
* bootstrap(MyApp, [bind(ChangeDetection).toClass(DynamicChangeDetection)]);
* bootstrap(MyApp, [bind(ChangeDetection).toValue(new DynamicChangeDetection())]);
* ```
*/
@CONST()
@ -49,6 +49,7 @@ export class DebugContext {
export interface ChangeDispatcher {
getDebugContext(elementIndex: number, directiveIndex: DirectiveIndex): DebugContext;
notifyOnBinding(bindingTarget: BindingTarget, value: any): void;
logBindingUpdate(bindingTarget: BindingTarget, value: any): void;
notifyOnAllChangesDone(): void;
}
@ -74,7 +75,8 @@ export interface ChangeDetector {
export interface ProtoChangeDetector { instantiate(dispatcher: ChangeDispatcher): ChangeDetector; }
export class ChangeDetectorGenConfig {
constructor(public genCheckNoChanges: boolean, public genDebugInfo: boolean) {}
constructor(public genCheckNoChanges: boolean, public genDebugInfo: boolean,
public logBindingUpdate: boolean) {}
}
export class ChangeDetectorDefinition {

View File

@ -60,7 +60,6 @@ import {Renderer, RenderCompiler} from 'angular2/src/render/api';
import {
DomRenderer,
DOCUMENT,
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES,
DefaultDomCompiler,
APP_ID_RANDOM_BINDING,
MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE,
@ -84,16 +83,15 @@ var _rootInjector: Injector;
var _rootBindings = [bind(Reflector).toValue(reflector), TestabilityRegistry];
function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
var bestChangeDetection: Type = DynamicChangeDetection;
var bestChangeDetection = new DynamicChangeDetection();
if (PreGeneratedChangeDetection.isSupported()) {
bestChangeDetection = PreGeneratedChangeDetection;
bestChangeDetection = new PreGeneratedChangeDetection();
} else if (JitChangeDetection.isSupported()) {
bestChangeDetection = JitChangeDetection;
bestChangeDetection = new JitChangeDetection();
}
return [
bind(DOCUMENT)
.toValue(DOM.defaultDoc()),
bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false),
bind(APP_COMPONENT).toValue(appComponentType),
bind(APP_COMPONENT_REF_PROMISE)
.toFactory(
@ -141,7 +139,7 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
DEFAULT_PIPES,
bind(IterableDiffers).toValue(defaultIterableDiffers),
bind(KeyValueDiffers).toValue(defaultKeyValueDiffers),
bind(ChangeDetection).toClass(bestChangeDetection),
bind(ChangeDetection).toValue(bestChangeDetection),
ViewLoader,
DirectiveResolver,
PipeResolver,

View File

@ -32,9 +32,12 @@ import {RenderEventDispatcher} from 'angular2/src/render/api';
import {ViewRef, ProtoViewRef, internalView} from './view_ref';
import {ElementRef} from './element_ref';
import {ProtoPipes} from 'angular2/src/core/pipes/pipes';
import {camelCaseToDashCase} from 'angular2/src/render/dom/util';
export {DebugContext} from 'angular2/src/change_detection/interfaces';
const REFLECT_PREFIX: string = 'ng-reflect-';
export class AppProtoViewMergeMapping {
renderProtoViewRef: renderApi.RenderProtoViewRef;
renderFragmentCount: number;
@ -193,6 +196,14 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
}
}
logBindingUpdate(b: BindingTarget, value: any): void {
if (b.isDirective() || b.isElementProperty()) {
var elementRef = this.elementRefs[this.elementOffset + b.elementIndex];
this.renderer.setElementAttribute(
elementRef, `${REFLECT_PREFIX}${camelCaseToDashCase(b.name)}`, `${value}`);
}
}
notifyOnAllChangesDone(): void {
var eiCount = this.proto.elementBinders.length;
var ei = this.elementInjectors;

View File

@ -35,7 +35,7 @@ import {
import {TemplateCloner} from './template_cloner';
import {DOCUMENT, DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from './dom_tokens';
import {DOCUMENT} from './dom_tokens';
const REFLECT_PREFIX: string = 'ng-reflect-';
@ -43,15 +43,11 @@ const REFLECT_PREFIX: string = 'ng-reflect-';
@Injectable()
export class DomRenderer extends Renderer {
_document;
_reflectPropertiesAsAttributes: boolean;
constructor(private _eventManager: EventManager,
private _domSharedStylesHost: DomSharedStylesHost,
private _templateCloner: TemplateCloner, @Inject(DOCUMENT) document,
@Inject(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES) reflectPropertiesAsAttributes:
boolean) {
private _templateCloner: TemplateCloner, @Inject(DOCUMENT) document) {
super();
this._reflectPropertiesAsAttributes = reflectPropertiesAsAttributes;
this._document = document;
}
@ -165,11 +161,6 @@ export class DomRenderer extends Renderer {
}
var view = resolveInternalDomView(location.renderView);
view.setElementProperty(location.renderBoundElementIndex, propertyName, propertyValue);
// Reflect the property value as an attribute value with ng-reflect- prefix.
if (this._reflectPropertiesAsAttributes) {
this.setElementAttribute(location, `${REFLECT_PREFIX}${camelCaseToDashCase(propertyName)}`,
`${propertyValue}`);
}
}
setElementAttribute(location: RenderElementRef, attributeName: string, attributeValue: string):

View File

@ -3,10 +3,6 @@ import {CONST_EXPR, StringWrapper, Math} from 'angular2/src/facade/lang';
export const DOCUMENT: OpaqueToken = CONST_EXPR(new OpaqueToken('DocumentToken'));
export const DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES: OpaqueToken =
CONST_EXPR(new OpaqueToken('DomReflectPropertiesAsAttributes'));
/**
* A unique id (string) for an angular application.
*/

View File

@ -54,7 +54,6 @@ import {RenderCompiler, Renderer} from 'angular2/src/render/api';
import {
DomRenderer,
DOCUMENT,
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES,
DefaultDomCompiler,
APP_ID,
SharedStylesHost,
@ -111,7 +110,6 @@ function _getAppBindings() {
bind(ElementSchemaRegistry).toValue(new DomElementSchemaRegistry()),
DomSharedStylesHost,
bind(SharedStylesHost).toAlias(DomSharedStylesHost),
bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false),
ProtoViewFactory,
AppViewPool,
AppViewManager,
@ -125,7 +123,7 @@ function _getAppBindings() {
DEFAULT_PIPES,
bind(IterableDiffers).toValue(defaultIterableDiffers),
bind(KeyValueDiffers).toValue(defaultKeyValueDiffers),
bind(ChangeDetection).toClass(DynamicChangeDetection),
bind(ChangeDetection).toValue(new DynamicChangeDetection()),
Log,
ViewLoader,
DynamicComponentLoader,

View File

@ -10,6 +10,7 @@ const CUSTOM_ANNOTATIONS_PARAM = 'custom_annotations';
const ENTRY_POINT_PARAM = 'entry_points';
const FORMAT_CODE_PARAM = 'format_code';
const GENERATE_CHANGE_DETECTORS_PARAM = 'generate_change_detectors';
const REFLECT_PROPERTIES_AS_ATTRIBUTES = 'reflectPropertiesAsAttributes';
const INIT_REFLECTOR_PARAM = 'init_reflector';
const INLINE_VIEWS_PARAM = 'inline_views';
const MIRROR_MODE_PARAM = 'mirror_mode';
@ -43,6 +44,8 @@ class TransformerOptions {
/// Whether to create change detector classes for discovered `@View`s.
final bool generateChangeDetectors;
final bool reflectPropertiesAsAttributes;
/// The number of phases to spend optimizing output size.
/// Each additional phase adds time to the transformation but may decrease
/// final output size. There is a limit beyond which this will no longer
@ -66,6 +69,7 @@ class TransformerOptions {
this.annotationMatcher,
this.optimizationPhases,
{this.generateChangeDetectors,
this.reflectPropertiesAsAttributes,
this.inlineViews,
this.formatCode});
@ -78,6 +82,7 @@ class TransformerOptions {
int optimizationPhases: DEFAULT_OPTIMIZATION_PHASES,
bool inlineViews: true,
bool generateChangeDetectors: true,
bool reflectPropertiesAsAttributes: true,
bool formatCode: false}) {
if (reflectionEntryPoints == null || reflectionEntryPoints.isEmpty) {
reflectionEntryPoints = entryPoints;
@ -94,6 +99,7 @@ class TransformerOptions {
annotationMatcher,
optimizationPhases,
generateChangeDetectors: generateChangeDetectors,
reflectPropertiesAsAttributes: reflectPropertiesAsAttributes,
inlineViews: inlineViews,
formatCode: formatCode);
}

View File

@ -15,6 +15,8 @@ TransformerOptions parseBarbackSettings(BarbackSettings settings) {
var inlineViews = _readBool(config, INLINE_VIEWS_PARAM, defaultValue: true);
var generateChangeDetectors =
_readBool(config, GENERATE_CHANGE_DETECTORS_PARAM, defaultValue: true);
var reflectPropertiesAsAttributes =
_readBool(config, REFLECT_PROPERTIES_AS_ATTRIBUTES, defaultValue: false);
var formatCode = _readBool(config, FORMAT_CODE_PARAM, defaultValue: false);
String mirrorModeVal =
config.containsKey(MIRROR_MODE_PARAM) ? config[MIRROR_MODE_PARAM] : '';
@ -41,6 +43,7 @@ TransformerOptions parseBarbackSettings(BarbackSettings settings) {
customAnnotationDescriptors: _readCustomAnnotations(config),
optimizationPhases: optimizationPhases,
generateChangeDetectors: generateChangeDetectors,
reflectPropertiesAsAttributes: reflectPropertiesAsAttributes,
formatCode: formatCode);
}

View File

@ -399,6 +399,7 @@ class _CodegenState {
var newValue = _names.getLocalName(r.selfIndex);
var oldValue = _names.getFieldName(r.selfIndex);
var notifyDebug = _genConfig.logBindingUpdate ? "this.logBindingUpdate(${newValue});" : "";
var br = r.bindingRecord;
if (br.target.isDirective()) {
@ -407,12 +408,14 @@ class _CodegenState {
return '''
${_genThrowOnChangeCheck(oldValue, newValue)}
$directiveProperty = $newValue;
${notifyDebug}
$_IS_CHANGED_LOCAL = true;
''';
} else {
return '''
${_genThrowOnChangeCheck(oldValue, newValue)}
this.notifyDispatcher(${newValue});
${notifyDebug}
''';
}
}

View File

@ -37,7 +37,7 @@ import 'view_definition_creator.dart';
/// This method assumes a {@link DomAdapter} has been registered.
Future<String> processTemplates(AssetReader reader, AssetId entryPoint,
{bool generateRegistrations: true,
bool generateChangeDetectors: true}) async {
bool generateChangeDetectors: true, bool reflectPropertiesAsAttributes: false}) async {
var viewDefResults = await createViewDefinitions(reader, entryPoint);
// Note: TemplateCloner(-1) never serializes Nodes into strings.
// we might want to change this to TemplateCloner(0) to force the serialization
@ -58,7 +58,8 @@ Future<String> processTemplates(AssetReader reader, AssetId entryPoint,
}
if (generateChangeDetectors) {
var saved = reflector.reflectionCapabilities;
var genConfig = new ChangeDetectorGenConfig(assertionsEnabled(), assertionsEnabled());
var genConfig = new ChangeDetectorGenConfig(assertionsEnabled(), assertionsEnabled(), reflectPropertiesAsAttributes);
reflector.reflectionCapabilities = const NullReflectionCapabilities();
var defs = getChangeDetectorDefinitions(viewDefEntry.hostMetadata,
protoView, viewDefEntry.viewDef.directives, genConfig);

View File

@ -33,7 +33,8 @@ class TemplateCompiler extends Transformer {
var id = transform.primaryInput.id;
var reader = new AssetReader.fromTransform(transform);
var transformedCode = formatter.format(await processTemplates(reader, id,
generateChangeDetectors: options.generateChangeDetectors));
generateChangeDetectors: options.generateChangeDetectors,
reflectPropertiesAsAttributes: options.reflectPropertiesAsAttributes));
transform.addOutput(new Asset.fromString(id, transformedCode));
});
}

View File

@ -1,7 +1,6 @@
// TODO (jteplitz602): This whole file is nearly identical to core/application.ts.
// There should be a way to refactor application so that this file is unnecessary. See #3277
import {Injector, bind, Binding} from "angular2/di";
import {Type, isBlank, isPresent} from "angular2/src/facade/lang";
import {Reflector, reflector} from 'angular2/src/reflection/reflection';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {
@ -24,7 +23,6 @@ import {AppRootUrl} from 'angular2/src/services/app_root_url';
import {
DomRenderer,
DOCUMENT,
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES,
DefaultDomCompiler,
APP_ID_RANDOM_BINDING,
MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE,
@ -75,12 +73,12 @@ var _rootBindings = [bind(Reflector).toValue(reflector)];
// TODO: This code is nearly identitcal to core/application. There should be a way to only write it
// once
function _injectorBindings(): List<Type | Binding | List<any>> {
var bestChangeDetection: Type = DynamicChangeDetection;
function _injectorBindings(): List<any> {
var bestChangeDetection = new DynamicChangeDetection();
if (PreGeneratedChangeDetection.isSupported()) {
bestChangeDetection = PreGeneratedChangeDetection;
bestChangeDetection = new PreGeneratedChangeDetection();
} else if (JitChangeDetection.isSupported()) {
bestChangeDetection = JitChangeDetection;
bestChangeDetection = new JitChangeDetection();
}
return [
@ -94,7 +92,6 @@ function _injectorBindings(): List<Type | Binding | List<any>> {
return new EventManager(plugins, ngZone);
},
[NgZone]),
bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false),
DomRenderer,
bind(Renderer).toAlias(DomRenderer),
APP_ID_RANDOM_BINDING,
@ -119,7 +116,7 @@ function _injectorBindings(): List<Type | Binding | List<any>> {
CompilerCache,
ViewResolver,
DEFAULT_PIPES,
bind(ChangeDetection).toClass(bestChangeDetection),
bind(ChangeDetection).toValue(bestChangeDetection),
ViewLoader,
DirectiveResolver,
Parser,

View File

@ -79,11 +79,11 @@ class PrintLogger {
function _injectorBindings(appComponentType, bus: MessageBusInterface,
initData: StringMap<string, any>): List<Type | Binding | List<any>> {
var bestChangeDetection: Type = DynamicChangeDetection;
var bestChangeDetection = new DynamicChangeDetection();
if (PreGeneratedChangeDetection.isSupported()) {
bestChangeDetection = PreGeneratedChangeDetection;
bestChangeDetection = new PreGeneratedChangeDetection();
} else if (JitChangeDetection.isSupported()) {
bestChangeDetection = JitChangeDetection;
bestChangeDetection = new JitChangeDetection();
}
return [
bind(APP_COMPONENT)
@ -123,7 +123,7 @@ function _injectorBindings(appComponentType, bus: MessageBusInterface,
DEFAULT_PIPES,
bind(IterableDiffers).toValue(defaultIterableDiffers),
bind(KeyValueDiffers).toValue(defaultKeyValueDiffers),
bind(ChangeDetection).toClass(bestChangeDetection),
bind(ChangeDetection).toValue(bestChangeDetection),
DirectiveResolver,
PipeResolver,
Parser,

View File

@ -28,13 +28,13 @@ export function main() {
it("should return a proto change detector when one is available", () => {
var map = {'id': (def) => proto};
var cd = new PreGeneratedChangeDetection(map);
var cd = new PreGeneratedChangeDetection(null, map);
expect(cd.getProtoChangeDetector('id', def)).toBe(proto)
});
it("should delegate to dynamic change detection otherwise", () => {
var cd = new PreGeneratedChangeDetection({});
var cd = new PreGeneratedChangeDetection(null, {});
expect(cd.getProtoChangeDetector('id', def)).toBeAnInstanceOf(DynamicProtoChangeDetector);
});
});

View File

@ -66,7 +66,7 @@ export var PROP_NAME = 'propName';
* In this case, we expect `id` and `expression` to be the same string.
*/
export function getDefinition(id: string): TestDefinition {
var genConfig = new ChangeDetectorGenConfig(true, true);
var genConfig = new ChangeDetectorGenConfig(true, true, true);
var testDef = null;
if (StringMapWrapper.contains(_ExpressionWithLocals.availableDefinitions, id)) {
let val = StringMapWrapper.get(_ExpressionWithLocals.availableDefinitions, id);
@ -110,6 +110,12 @@ export function getDefinition(id: string): TestDefinition {
var records = _createBindingRecords("a");
let cdDef = new ChangeDetectorDefinition(id, "ON_PUSH_OBSERVE", [], records, [], [], genConfig);
testDef = new TestDefinition(id, cdDef, null);
} else if (id == "updateElementProduction") {
var genConfig = new ChangeDetectorGenConfig(false, false, false);
var records = _createBindingRecords("name");
let cdDef = new ChangeDetectorDefinition(id, null, [], records, [], [], genConfig);
testDef = new TestDefinition(id, cdDef, null);
}
@ -138,7 +144,7 @@ export function getAllDefinitions(): List<TestDefinition> {
ListWrapper.concat(allDefs, StringMapWrapper.keys(_DirectiveUpdating.availableDefinitions));
allDefs = ListWrapper.concat(allDefs, _availableEventDefinitions);
allDefs = ListWrapper.concat(allDefs, _availableHostEventDefinitions);
allDefs = ListWrapper.concat(allDefs, ["onPushObserve"]);
allDefs = ListWrapper.concat(allDefs, ["onPushObserve", "updateElementProduction"]);
return ListWrapper.map(allDefs, (id) => getDefinition(id));
}
@ -150,7 +156,7 @@ class _ExpressionWithLocals {
var variableBindings = _convertLocalsToVariableBindings(this.locals);
var bindingRecords = _createBindingRecords(this._expression);
var directiveRecords = [];
var genConfig = new ChangeDetectorGenConfig(true, true);
var genConfig = new ChangeDetectorGenConfig(true, true, true);
return new ChangeDetectorDefinition('(empty id)', strategy, variableBindings, bindingRecords,
[], directiveRecords, genConfig);
}
@ -210,7 +216,7 @@ class _ExpressionWithMode {
_createHostEventRecords("(host-event)='false'", dirRecordWithOnPush))
}
var genConfig = new ChangeDetectorGenConfig(true, true);
var genConfig = new ChangeDetectorGenConfig(true, true, true);
return new ChangeDetectorDefinition('(empty id)', this._strategy, variableBindings,
bindingRecords, eventRecords, directiveRecords, genConfig);
@ -236,7 +242,7 @@ class _DirectiveUpdating {
createChangeDetectorDefinition(): ChangeDetectorDefinition {
var strategy = null;
var variableBindings = [];
var genConfig = new ChangeDetectorGenConfig(true, true);
var genConfig = new ChangeDetectorGenConfig(true, true, true);
return new ChangeDetectorDefinition('(empty id)', strategy, variableBindings,
this._bindingRecords, [], this._directiveRecords,

View File

@ -535,6 +535,31 @@ export function main() {
});
});
describe("logBindingUpdate", () => {
it('should be called for element updates in the dev mode', () => {
var person = new Person('bob');
var val = _createChangeDetector('name', person);
val.changeDetector.detectChanges();
expect(val.dispatcher.debugLog).toEqual(['propName=bob']);
});
it('should be called for directive updates in the dev mode', () => {
var val = _createWithoutHydrate('directNoDispatcher');
val.changeDetector.hydrate(_DEFAULT_CONTEXT, null,
new FakeDirectives([new TestDirective()], []), null);
val.changeDetector.detectChanges();
expect(val.dispatcher.debugLog).toEqual(["a=42"]);
});
it('should not be called in the prod mode', () => {
var person = new Person('bob');
var val = _createChangeDetector('updateElementProduction', person);
val.changeDetector.detectChanges();
expect(val.dispatcher.debugLog).toEqual([]);
});
});
describe('reading directives', () => {
it('should read directive properties', () => {
var directive = new TestDirective();
@ -1123,7 +1148,8 @@ class FakeDirectives {
}
class TestDispatcher implements ChangeDispatcher {
log: List<string>;
log: string[];
debugLog: string[];
loggedValues: List<any>;
onAllChangesDoneCalled: boolean = false;
@ -1131,6 +1157,7 @@ class TestDispatcher implements ChangeDispatcher {
clear() {
this.log = [];
this.debugLog = [];
this.loggedValues = [];
this.onAllChangesDoneCalled = true;
}
@ -1140,6 +1167,8 @@ class TestDispatcher implements ChangeDispatcher {
this.loggedValues.push(value);
}
logBindingUpdate(target, value) { this.debugLog.push(`${target.name}=${this._asString(value)}`); }
notifyOnAllChangesDone() { this.onAllChangesDoneCalled = true; }
getDebugContext(a, b) { return null; }

View File

@ -58,7 +58,10 @@ import {
import {
PipeTransform,
ChangeDetectorRef,
ON_PUSH
ON_PUSH,
ChangeDetection,
DynamicChangeDetection,
ChangeDetectorGenConfig
} from 'angular2/src/change_detection/change_detection';
import {Directive, Component, View, ViewMetadata, Attribute, Query, Pipe} from 'angular2/metadata';
@ -1518,6 +1521,30 @@ export function main() {
}));
});
describe('logging property updates', () => {
beforeEachBindings(() => [
bind(ChangeDetection)
.toValue(new DynamicChangeDetection(new ChangeDetectorGenConfig(true, true, true)))
]);
it('should reflect property values as attributes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var tpl = '<div>' +
'<div my-dir [elprop]="ctxProp"></div>' +
'</div>';
tcb.overrideView(MyComp, new ViewMetadata({template: tpl, directives: [MyDir]}))
.createAsync(MyComp)
.then((rootTC) => {
rootTC.componentInstance.ctxProp = 'hello';
rootTC.detectChanges();
expect(DOM.getInnerHTML(rootTC.nativeElement))
.toContain('ng-reflect-dir-prop="hello"');
async.done();
});
}));
});
describe('different proto view storages', () => {
function runWithMode(mode: string) {

View File

@ -24,8 +24,6 @@ import {
RenderViewRef,
ViewEncapsulation
} from 'angular2/src/render/api';
import {DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from 'angular2/src/render/dom/dom_tokens';
import {bind} from 'angular2/di';
export function main() {
describe('DomRenderer integration', () => {
@ -106,72 +104,6 @@ export function main() {
});
}));
it('should NOT reflect property values as attributes if flag is NOT set',
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
tb.compileAndMerge(someComponent,
[
new ViewDefinition({
componentId: 'someComponent',
template: '<input [title]="y">',
directives: []
})
])
.then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings);
var el = DOM.childNodes(rootView.hostElement)[0];
tb.renderer.setElementProperty(elRef(rootView.viewRef, 1), 'maxLength', '20');
expect(DOM.getAttribute(<HTMLInputElement>el, 'ng-reflect-max-length'))
.toEqual(null);
async.done();
});
}));
describe('reflection', () => {
beforeEachBindings(() => [bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(true)]);
it('should reflect property values as attributes if flag is set',
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
tb.compileAndMerge(someComponent,
[
new ViewDefinition({
componentId: 'someComponent',
template: '<input [title]="y">',
directives: []
})
])
.then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings);
var el = DOM.childNodes(rootView.hostElement)[0];
tb.renderer.setElementProperty(elRef(rootView.viewRef, 1), 'maxLength', '20');
expect(DOM.getAttribute(<HTMLInputElement>el, 'ng-reflect-max-length'))
.toEqual('20');
async.done();
});
}));
it('should reflect non-string property values as attributes if flag is set',
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
tb.compileAndMerge(someComponent,
[
new ViewDefinition({
componentId: 'someComponent',
template: '<input [title]="y">',
directives: []
})
])
.then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings);
var el = DOM.childNodes(rootView.hostElement)[0];
tb.renderer.setElementProperty(elRef(rootView.viewRef, 1), 'maxLength', 20);
expect(DOM.getAttribute(<HTMLInputElement>el, 'ng-reflect-max-length'))
.toEqual('20');
async.done();
});
}));
});
if (DOM.supportsDOMEvents()) {
it('should call actions on the element independent of the compilation',
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {

View File

@ -58,6 +58,7 @@ class _MyComponent_ChangeDetector0
}
this.notifyDispatcher(l_interpolate1);
this.logBindingUpdate(l_interpolate1);
this.interpolate1 = l_interpolate1;
}

View File

@ -12,6 +12,7 @@ import {
DynamicChangeDetection,
JitChangeDetection,
ChangeDetectorDefinition,
ChangeDetectorGenConfig,
BindingRecord,
DirectiveRecord,
DirectiveIndex,
@ -249,8 +250,9 @@ function setUpChangeDetection(changeDetection: ChangeDetection, iterations, obje
var dispatcher = new DummyDispatcher();
var parser = new Parser(new Lexer());
var genConfig = new ChangeDetectorGenConfig(false, false, false);
var parentProto = changeDetection.getProtoChangeDetector(
"id", new ChangeDetectorDefinition('parent', null, [], [], [], [], false));
"id", new ChangeDetectorDefinition('parent', null, [], [], [], [], genConfig));
var parentCd = parentProto.instantiate(dispatcher);
var directiveRecord = new DirectiveRecord({directiveIndex: new DirectiveIndex(0, 0)});
@ -279,7 +281,7 @@ function setUpChangeDetection(changeDetection: ChangeDetection, iterations, obje
var proto = changeDetection.getProtoChangeDetector(
"id",
new ChangeDetectorDefinition("proto", null, [], bindings, [], [directiveRecord], false));
new ChangeDetectorDefinition("proto", null, [], bindings, [], [directiveRecord], genConfig));
var targetObj = new Obj();
parentCd.hydrate(object, null, new FakeDirectives(targetObj), null);
@ -385,6 +387,7 @@ class DummyDispatcher implements ChangeDispatcher {
getDebugContext(elementIndex: number, directiveIndex: DirectiveIndex): DebugContext {
throw "getDebugContext not implemented.";
}
notifyOnBinding(bindingRecord, newValue) { throw "Should not be used"; }
notifyOnBinding(bindingTarget, newValue) { throw "Should not be used"; }
logBindingUpdate(bindingTarget, newValue) { throw "Should not be used"; }
notifyOnAllChangesDone() {}
}