$scope or scope in $scope.$watch? - javascript

Found such an idea in article:
Notice how the value function takes the scope as parameter (without
the $ in the name). Via this parameter the value function can access
the $scope and its variables.
$scope.$watch( function( scope ) {
return scope.val;
...
instead of what i used to:
$scope.$watch( function() {
return $scope.val;
...
Is it really better? And what is the reasoning behind this way?

From AngularJs docs
function(scope): called with current scope as a parameter.
So it does not change the behavior of your code. However this version prevents a capture of the $scope variable inside the callback :
$scope.$watch(function(scope) {
return scope.val;
}, function(value){ });

Related

Force Two-Way-Binding to Take Effect

I am writing an AngularJS 1.x directive (let's call it MyDirective). Its scope is declared as follows:
scope: {
accessor: '='
}
In its link function, I am assigning a new object to that accessor field, like so:
scope.accessor = {
// methods such as doSomethingToMyDirective()
};
Now, I am instantiating this directive dynamically with $compile:
var element = $compile('<div data-my-directive data-accessor="directiveAccessor"></div>')(myScope);
Once this has run, my current scope (myScope) has a directiveAccessor property that references the object instance created within the directive.
Problem: This field is not immediately available.
In other words, once I have run $compile, I cannot access myScope.directiveAccessor immediately in the next command. When I check the scope later, the field is there, and probably, a single $timeout would be sufficient.
With some breakpoints, I can observe that the object is indeed created right when $compile is executed; accessor on the inner scope already points to the object. However, it seems that the two-way-binding that would copy the value from accessor on the inner scope to myScope.directiveAccessor does not become active until a later point.
Is there any way to force AngularJS to copy two-way-bound values immediately (i.e. without waiting for any promise)?
Use expression binding (&) to immediately set a parent scope variable:
app.directive("myDirective", function () {
return {
scope: { onPostLink: "&" },
link: postLink
};
function postLink(scope, elem, attrs) {
scope.accessor = {
doSomethingToMyDirective: function() {
return "Hello world";
}
};
scope.onPostLink({$event: scope.accessor});
scope.$on("$destroy", function() {
scope.onPostLink({$event: null});
});
}
})
Usage:
<my-directive on-post-link="directiveAccessor=$event">
</my-directive>
Be sure to null the reference when the isolate scope is destroyed. Otherwise the code risks creating memory leaks.

AngularJS: How to add setter on $scope object property of directive?

This is my current implmentation to fire callback on customVar get change using $watch...
module.directive('mudirective', function() {
return {
scope: {
callback: '&'
},
template: '<h1>Hello</h1><button ng-click="changeVaar()>Click</button>"',
controller: function($scope) {
$scope.customVar = false;
$scope.changeVaar = function() {
// some large logical execution
// which set customeVar
$scope.customVar = '';//some value assgined
};
},
link: function($scope) {
$scope.$watch('customVar', function() {
$scope.callback();
});
}
};
});
But i would like to replace this $watch with setter...
Can anybody has idea how could it be possible?
OR
Other option to avoid $watch function but fire callback on customVar changes.
But callback should be fire once it is confirmed that customVar
has changed in directive itself.
First, I will answer the comments under the question. I had this use case when I saw a controller putting a watcher on a scope value only to detect changes while the value was changed only by assignments inside the controller itself...
The watch was calling a function updating the UI depending on the assigned value (null or not, whatever).
Of course, we could call this function on each assignment. Or replace the watch with a function setting the value given as parameter, and calling this function. But somehow, using a setter was more "transparent", made a minimal set of changes, and you are sure not to miss an assignment.
On hindsight, it is similar to the way MobX works (go see this library if you have complex dependency watching to do).
Second, here is how to do it:
Object.defineProperty($scope, 'watchedValue',
{
set(newValue) { $scope._watchedValue = newValue; this.doSomethingWith(newValue); },
get() { return $scope._watchedValue; },
});

Checking for defined functions in nested directives

When passing a function into a directive which then is passed into a nested child directive the function is always considered defined when checked in the scope of the child directive regardless if it is passed in or not in the parent directive.
Is there a better way to either pass in function pointers or check if they are defined when dealing with nested directives.
plunker
<body ng-app="myApp">
<div ng-controller="myController">
<dir1"></dir1>
</div>
<script type="text/ng-template" id="dir1">
<div>
<dir2 fun="fun()"></dir2>
</div>
</script>
<script type="text/ng-template" id="dir2">
<div>{{fun()}}</div>
<div>{{funDefined()}}</div> <!-- always true-->
</script>
</body>
var app = angular.module('myApp', []);
app.controller('myController', function($scope) {
$scope.fun = function() {
alert("function");
};
});
app.directive('dir1', function() {
return {
scope: {
fun: '&'
},
templateUrl: 'dir1'
};
});
app.directive('dir2', function() {
return {
scope: {
fun: '&'
},
link: function(scope, elem, attrs) {
scope.funDefined = function() {
return angular.isDefined(attrs.fun);
};
},
templateUrl: 'dir2'
};
});
If you set debugger inside your scope.funDefined method of dir2 you'll see that attrs.fun equals string "fun()". That's because you take raw value from attributes. And since it's a not empty string it'll always give you true.
Here is updated plunker
There's no elegant way I know to get what you want. Like it was mentioned before this line:
angular.isDefined(attrs.fun)
performs check on string so it will return true every time fun attribute is defined. And in your dir1 directive template you have <dir2 fun="fun()"></dir2> so fun is obviously defined (and it's string). If you take a look at angular's sources:
case '&':
// Don't assign Object.prototype method to scope
parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
// Don't assign noop to destination if expression is not valid
if (parentGet === noop && optional) break;
destination[scopeName] = function(locals) {
return parentGet(scope, locals);
};
break;
you'll see that presence of the attribute will always result in some function assigned to the scope ($parse returns function even for string that doesn't make much sense).
So the only solution I can think of is to perform check in the first level directive (it's possible there since attribute is really undefined) and have two <dir2> tags (with and without fun attribute) - one always excluded using ng-if. Something like this. Again, I know, its ugly solution.
One side note - Angular's source also shows that scope property will not be set if there's no attribute and binding is optional (using &?) - then you can check scope.fun value instead of attrs.fun - some may find it more elegant.
The best way I could find is based in what #xersiee commented in another answer. The idea is to make the scope parameter optional in the parent directive and then use angular.isUndefined(scope.$parent.$eval(attribute.myFun)) to check if the function was passed or not. This is not explained in the official documentation... I wonder why.
As other people has mentioned, this solution is far from ideal because using scope.$parent is an anti-pattern, but again, this is the best option I could find.
Plunker with this solution: http://plnkr.co/edit/SUUMae?p=preview

How to specify execution order in (asynchronous) Angular/Javascript

I have a custom directive (this is just a simplified example). When the directive is clicked, a variable is updated and a function is executed. Both the variable and the function is from the parent scope, accessed via two-way binding and method/behavior binding respectively.
In some cases the parent scope might overrule the directive and force the variable to be a different value than the one set by the directive.
My problem is that the function is called before the variable is updated, probably because the asynchronous behaviour. That means that the variable gets overwritten by the directive.
Is there some way to force the function to be called after the variable has been updated?
This is from the directive's link function:
element.bind('click', function(){
scope.val = 'someValue'; // This should alway be executed first
scope.directiveClicked(); // This should always be executed last
});
The variable and method are accessed like this:
scope: {
val: '=value',
directiveClicked: '&onDirectiveClick'
}
Turned out that it was not called in the wrong order after all, but somehow the updated value was not displayed/stored. Adding a scope.$apply() before notifying the parent function did the trick.
element.bind('click', function(){
scope.val = 'someValue';
scope.$apply();
scope.directiveClicked();
});

add watch on a non scope variable in angularjs

Is there a way to add watch to a non scope variable. I want to add a watch to local variable. I have something like this
function EditAssetRegistryController(assetregistryService, manufacturerService, assettypeService, projectService, $localStorage, $routeParams) {
var vm = this;
vm.manufacturers = [];
vm.projects = [];
vm.asset_types = [];
vm.ch_group_uniq = 'none';
}
here is there a way to add watch to vm.ch_group_uniq?
I know how it will be done with scope variable but I have scenarios where I have to check many complex variables.
Well, you can easily add a watch for anything by passing a function as the first parameter:
$scope.$watch(function watchFunction(scope) {
return vm.ch_group_uniq
}, handler)
A few things to consider: watchFunction must return the same value if nothing has changed. This can lead to some gotchas, for example, returning the result of some array operations: [1,2,3].filter(...) will always return a new array, and lead to an endless $digest cycle. Also note the third parameter of $scope.$watch, which indicates whether to use an identity comparison, or angular.equals when comparing the values. (Check out the docs for further information - https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch)
However, your specific problem seems to be trying to use controllerAs and custom $watch-es. There's a very handy library that addresses this issue specifically: https://github.com/christopherthielen/angular-360-no-scope
$watch will not work as normal syntax with controllerAs. You need to bind it to $scope, and then you can watch that variable:
Code
$scope.$watch(angular.bind(this, function (ch_group_uniq) {
return this.ch_group_uniq;
}), function (newVal, oldVal) {
console.log('Name changed to ' + newVal);
});
Here is the reference Todd Motto Article
A cleaner syntax using ES6
$scope.$watch(() => {
return this.thingToWatch;
}, (newVal, oldVal) => {
// Your code here...
});

Categories

Resources