-
Notifications
You must be signed in to change notification settings - Fork 27.4k
fix($parse): standardize one-time literal vs non-literal and interceptors #15858
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,13 +14,6 @@ function classDirective(name, selector) { | |
return { | ||
restrict: 'AC', | ||
link: function(scope, element, attr) { | ||
var expression = attr[name].trim(); | ||
var isOneTime = (expression.charAt(0) === ':') && (expression.charAt(1) === ':'); | ||
|
||
var watchInterceptor = isOneTime ? toFlatValue : toClassString; | ||
var watchExpression = $parse(expression, watchInterceptor); | ||
var watchAction = isOneTime ? ngClassOneTimeWatchAction : ngClassWatchAction; | ||
|
||
var classCounts = element.data('$classCounts'); | ||
var oldModulo = true; | ||
var oldClassString; | ||
|
@@ -43,7 +36,7 @@ function classDirective(name, selector) { | |
scope.$watch(indexWatchExpression, ngClassIndexWatchAction); | ||
} | ||
|
||
scope.$watch(watchExpression, watchAction, isOneTime); | ||
scope.$watch($parse(attr[name], toClassString), ngClassWatchAction); | ||
|
||
function addClasses(classString) { | ||
classString = digestClassCounts(split(classString), 1); | ||
|
@@ -85,9 +78,9 @@ function classDirective(name, selector) { | |
} | ||
|
||
function ngClassIndexWatchAction(newModulo) { | ||
// This watch-action should run before the `ngClass[OneTime]WatchAction()`, thus it | ||
// This watch-action should run before the `ngClassWatchAction()`, thus it | ||
// adds/removes `oldClassString`. If the `ngClass` expression has changed as well, the | ||
// `ngClass[OneTime]WatchAction()` will update the classes. | ||
// `ngClassWatchAction()` will update the classes. | ||
if (newModulo === selector) { | ||
addClasses(oldClassString); | ||
} else { | ||
|
@@ -97,15 +90,13 @@ function classDirective(name, selector) { | |
oldModulo = newModulo; | ||
} | ||
|
||
function ngClassOneTimeWatchAction(newClassValue) { | ||
var newClassString = toClassString(newClassValue); | ||
|
||
if (newClassString !== oldClassString) { | ||
ngClassWatchAction(newClassString); | ||
function ngClassWatchAction(newClassString) { | ||
//When using a one-time binding the newClassString will return | ||
//the pre-interceptor value until the one-time is complete | ||
if (newClassString && typeof newClassString !== "string") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've updated to |
||
newClassString = toClassString(newClassString); | ||
} | ||
} | ||
|
||
function ngClassWatchAction(newClassString) { | ||
if (oldModulo === selector) { | ||
updateClasses(oldClassString, newClassString); | ||
} | ||
|
@@ -152,34 +143,6 @@ function classDirective(name, selector) { | |
|
||
return classString; | ||
} | ||
|
||
function toFlatValue(classValue) { | ||
var flatValue = classValue; | ||
|
||
if (isArray(classValue)) { | ||
flatValue = classValue.map(toFlatValue); | ||
} else if (isObject(classValue)) { | ||
var hasUndefined = false; | ||
|
||
flatValue = Object.keys(classValue).filter(function(key) { | ||
var value = classValue[key]; | ||
|
||
if (!hasUndefined && isUndefined(value)) { | ||
hasUndefined = true; | ||
} | ||
|
||
return value; | ||
}); | ||
|
||
if (hasUndefined) { | ||
// Prevent the `oneTimeLiteralWatchInterceptor` from unregistering | ||
// the watcher, by including at least one `undefined` value. | ||
flatValue.push(undefined); | ||
} | ||
} | ||
|
||
return flatValue; | ||
} | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1765,8 +1765,8 @@ function $ParseProvider() { | |
if (parsedExpression.constant) { | ||
parsedExpression.$$watchDelegate = constantWatchDelegate; | ||
} else if (oneTime) { | ||
parsedExpression.$$watchDelegate = parsedExpression.literal ? | ||
oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; | ||
parsedExpression.oneTime = true; | ||
parsedExpression.$$watchDelegate = oneTimeWatchDelegate; | ||
} else if (parsedExpression.inputs) { | ||
parsedExpression.$$watchDelegate = inputsWatchDelegate; | ||
} | ||
|
@@ -1852,6 +1852,7 @@ function $ParseProvider() { | |
} | ||
|
||
function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { | ||
var doneCriteria = parsedExpression.literal ? isAllDefined : isDefined; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could have a better name, e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea, done. |
||
var unwatch, lastValue; | ||
if (parsedExpression.inputs) { | ||
unwatch = inputsWatchDelegate(scope, oneTimeListener, objectEquality, parsedExpression, prettyPrintExpression); | ||
|
@@ -1868,41 +1869,22 @@ function $ParseProvider() { | |
if (isFunction(listener)) { | ||
listener(value, old, scope); | ||
} | ||
if (isDefined(value)) { | ||
if (doneCriteria(value)) { | ||
scope.$$postDigest(function() { | ||
if (isDefined(lastValue)) { | ||
if (doneCriteria(lastValue)) { | ||
unwatch(); | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
|
||
function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { | ||
var unwatch, lastValue; | ||
unwatch = scope.$watch(function oneTimeWatch(scope) { | ||
return parsedExpression(scope); | ||
}, function oneTimeListener(value, old, scope) { | ||
lastValue = value; | ||
if (isFunction(listener)) { | ||
listener(value, old, scope); | ||
} | ||
if (isAllDefined(value)) { | ||
scope.$$postDigest(function() { | ||
if (isAllDefined(lastValue)) unwatch(); | ||
}); | ||
} | ||
}, objectEquality); | ||
|
||
return unwatch; | ||
|
||
function isAllDefined(value) { | ||
var allDefined = true; | ||
forEach(value, function(val) { | ||
if (!isDefined(val)) allDefined = false; | ||
}); | ||
return allDefined; | ||
} | ||
function isAllDefined(value) { | ||
var allDefined = true; | ||
forEach(value, function(val) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Not directly related to this PR, but I think it makes sense to exit early once an undefined value is found. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think Since this is currently only used for object/array literal cases, we could split this into separate object/array helper methods (one using for-in one using array.every?)... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree it is not worth it. It is just for one-time literals and it will only matter if one has huge literals which don't settle quickly (a very unlikely usecase). |
||
if (!isDefined(val)) allDefined = false; | ||
}); | ||
return allDefined; | ||
} | ||
|
||
function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { | ||
|
@@ -1918,22 +1900,28 @@ function $ParseProvider() { | |
var watchDelegate = parsedExpression.$$watchDelegate; | ||
var useInputs = false; | ||
|
||
var regularWatch = | ||
watchDelegate !== oneTimeLiteralWatchDelegate && | ||
watchDelegate !== oneTimeWatchDelegate; | ||
var doneCriteria = parsedExpression.literal ? isAllDefined : isDefined; | ||
|
||
var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) { | ||
function regularInterceptedExpression(scope, locals, assign, inputs) { | ||
var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs); | ||
return interceptorFn(value, scope, locals); | ||
} : function oneTimeInterceptedExpression(scope, locals, assign, inputs) { | ||
var value = parsedExpression(scope, locals, assign, inputs); | ||
} | ||
|
||
function oneTimeInterceptedExpression(scope, locals, assign, inputs) { | ||
var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs); | ||
var result = interceptorFn(value, scope, locals); | ||
// we only return the interceptor's result if the | ||
// initial value is defined (for bind-once) | ||
return isDefined(value) ? result : value; | ||
}; | ||
return doneCriteria(value) ? result : value; | ||
} | ||
|
||
var fn = parsedExpression.oneTime ? oneTimeInterceptedExpression : regularInterceptedExpression; | ||
|
||
// Propogate the literal/oneTime attributes | ||
fn.literal = parsedExpression.literal; | ||
fn.oneTime = parsedExpression.oneTime; | ||
|
||
// Propagate $$watchDelegates other then inputsWatchDelegate | ||
// Propagate or create inputs / $$watchDelegates | ||
useInputs = !parsedExpression.inputs; | ||
if (watchDelegate && watchDelegate !== inputsWatchDelegate) { | ||
fn.$$watchDelegate = watchDelegate; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You realy don't like spaces after
//
, do you 😛There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bad (or just different!) habit I guess :)