diff --git a/packages/ember-htmlbars/tests/helpers/closure_component_test.js b/packages/ember-htmlbars/tests/helpers/closure_component_test.js
index d6d5da4f0d9..04807e63259 100644
--- a/packages/ember-htmlbars/tests/helpers/closure_component_test.js
+++ b/packages/ember-htmlbars/tests/helpers/closure_component_test.js
@@ -1,6 +1,7 @@
import { runAppend, runDestroy } from 'ember-runtime/tests/utils';
import ComponentLookup from 'ember-views/component_lookup';
import Component from 'ember-views/components/component';
+import EventDispatcher from 'ember-views/system/event_dispatcher';
import compile from 'ember-template-compiler/system/compile';
import run from 'ember-metal/run_loop';
import isEmpty from 'ember-metal/is_empty';
@@ -614,3 +615,156 @@ QUnit.test('adding parameters to a closure component\'s instance does not add it
runAppend(component);
equal(component.$().text(), 'Foo', 'there is only one Foo');
});
+
+QUnit.test('parameters in a closure are mutable when closure is a param', function(assert) {
+ let dispatcher = EventDispatcher.create();
+ dispatcher.setup();
+
+ let ChangeButton = Component.extend().reopenClass({
+ positionalParams: ['val']
+ });
+
+ owner.register(
+ 'component:change-button',
+ ChangeButton
+ );
+ owner.register(
+ 'template:components/change-button',
+ compile('')
+ );
+
+ let template = compile('{{component (component "change-button" val2)}}{{val2}}');
+
+ component = Component.extend({
+ [OWNER]: owner,
+ template
+ }).create({
+ val2: 8
+ });
+
+ runAppend(component);
+
+ assert.equal(component.$('.value').text(), '8', 'initial state is right');
+
+ run(() => component.$('.my-button').click());
+
+ assert.equal(component.$('.value').text(), '10', 'Value gets updated');
+
+ runDestroy(dispatcher);
+});
+
+QUnit.test('parameters in a closure are mutable when closure is in a nested param', function(assert) {
+ let dispatcher = EventDispatcher.create();
+ dispatcher.setup();
+
+ let ChangeButton = Component.extend().reopenClass({
+ positionalParams: ['val']
+ });
+
+ owner.register(
+ 'component:change-button',
+ ChangeButton
+ );
+ owner.register(
+ 'template:components/change-button',
+ compile('')
+ );
+
+ owner.register(
+ 'component:my-comp',
+ Component.extend().reopenClass({
+ positionalParams: ['components']
+ })
+ );
+ owner.register(
+ 'template:components/my-comp',
+ compile('{{component components.comp}}')
+ );
+
+ let template = compile('{{my-comp (hash comp=(component "change-button" val2))}}{{val2}}');
+
+ component = Component.extend({
+ [OWNER]: owner,
+ template
+ }).create({
+ val2: 8
+ });
+
+ runAppend(component);
+
+ assert.equal(component.$('.value').text(), '8', 'initial state is right');
+
+ run(() => component.$('.my-button').click());
+
+ assert.equal(component.$('.value').text(), '10', 'Value gets updated');
+
+ runDestroy(dispatcher);
+});
+
+QUnit.test('parameters in a closure are mutable when closure is a hash value', function(assert) {
+ let dispatcher = EventDispatcher.create();
+ dispatcher.setup();
+
+ owner.register(
+ 'template:components/change-button',
+ compile('')
+ );
+
+ owner.register(
+ 'template:components/my-comp',
+ compile('{{component component}}')
+ );
+
+ let template = compile('{{my-comp component=(component "change-button" val=val2)}}{{val2}}');
+
+ component = Component.extend({
+ [OWNER]: owner,
+ template
+ }).create({
+ val2: 8
+ });
+
+ runAppend(component);
+
+ assert.equal(component.$('.value').text(), '8', 'initial state is right');
+
+ run(() => component.$('.my-button').click());
+
+ assert.equal(component.$('.value').text(), '10', 'Value gets updated');
+
+ runDestroy(dispatcher);
+});
+
+QUnit.test('parameters in a closure are mutable when closure is a nested hash value', function(assert) {
+ let dispatcher = EventDispatcher.create();
+ dispatcher.setup();
+
+ owner.register(
+ 'template:components/change-button',
+ compile('')
+ );
+
+ owner.register(
+ 'template:components/my-comp',
+ compile('{{component components.button}}')
+ );
+
+ let template = compile('{{my-comp components=(hash button=(component "change-button" val=val2))}}{{val2}}');
+
+ component = Component.extend({
+ [OWNER]: owner,
+ template
+ }).create({
+ val2: 8
+ });
+
+ runAppend(component);
+
+ assert.equal(component.$('.value').text(), '8', 'initial state is right');
+
+ run(() => component.$('.my-button').click());
+
+ assert.equal(component.$('.value').text(), '10', 'Value gets updated');
+
+ runDestroy(dispatcher);
+});
diff --git a/packages/ember-template-compiler/lib/index.js b/packages/ember-template-compiler/lib/index.js
index 10eb52a36d2..8aeeced6376 100644
--- a/packages/ember-template-compiler/lib/index.js
+++ b/packages/ember-template-compiler/lib/index.js
@@ -7,6 +7,7 @@ import { registerPlugin } from 'ember-template-compiler/plugins';
import TransformOldBindingSyntax from 'ember-template-compiler/plugins/transform-old-binding-syntax';
import TransformOldClassBindingSyntax from 'ember-template-compiler/plugins/transform-old-class-binding-syntax';
import TransformItemClass from 'ember-template-compiler/plugins/transform-item-class';
+import TransformClosureComponentAttrsIntoMut from 'ember-template-compiler/plugins/transform-closure-component-attrs-into-mut';
import TransformComponentAttrsIntoMut from 'ember-template-compiler/plugins/transform-component-attrs-into-mut';
import TransformComponentCurlyToReadonly from 'ember-template-compiler/plugins/transform-component-curly-to-readonly';
import TransformAngleBracketComponents from 'ember-template-compiler/plugins/transform-angle-bracket-components';
@@ -23,6 +24,7 @@ import 'ember-template-compiler/compat';
registerPlugin('ast', TransformOldBindingSyntax);
registerPlugin('ast', TransformOldClassBindingSyntax);
registerPlugin('ast', TransformItemClass);
+registerPlugin('ast', TransformClosureComponentAttrsIntoMut);
registerPlugin('ast', TransformComponentAttrsIntoMut);
registerPlugin('ast', TransformComponentCurlyToReadonly);
registerPlugin('ast', TransformAngleBracketComponents);
diff --git a/packages/ember-template-compiler/lib/plugins/transform-closure-component-attrs-into-mut.js b/packages/ember-template-compiler/lib/plugins/transform-closure-component-attrs-into-mut.js
new file mode 100644
index 00000000000..79381e66b5b
--- /dev/null
+++ b/packages/ember-template-compiler/lib/plugins/transform-closure-component-attrs-into-mut.js
@@ -0,0 +1,78 @@
+function TransformClosureComponentAttrsIntoMut() {
+ // set later within HTMLBars to the syntax package
+ this.syntax = null;
+}
+
+/**
+ @private
+ @method transform
+ @param {AST} ast The AST to be transformed.
+*/
+TransformClosureComponentAttrsIntoMut.prototype.transform = function TransformClosureComponentAttrsIntoMut_transform(ast) {
+ let b = this.syntax.builders;
+ let walker = new this.syntax.Walker();
+
+ walker.visit(ast, function(node) {
+ if (validate(node)) {
+ processExpression(b, node);
+ }
+ });
+
+ return ast;
+};
+
+function processExpression(builder, node) {
+ processSubExpressionsInNode(builder, node);
+
+ if (isComponentClosure(node)) {
+ mutParameters(builder, node);
+ }
+}
+
+function processSubExpressionsInNode(builder, node) {
+ for (let i = 0; i < node.params.length; i++) {
+ if (node.params[i].type === 'SubExpression') {
+ processExpression(builder, node.params[i]);
+ }
+ }
+
+ each(node.hash.pairs, function(pair) {
+ let { value } = pair;
+
+ if (value.type === 'SubExpression') {
+ processExpression(builder, value);
+ }
+ });
+}
+
+function isComponentClosure(node) {
+ return node.type === 'SubExpression' && node.path.original === 'component';
+}
+
+function mutParameters(builder, node) {
+ for (let i = 1; i < node.params.length; i++) {
+ if (node.params[i].type === 'PathExpression') {
+ node.params[i] = builder.sexpr(builder.path('@mut'), [node.params[i]]);
+ }
+ }
+
+ each(node.hash.pairs, function(pair) {
+ let { value } = pair;
+
+ if (value.type === 'PathExpression') {
+ pair.value = builder.sexpr(builder.path('@mut'), [pair.value]);
+ }
+ });
+}
+
+function validate(node) {
+ return node.type === 'BlockStatement' || node.type === 'MustacheStatement';
+}
+
+function each(list, callback) {
+ for (var i = 0, l = list.length; i < l; i++) {
+ callback(list[i]);
+ }
+}
+
+export default TransformClosureComponentAttrsIntoMut;