I have a object and want to run a function if that object changes. I tried using angular $watch, but this only works without setters.
Searching for another solution i found obj.observe but mdn says it's obsolete and not widely supported. so is there any other way to run a function if my object changes?
Edit:
// watch using this doen't work, when update through ng-model
Object.defineProperties(self, {
'myprop': {
get: function () {return _myprop; },
set: function (value) {
_myprop = value;
}
},
I'm not sure what do you mean by $watch "working without setters" .
scope.$watch('name', function(newValue, oldValue) {
console.log('name has been changed: ' + $scope.name);
scope.counter = scope.counter + 1;
});
Just start simple, put the above code into a controller and have a simple field modifying the $scope.name variable. You'll see in the console that the watch is triggered.
Related
In AngularJS the data-binding work to expose immediate data to our View !!
this stuff's due of the object scope which is the glue between the Logic Code AND The View.
Also all we know that AngularJs support the tow-way-binding !!!
My Question Is :
How the $scope can know that there object binding was changed or not??
if there while condition inside scope for auto-change detect or what?
Check angular.js file, we will get the code for ngBindDirective:
var ngBindDirective = ['$compile', function($compile) {
return {
restrict: 'AC',
compile: function ngBindCompile(templateElement) {
$compile.$$addBindingClass(templateElement);
return function ngBindLink(scope, element, attr) {
$compile.$$addBindingInfo(element, attr.ngBind);
element = element[0];
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
element.textContent = isUndefined(value) ? '' : value;
});
};
}
};
}];
Note the last two line, it used watcher for attribute ngBind, for any change it apply to the element.
How can I listen to angular component binding change and perform actions?
angular.module('myapp')
.component('myComponent', {
templateUrl: 'some.html',
controller: MyController,
controllerAs: 'myCtrl',
bindings: {
items: '<'
}
});
now when items changes I want to perform another action using this value,
How can I do it?
You can add the $onChanges method to the controller
$onChanges(changesObj) is called whenever one-way bindings are updated. The changesObj is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form.
Following example handles canChange change event.
angular.module('app.components', [])
.component('changeHandler', {
controller: function ChangeHandlerController() {
this.$onChanges = function (changes) {
if (changes.canChange)
this.performActionWithValueOf(changes.canChange);
};
},
bindings: {
canChange: '<'
},
templateUrl: 'change-handler.html'
});
Requires AngularJS >= 1.5.3 and works only with one-way data-binding (as in the example above).
Docs: https://docs.angularjs.org/guide/component
Reference: http://blog.thoughtram.io/angularjs/2016/03/29/exploring-angular-1.5-lifecycle-hooks.html
now when items changes I want to perform another action using this value,
How can I do it?
But I want to avoid using the dying $scope
If you don't want to use $scope you can use a property setter to detect any changes e.g. :
class MyController {
private _items: string[] = []
set items(value:string[]){
this._items = value;
console.log('Items changed:',value);
}
get items():string[]{
return this._items;
}
}
const ctrl = new MyController();
ctrl.items = ['hello','world']; // will also log to the console
Please note that you shouldn't use it for complex logic (reasons : https://basarat.gitbooks.io/typescript/content/docs/tips/propertySetters.html) 🌹
Here's an ES5.1 version of basarat's answer:
function MyController() {
var items = [];
Object.defineProperty(this, 'items', {
get: function() {
return items;
},
set: function(newVal) {
items = newVal;
console.log('Items changed:', newVal);
}
});
}
Using Object.defineProperty(). Supported by all major browsers and IE9+.
I've discovered a way but not sure it's the most efficient. First bring in $scope as a dependency and set it to this._scope or the like in your constructor. I have the following then in my $onInit function:
this._scope.$watch(() => {
return this.items;
},
(newVal, oldVal) => {
// Do what you have to here
});
It's highly inspired by the answer here: Angularjs: 'controller as syntax' and $watch
Hope it helps, it's what I'm going to use until I'm told otherwise.
Currently you can't use angular watchers without $scope as change detection is based around $scope. Even if you use expressions in HTML it would delegate watch functionality to $scope.
Even if you create some other mechanism to watch you will need to remember to unwatch manually - and with $scope it's done automatically.
This approach might help:
import { Input } from '#angular/core';
class MyComponent {
#Input set items(value) {
if (this._items !== value) {
console.log(`The value has been changed from "${this._items}" to "${value}"`);
this._items = value;
}
}
private _items;
get items() {
return this._items;
}
}
I am trying to figure out how to create my own "one time binding", for Angularjs <= 1.2.
I found this answer, describing how to create your own bindOnce directive. I do see that when using the following directive:
app.directive('bindOnce', function() {
return {
scope: true,
link: function( $scope ) {
setTimeout(function() {
$scope.$destroy();
}, 0);
}
}
});
Data is binded once. But, I can see that the $$watchers is still on. Take a look at the following JSBin - running the commented watcher count code on the console will reveal that the watchers are still alive.
EDIT: for some reason, when using the same directive with angular 1.3, the watcher count dod changed to be 0!!!
Use the cleanup function to remove watchers:
function cleanup(element)
{
element.off().removeData();
var injector = currentSpec.$injector;
element.$injector = null;
// clean up jquery's fragment cache
angular.forEach(angular.element.fragments, function(val, key) {
delete angular.element.fragments[key];
});
angular.forEach(angular.callbacks, function(val, key)
{
delete angular.callbacks[key];
});
angular.callbacks.counter = 0;
}
Use a self-destructing service as a simple bind once:
function onetime()
{
/*...*/
onetime = Function;
}
angular.module('myApp').constant('bindonce', onetime);
angular.module('myApp').controller('foo', function($bindonce){
this.$inject = '$bindonce';
$scope.mybind = $bindonce();
}
Use the migration guide for reference to find breaking changes:
References
testabilityPatch.js
angular-mocks.js
AngularJS Developer Guide: Migration from Previous Versions
I have a situation in which I want to monitor a specific variable.
$scope.$watch('action', function() { /* do something */ }
However, I only want to do something if $scope.id didn't change (if $scope.action changes it is possible that $scope.id has changed too.)
The only solution I can think of is
$scope.$watch('action', function() {
if(idHasChanhed === false){
/* do something */
}
idHasChanged = false;
});
$scope.$watch('id', function() { idHasChanged = true }
However, I was wondering if angular has a better solution than this, and I don't know if this solution will always work ( is the order in which the $watches are executed random !?)
My solution :
$scope.$watch('[action,id]', function() {
// called when action or id changed
});
Info : The string concatenation action + id will fail.
var action,id; // variables you need to watch
$scope.$watch('action + id', function() {
// actions
});
$watchGroup This function is introduced in Angular1.3. This works the same as $watch() function except that the first parameter is an array of expressions to watch.
Use $watchGroup
$scope.$watchGroup(['action', 'id'], function(newVal, oldVal) {
//write your code here
});
I have an object outside the AngularJS scope that looks something like:
obj = { isBig : true, name: "John" ... }
it has many parameters and it was created outside the angular scope and it updates outside of angularJS (for example - via ajax call that I make with jQuery).
inside my controller I write:
$scope.obj = obj;
How do I create an event that listens to any of the object's changes and updates the view ? So the event is the objects's changes
Specifically I have a parameter called obj.isAjaxCallFailed = true/false that I use in the view as <div ng-show="obj.isAjaxCallFailed"></div>
It's not working. I tried using $on and I can't figure out any way to do it.
Use $watch() on obj to listen for changes (in your controller):
$scope.$watch(function() {
return obj;
}, function(value) {
if(value) {
$scope.obj = obj;
}
}, true);
At the place in your code where you create or update obj, you use $rootScope.$apply() to kickoff the watches:
function someCallbackOutsideAngular(data) {
var $rootScope = angular.injector(['ng']).get('$rootScope');
$rootScope.$apply(function() {
obj.someKey = data.someValue;
});
}
Note: angular.injector(['ng']) should probably set somewhere outside the callback (no need to execute it all the time).