diff --git a/lib/mixins/property-effects.js b/lib/mixins/property-effects.js index c92bea3fa6..dccf2a0051 100644 --- a/lib/mixins/property-effects.js +++ b/lib/mixins/property-effects.js @@ -19,12 +19,14 @@ import { camelToDashCase, dashToCamelCase } from '../utils/case-map.js'; import { PropertyAccessors } from './property-accessors.js'; /* for annotated effects */ import { TemplateStamp } from './template-stamp.js'; -import { sanitizeDOMValue } from '../utils/settings.js'; +import { sanitizeDOMValue, legacyUndefined } from '../utils/settings.js'; // Monotonically increasing unique ID used for de-duping effects triggered // from multiple properties in the same turn let dedupeId = 0; +const NOOP = {}; + /** * Property effect types; effects are stored on the prototype using these keys * @enum {string} @@ -432,6 +434,10 @@ function runComputedEffects(inst, changedProps, oldProps, hasPaths) { */ function runComputedEffect(inst, property, props, oldProps, info) { let result = runMethodEffect(inst, property, props, oldProps, info); + // Abort if method returns a no-op result + if (result === NOOP) { + return; + } let computedProp = info.methodInfo; if (inst.__dataHasAccessor && inst.__dataHasAccessor[computedProp]) { inst._setPendingProperty(computedProp, result, true); @@ -579,7 +585,10 @@ function runBindingEffect(inst, path, props, oldProps, info, hasPaths, nodeList) } else { let value = info.evaluator._evaluateBinding(inst, part, path, props, oldProps, hasPaths); // Propagate value to child - applyBindingValue(inst, node, binding, part, value); + // Abort if value is a no-op result + if (value !== NOOP) { + applyBindingValue(inst, node, binding, part, value); + } } } @@ -816,7 +825,7 @@ function runMethodEffect(inst, property, props, oldProps, info) { let fn = context[info.methodName]; if (fn) { let args = inst._marshalArgs(info.args, property, props); - return fn.apply(context, args); + return args === NOOP ? NOOP : fn.apply(context, args); } else if (!info.dynamicFn) { console.warn('method `' + info.methodName + '` not defined'); } @@ -2215,6 +2224,11 @@ export const PropertyEffects = dedupingMixin(superClass => { value = structured ? getArgValue(data, props, name) : data[name]; } } + // When the `legacyUndefined` flag is enabled, pass a no-op value + // so that the observer, computed property, or compound binding is aborted. + if (legacyUndefined && value === undefined) { + return NOOP; + } values[i] = value; } return values; diff --git a/lib/utils/settings.js b/lib/utils/settings.js index 6b878c4ed1..4d99402006 100644 --- a/lib/utils/settings.js +++ b/lib/utils/settings.js @@ -158,3 +158,22 @@ export let syncInitialRender = false; export const setSyncInitialRender = function(useSyncInitialRender) { syncInitialRender = useSyncInitialRender; }; + +/** + * Setting to retain the legacy Polymer 1 behavior for multi-property + * observers around undefined values. Observers and computed property methods + * are not called until no argument is undefined. + */ +export let legacyUndefined = false; + +/** + * Sets `legacyUndefined` globally for all elements to enable legacy + * multi-property behavior for undefined values. + * + * @param {boolean} useLegacyUndefined enable or disable legacy + * multi-property behavior for undefined. + * @return {void} + */ +export const setLegacyUndefined = function(useLegacyUndefined) { + legacyUndefined = useLegacyUndefined; +};