Reputation: 1071
In this plunk I have a controller with a scope variable var1
and a directive with a scope variable var2
. var2
should mirror the value of var1
and vice versa. I watch the variable in the directive to know when it's changed in the controller.
The problem is that this generates a loop when the watched variable is changed in the directive (see the plunk console).
To prevent this loop, I could create in the directive two scope variables var2Input
and var2Output
to read/write var1
in the controller. Only var2Input
would be watched and whatever I change in the directive I change in var2Output
(that is read in the controller). But I don't want to create two variables as they would always have the same value. Any ideas how to approach this?
Javascript:
app.directive('someDirective', function () {
var directive = {};
directive.restrict = 'EA';
directive.scope = {
var2: '='
};
directive.template = 'This is var2: {{var2}} <br/> ' +
'<button ng-click="add1()">Add 1</button> <br/> ' +
'{{log}}';
directive.link = {};
directive.link.pre = function (scope, element, attrs) {};
directive.link.post = function (scope, element, attrs) {
scope.log = '';
scope.$watch('var2', function (newValue, oldValue) {
if (typeof newValue === 'undefined')
return;
scope.var2 = newValue * 10;
scope.log = scope.log + '<watched ' + newValue + '>';
});
scope.add1 = function() {
scope.var2++;
};
};
return directive;
});
Upvotes: 1
Views: 133
Reputation: 10945
OK. Here is some code that should do what you are looking for. Look at the code then my explanation below:
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<title>AngularJS Example</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
var myApp = angular.module("myApp", []);
myApp.controller('appController', function($scope) {
$scope.var1 = 1;
$scope.add = function(amount) {
$scope.var1 += amount;
}
});
myApp.controller('watcherController', watcherController);
watcherController.$inject = ['$scope'];
function watcherController($scope) {
$scope.log = '';
$scope.$watch('var2', function (newValue, oldValue) {
if (typeof newValue === 'undefined') {
return;
}
$scope.log = $scope.log + '\nwatched ' + newValue;
});
$scope.add1 = function() {
$scope.var2++;
};
}
myApp.directive('watcher', watcherDirective );
function watcherDirective() {
return {
'restrict': 'EA',
'template': 'This is var2: {{var2}} <br/><hr/><button ng-click="add1()">Add 1</button><br/><hr/><h4>LOG:</h4><pre>{{log}}</pre>',
'controller': 'watcherController',
'scope': {
var2: '@'
},
'link': function ($scope, $element, $attrs, ctrl, transclude) {
$attrs.$observe('var2', function(newValue, one, two) {
$scope.var2 = parseInt(newValue,10)*10;
$scope.log += "\nChanged on the outside"+$scope.var2+' - '+(typeof $scope.var2);
});
}
};
}
</script>
</head>
<body ng-controller="appController">
<button ng-click="add(5)">Add from outer controller</button><br/>Var1:{{var1}}<br/><hr/>
<watcher var2="{{var1}}"></watcher>
</body>
</html>
The $scope.$watch
is called every time $scope.var2
is changed no matter who changes it so we have to be careful what we do inside of that watcher.
I now display the outer controller's value for $scope.var1
and I added a second button that changes the outer controller's $scope.var1
That value gets passed down into the directive every time it changes.
Instead of using 2-way binding 'var2': '='
I am using 1-way binding 'var2': '@'
This converts the value of $scope.var1
into a string and passes that into the directive.
Then, in the directive I added $attrs.$observe
to let me know when the outer value changes. I have to convert that value back into a number by using parseInt()
. The $attrs.$observer
function is only called when the outside world changes the value for the directive's attribute var2
.
If you need 2-way binding then there are different things you would need to do. So if the directive needs to be able to modify the value of the outer controller's $scope.var1
then I will need to demo something else.
If this doesn't make sense let me know and I will amend my answer.
AMENDED ANSWER:
Here is some revised code. Below is an explanation:
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<title>AngularJS Example</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
var myApp = angular.module("myApp", []);
myApp.controller('appController', function($scope) {
$scope.var1 = 1;
$scope.add = function() {
$scope.var1++;
}
$scope.reset = function() {
$scope.var1 = 1;
}
});
//******************* BEGIN TWO WAY BINDING
myApp.controller('watcherController', watcherController);
watcherController.$inject = ['$scope'];
function watcherController($scope) {
$scope.log = '';
var doWatchCode = true;
$scope.$watch('var2', function (newValue, oldValue) {
if (typeof newValue === 'undefined') {
return;
}
if (doWatchCode) {
$scope.var2 = newValue * 10;
$scope.log = $scope.log + '\nvar2 watched newValue(' + newValue+') - $scope.var2('+$scope.var2+')';
doWatchCode = false;
}
else {
doWatchCode = true;
}
});
$scope.add1 = function() {
doWatchCode = false;
$scope.var2++;
};
}
myApp.directive('watcher', watcherDirective );
function watcherDirective() {
return {
'restrict': 'EA',
'template': 'This is var2: {{var2}} <br/><hr/><button ng-click="add1()">Add 1</button><br/><hr/><h4>LOG:</h4><pre>{{log}}</pre>',
'controller': 'watcherController',
'scope': {
var2: '='
},
'link': function ($scope, $element, $attrs, ctrl, transclude) {
$attrs.$observe('var2', function(newValue) {
$scope.log += "\nChanged on the outside"+newValue+' - '+(typeof newValue);
});
}
};
}
//******************* END TWO WAY BINDING
</script>
</head>
<body ng-controller="appController">
<button ng-click="reset()">Reset Var1</button><br/>
<button ng-click="add()">Add from outer controller</button><br/>Var1:{{var1}}<br/><hr/>
<watcher var2="var1"></watcher>
</body>
</html>
What I am doing here is adding a gate variable doWatchCode
that either allows your $watch
function to perform its operations or not.
When doWatchCode
is true
then your $watch
function can do anything it needs to do. If it changes the value of var2
then it must change the value of doWatchCode
to false
. The reason for this is that changing var2
will cause your $watch
function to be called again. But this time doWatchCode
will be false
and prevent the value of var2
from being updated again. The value for doWatchCode
must be set back to true
before exiting the function.
In you add
function you must set doWatchCode
to false
to prevent your internal functions from allowing your multiply to happen again.
When the value for var2
is changed from the outside doWatchCode
should be true
which will allow your $watch
function to do its work.
Let me know if that works.
Upvotes: 2
Reputation: 77904
You need watcher only for 1st time to catch 1st text
change. After that you can cancel it. Also CodeMirror
is 3d party plugin and you can add empty $timeout
to trigger digest cycle. Here is a working fixed demo:
var app = angular.module('app', []);
app.controller('myCtl', function($scope) {
$scope.text = "this is the text";
$scope.showText = function() {
alert($scope.text);
};
});
app.directive('editor', function ($timeout) {
var directive = {};
directive.restrict = 'EA';
directive.scope = {
text: '='
};
directive.template = '<textarea id="cm"> </textarea>';
directive.link = function (scope, element, attrs) {
scope.editor = CodeMirror.fromTextArea(document.getElementById('cm'), {
lineNumbers: true
});
scope.editor.setSize(300, 100);
var canceler = scope.$watch('text', function (newValue, oldValue) {
if (typeof newValue === 'undefined')
return;
scope.editor.setValue(newValue);
canceler();
});
scope.editor.on("change", function(cm, change) {
$timeout(function(){
scope.text = scope.editor.getValue();
});
});
};
return directive;
});
Upvotes: 1