diff --git a/packages/@ember/-internals/glimmer/lib/component-managers/custom.ts b/packages/@ember/-internals/glimmer/lib/component-managers/custom.ts index bbdeb2e96f3..1f8c469e9ab 100644 --- a/packages/@ember/-internals/glimmer/lib/component-managers/custom.ts +++ b/packages/@ember/-internals/glimmer/lib/component-managers/custom.ts @@ -1,6 +1,10 @@ +import { getCurrentTracker } from '@ember/-internals/metal'; import { Factory } from '@ember/-internals/owner'; +import { HAS_NATIVE_PROXY } from '@ember/-internals/utils'; import { OwnedTemplateMeta } from '@ember/-internals/views'; +import { EMBER_CUSTOM_COMPONENT_ARG_PROXY } from '@ember/canary-features'; import { assert } from '@ember/debug'; +import { DEBUG } from '@glimmer/env'; import { ComponentCapabilities, Dict, @@ -150,16 +154,87 @@ export default class CustomComponentManager const { delegate } = definition; const capturedArgs = args.capture(); - const component = delegate.createComponent( - definition.ComponentClass.class, - capturedArgs.value() - ); + let value; + let namedArgsProxy; + + if (EMBER_CUSTOM_COMPONENT_ARG_PROXY) { + if (HAS_NATIVE_PROXY) { + let handler: ProxyHandler<{}> = { + get(_target, prop) { + assert('args can only be strings', typeof prop === 'string'); + + let tracker = getCurrentTracker(); + let ref = capturedArgs.named.get(prop as string); + + if (tracker) { + tracker.add(ref.tag); + } + + return ref.value(); + }, + }; + + if (DEBUG) { + handler.set = function(target, prop) { + assert( + `You attempted to set ${target}#${String( + prop + )} on a components arguments. Component arguments are immutable and cannot be updated directly, they always represent the values that are passed to your component. If you want to set default values, you should use a getter instead` + ); + + return false; + }; + } + + namedArgsProxy = new Proxy({}, handler); + } else { + namedArgsProxy = {}; + let i; + + for (i = 0; i < capturedArgs.named.names.length; i++) { + let name = capturedArgs.named.names[i]; + let ref = capturedArgs.named.references[i]; + + Object.defineProperty(namedArgsProxy, name, { + get() { + let tracker = getCurrentTracker(); + + if (tracker) { + tracker.add(ref.tag); + } + + return ref.value(); + }, + }); + } + } + + value = { + named: namedArgsProxy, + positional: capturedArgs.positional.value(), + }; + } else { + value = capturedArgs.value(); + } - return new CustomComponentState(delegate, component, capturedArgs); + const component = delegate.createComponent(definition.ComponentClass.class, value); + + return new CustomComponentState(delegate, component, capturedArgs, namedArgsProxy); } - update({ delegate, component, args }: CustomComponentState) { - delegate.updateComponent(component, args.value()); + update({ delegate, component, args, namedArgsProxy }: CustomComponentState) { + let value; + + if (EMBER_CUSTOM_COMPONENT_ARG_PROXY) { + value = { + named: namedArgsProxy!, + positional: args.positional.value(), + }; + } else { + value = args.value(); + } + + delegate.updateComponent(component, value); } didCreate({ delegate, component }: CustomComponentState) { @@ -221,7 +296,8 @@ export class CustomComponentState { constructor( public delegate: ManagerDelegate, public component: ComponentInstance, - public args: CapturedArguments + public args: CapturedArguments, + public namedArgsProxy?: {} ) {} destroy() { diff --git a/packages/@ember/-internals/glimmer/tests/utils/glimmerish-component.js b/packages/@ember/-internals/glimmer/tests/utils/glimmerish-component.js index cddc9ca5608..ce3b8003524 100644 --- a/packages/@ember/-internals/glimmer/tests/utils/glimmerish-component.js +++ b/packages/@ember/-internals/glimmer/tests/utils/glimmerish-component.js @@ -1,5 +1,4 @@ import { setComponentManager, capabilities } from '@ember/-internals/glimmer'; -import { get, set } from '@ember/-internals/metal'; import { setOwner } from '@ember/-internals/owner'; class GlimmerishComponentManager { @@ -12,9 +11,7 @@ class GlimmerishComponentManager { return new Factory(this.owner, args.named); } - updateComponent(component, args) { - set(component, 'args', args.named); - } + updateComponent() {} getContext(component) { return component; @@ -26,14 +23,6 @@ class GlimmerishComponent { setOwner(this, owner); this.args = args; } - - get args() { - return get(this, '__args__'); - } - - set args(args) { - set(this, '__args__', args); - } } setComponentManager(() => new GlimmerishComponentManager(), GlimmerishComponent); diff --git a/packages/@ember/canary-features/index.ts b/packages/@ember/canary-features/index.ts index 503c779c916..02bb0857c07 100644 --- a/packages/@ember/canary-features/index.ts +++ b/packages/@ember/canary-features/index.ts @@ -21,6 +21,7 @@ export const DEFAULT_FEATURES = { EMBER_GLIMMER_ANGLE_BRACKET_NESTED_LOOKUP: true, EMBER_ROUTING_BUILD_ROUTEINFO_METADATA: true, EMBER_NATIVE_DECORATOR_SUPPORT: true, + EMBER_CUSTOM_COMPONENT_ARG_PROXY: null, }; /** @@ -84,3 +85,6 @@ export const EMBER_ROUTING_BUILD_ROUTEINFO_METADATA = featureValue( FEATURES.EMBER_ROUTING_BUILD_ROUTEINFO_METADATA ); export const EMBER_NATIVE_DECORATOR_SUPPORT = featureValue(FEATURES.EMBER_NATIVE_DECORATOR_SUPPORT); +export const EMBER_CUSTOM_COMPONENT_ARG_PROXY = featureValue( + FEATURES.EMBER_CUSTOM_COMPONENT_ARG_PROXY +);