I have a directive that displays the logo of the selected component object.
<div avatar component-id="3"
component-object="Modio.selectedFacility"
image-url="Modio.selectedFacility.logo_url"
bind="true"
style="display: inline-block"
wrapper-style="{'display': 'inline-block' }"
allow-change-photo="true"
size="50"
change-photo="modioModal.showTeamProfileModal"
label="Profile">
</div>
When the user selects an object from the dropdown list, the method setselectedFacility is called from the service. In this method I call another method getFacilityLogoUrl that sends a request to the server to get the logo and as long as there is no response, a directive is called and set default image. When I get the response the update of logo does not occur.
Directive seems doesn't know that something happened.
this.setSelectedFacility = function(facility) {
this.selectedFacility = facility;
if (this.selectedFacility) {
this.getFacilityLogoUrl(this.selectedFacility.id).then(function(url) {
_this.selectedFacility.logo_url = url;
}, modioException.errorFn);
}
//Save to local storage
localStorageService.set('selected-facility', this.selectedFacility);
};
How can I say to a directive to update an old value.
Watcher in my directive:
if (scope.bind) {
scope.$watch('componentObject', function(newValue, oldValue){
if (newValue) {
render();
}
});
} else {
render();
}
I'm not certain because you haven't posted the code for your selectedFacility watcher, but the problem could be that you are using a shallow watcher, but you need a deep watcher, because you are watching object properties.
Try using the third argument to the $watch function. For example:
$scope.$watch('selectedFacility', function (newVal, oldVal) { /*...*/ }, true);
By default, $scope.$watch() will only check for value changes, where the value for primitives is their value, and the value for objects is their reference. Therefore, default watches will only trigger when the object being watched is reassigned.
For objects and arrays you can use a deep watcher, as above, or $scope.$watchCollection, which will watch for reference changes one level deep.
Another solution is to use immutable objects, so that all changes to an object require creation of a new object (and therefore a reference change.) This works very well with AngularJS and Angular's dirty-checking change-detection strategy.
Related
In the following Angular 1.7.5 code, I hook up a text box to a certain controller field with ng-model and use $doCheck to do something whenever the value changes. Note that in my actual project, the value I'm watching is an object and I need to find out whenever any of its fields change, so I have to use $doCheck. I know it's overkill for a textbox.
HTML
<div ng-app="app" ng-controller="TheController as ctrl">
<input ng-model="ctrl.value" />
</div>
JS
angular.module("app", [])
.controller("TheController",
class TheController
{
constructor()
{
this.value = "";
}
$doCheck()
{
console.log("check: " + this.value);
}
}
);
Whenever the value in the text box changes, you can see that $doCheck is called twice (both times with the new value). Why is this? How can I filter out the "pointless" $doCheck calls and just get the ones where the value actually changed?
The $digest loop keeps iterating until the model stabilizes, which means that the $evalAsync queue is empty and the $watch list does not detect any changes.
This means that $doCheck will called a minimum of two times. Once to detect changes and again to detect that there are no new changes. If there are more changes or functions added to the $evalAsync list. It will be called again and again to the limit set by $rootScopeProvider.digestTtl(limit).
If detecting changes, you must store the previous value(s) for comparison to the current values.
this.$doCheck = function() {
var newValue;
var oldValue;
oldValue = newValue;
newValue = itemOfInterest();
if (newValue != oldValue) {
//Code to act on changes
};
};
For more information, see
AngularJS Developer Guide - Integration with the browser event loop
AngularJS $compile Service API Reference - Life-cycle Hooks
I have a variable that stores the available cars at any moment. Is there a way to automatically re-evaluate this function on every change?
Just using this.carFactory.available in this case is not a solution, because this example I'm showing is simplified - the real calculation in my project is alot more complex.
calculateAvailableCars(){
this.carFactory.available.forEach(function(item){
this.availableCars.push(car.id);
}.bind(this));
}
How could I do this in Angular 2? In Angular JS there was the possibility to $watch a function.
I could of course manually call this function everytime something changes, but it would be nice not to have to call this function in every part of the application that can change the data.
Using template function reference with auto change detection
You can use this function output on template:
carOutput(): cars[] {
this.calculateAvailableCars()
return this.availableCars;
}
and use output on template:
<p>My car ratio is {{ carOutput() }} </p>
However this will trigger very aggressive change detection strategy on this variable. This solution is the simpliest one, but from engineering perspective rather worst: consumes tons of unnecessary function calls. One note, that hosting element must not be set to detect changes onPush.
Separate data model to parent component and pass as property to child
You can store car list display in separate component, and pass new car array as input property to this component:
<car-display [cars]="availableCars"></car-display>
Then you can set changeDetetcion policy in this component to onPush, and each time input property bind to availableCars will change, <car-display> will re-render.
If update relays on some host binding
If some external host action is triggering new cars calculation, then hostBinding may help:
#hostListener(`hover`) recalculateCars() {
this.calculateAvailableCars()
}
And finally, (because you describe your use case quite cryptically, without many details, thus I'm scratching all possible scenarios) if some external component action shall trigger re-calculation, you can hook to ngLifecycle ngOnChanges() if for example external input property change shall re-trigger cars calculation.
In other words and summing all that up, it depends who and from where triggers changes, that shall re-trigger available cars recalculation.
And very important, see an answer from #chiril.sarajiu, because what we are trying to work around here can be handled automatically by single observable. This requires additional setup (service, provide observable to components, e.c.t.) but it's worth.
--- EDIT ---
If each variable change shall retrigger data
As OP clarified, that changes are related with model bound to component. So another option with mentioned by #marvstar is using set, where each model variable change will retrigger fetching function:
modelSchangeSubject: Subject<Model> = new Subject<Model>();
ngOnInitt() {
this.modelSchangeSubject
.subscribe((v: Model) => {
this.calculateAvailableCars()
})
}
/* Rest of controller code */
set modelBounded(v: Model) {
this.modelSchangeSubject.next(v);
}
You need RxJS. What you do is you create a data service, which will store an Observable (in my case a BehaviorSubject, which is mostly the same, but in my case I start with a value).
export class DataService {
private dataStorage$ = new BehaviorSubject(null); //here is the data you start with
get getDataStorage() {
return this.dataStorage$.asObservable(); // so you won't be able to change it outside the service
}
set setDataStorage(data: any) {
this.dataStorage$.next(data);
}
}
Then you subscribe to this data changes everywhere you need to:
constructor(private dataService: DataService){}
ngOnInit() {
this.dataService.getDataStorage.subscribe((data) => this.calculateAvailableCars(data));
}
calculateAvailableCars(){
this.carFactory.available.forEach(function(item){
this.availableCars.push(car.id);
}.bind(this));
}
Read more about best practices of using RxJS in Angular, as there can be quite a bit of pitfalls and problems.
Try using setter and getter.
private _YourVariable:any;
public set YourVariable(value:any){
this._YourVariable = value;
//do your logik stuff here like. calculateAvailableCars
}
public get YourVariable():any{
return this._YourVariable ;
}
I have this Vue.js code:
new Vue({
data:{
myValue:'x',
myOtherValue:'y'
},
computed: {
myComputed: myFunction(){
return this['my' + 'Value']
}
}
})
As you can see the computed property will be cached and it is depended only on data.myValue. My question is how Vue.js caching system knows that run the computed function again only if myValue is changed?
If I change the myOtherValue variable, the myComputed function will use the cache, and will not be run again will I call it.
I thought about several ways how it is possible. But how Vuejs doing that?
I have read this article: https://v2.vuejs.org/v2/guide/computed.html and found no answer.
And what happen in this code, what it will be depeneded on?
const flag=2
new Vue({
data:{
myValue:'x',
myOtherValue:'y'
},
computed: {
myComputed: myFunction(){
if (flag==1){
return this['my' + 'Value']
}
else
return this['my' + 'Other' + 'Value']
}
}
})
Bonus: I will appreciate I link to the relevant function in the VueJS code: https://github.com/vuejs/vue
I will address only the specific question how does vue.js know which dependencies affect which computed property?
The simple answer is that each time vue evaluates a computed property it creates a map of all the reactive properties that were accessed in the span of that call. The next time any of these reactive properties change they will trigger a reevaluation of the computed property.
If during the most recent evaluation of a computed property, one of its reactive dependencies is never reached (maybe because it is within the non-traveled path of an if/else construct), subsequent changes to that reactive property will not trigger a reevaluation of the computed property.
Observe this behavior by modifying the two reactive properties in this fiddle (by simply typing in their corresponding input boxes). A few things to note:
the called computed property is evaluated once on document load (it's triggered because it's rendered in the template).
because the path is set to 1 the reactive property that will be mapped as a dependency is val1. As a result it will be the only one that can trigger a reevaluation of called when it changes. The value of val2 can also change but will not have the same effect on called, even though it's clearly present in the function.
When you click on the "Change Path" button, path is toggled from 1 to 2.
right after the path switch, note that a change to val1 will affect called only once more. Because path has been set to 2 prior to that last reevaluation, val1 will not be reachable and will not be mapped as a dependency of called any longer. Subsequent changes to its value won't trigger a reevaluation of called from that point on. But then val2 has now been mapped as a dependency of called and changes to it trigger the reevaluation the same way they did for val1 earlier. It will be so until the next path toggle from 2 back to 1.
Here's the code.
let path=1
let count=0
const vm=new Vue({
el:"#app",
data:{
val1:null,
val2:null,
},
computed: {
called: function(){
if (path==1){
this.val1
}
if (path==2){
this.val2
}
return "I was just called "+ ++count +" times"
}
},
methods: {
changePath(){
path = path==2 ? 1 : 2
}
}
})
and corresponding template
<div id="app">
<input v-model="val1"/> {{val1}}
<br>
<input v-model="val2"/> {{val2}}
<br>
<button #click="changePath">change path</button>
<br>
{{ called }}
</div>
It's the reactivity system of Vue.js, not a caching system.
The data in a component will be convert to getters and setters. When you access a value via a getter, the getter will add it to the dependencies, and when you modify the value via a setter, the setter will notify everyone who depends on the value.
Here is the source code, all the magic happens in this function: https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js#L131
From the docs it reads that:
Computed properties are cached, and only re-computed on reactive dependency changes.
However the following fiddle shows something a bit different.
https://jsfiddle.net/z11fe07p/267/
From the fiddle if you set the flag to 2, the computed property will be re-evaluated and executed if you change myOtherValue, however this will not happen if the flag is set to 1. I think it keeps track of your if conditions.
In the docs usually you can find links to the relevant source code.
Here is the code for computed properties:
https://github.com/search?utf8=%E2%9C%93&q=repo%3Avuejs%2Fvue+extension%3Ajs+%22computed%22&type=Code
In my angular application, I am using an ng-repeat and, inside each, a child controller with a form for each item. I'm also using a debounce so that I can auto-save the data. But the issue is that after saving the data (with Restangular), the changes are not propagated back up to the array in the parent. A simple example will work using angular.copy, but this has issues with Restangular and its replacement, Restangular.copy, does not function exactly the same way. I also tried explicitly setting the item to the right index on the array, but it causes my cursor to lose focus in the input.
Here is a simplified version of my child controller's code. And here is a full JS Bin. Is there another approach to solve this problem?
$scope.personCopy = angular.copy($scope.person);
// Debounce and auto-save changes
$scope.$watch('personCopy', function(newVal, oldVal) {
if (newVal && newVal != oldVal) {
if (timeout) $timeout.cancel(timeout);
timeout = $timeout(savePerson, 1000);
}
}, true);
var savePerson = function() {
// (In my real app, the following is inside a save callback)
// Method 1: (doesn't work at all)
$scope.person = $scope.personCopy
// Method 2: (works with angular.copy, but not Restangular.copy)
// angular.copy($scope.personCopy, $scope.person);
// Method 3: (works, but cursor loses focus)
// $scope.people[$scope.$index] = $scope.personCopy;
};
Method 3 will work for you if you add "track by" to your ng-repeat:
<li ng-repeat='person in people track by $index' ng-controller='EditPersonController'>
Here's that working: http://jsbin.com/jitujaro/3/edit
The reason you're losing focus is the DOM for that ngRepeat is currently being recreated when you update people. So the element that the focus was on is gone. When using track by Angular knows it doesn't need to recreate those DOM elements.
The reason method 1 and 2 don't work is Javascript's prototypal inheritance. Upon writing to a variable that is on a parent scope a new local copy of that variable is made on the local scope. However, when writing to a property of an object that is on a parent scope (as you do in method 3), the write occurs on the parent object as you expect.
Try
angular.extend($scope.person, $scope.personCopy);
instead of
$scope.person = $scope.personCopy;
In knockoutJS it is possible to subscribe to a change of an observable viewmodel property, e.g., like:
myViewModel.personName.subscribe(function(newValue) {
alert("The person's new name is " + newValue);
});
I am currently learning AngularJS, and I was wondering if there is an equivalent to this in AngularJS? I have tried searching for this, but without any luck.
The scope object in AngularJS has a special method called $watch for 'watching' scope properties.
It accepts a callback that receives the new and the old value of the model:
$scope.$watch('modelName', function(newValue, oldValue){
// Do anything you like here
});
The callback is on initialization and each time the model changes. Therefore it may be good to add an extra check for equality like this:
$scope.$watch('modelName', function(newValue, oldValue){
// Check if value has changes
if(newValue === oldValue){
return;
}
// Do anything you like here
});
This allows you to 'watch' your model and perform some action if needed.
One extra remark: if you're watching a model that contains an object, you should use an extra third parameter that tells AngularJS to compare both values by object equality and not by reference (as the reference would not change and thus not trigger the watcher) like this:
$scope.$watch('modelName', function(newValue, oldValue){
// Do anything you like here
}, true); // Add extra 'true' parameter to check for object equality
You can read more documentation on the AngularJS scope page.
Hope that helps!
For API calls and async data triggered by user actions or application events, you are better off using
$rootScope.$broadcast('updateSearch', value);
directly in the callback of the service function and leveraging it in your controllers like:
$scope.$on('updateSearch', function(event,value) {
$scope.search = value;
});
You can check when a property of a scope object changes using $watch.
http://docs.angularjs.org/api/ng.$rootScope.Scope#$watch