Angular 6 template doesn't update when variable changes - javascript

I have a menu object that will end up displaying a menu on the template. One of the menu objects properties is set by a variable that gets changed during the ngOnInit(). At that point, the template is already created I believe, so the menu never updates.
We want the MemberProfile link to display the user ID and not the 'undefined' that it is initialized with.
Check out the stack blitz link.
It's pretty straight forward I know I'm missing something simple.
When you open the menu, I'm printing out the Link Label and the Link URL.
The second link should be Admin/MemberProfile/45 and not Admin/MemberProfile/undefined
https://stackblitz.com/edit/angular-ivy-yu9hty

First, you need to understand how template/html rendering in Angular works.
As in your case, since you are rendering your menu link list from an array, menu.admin. Angular will not re-render the *ngFor block, until it detects changes in the menu.admin array. So, you just need to update that array.
Now, what you can do is something like this,
public ngOnInit() {
this.userId = 45;
this.menu.admin[this.menu.admin.length - 1].link = '/Admin/MemberProfile/' + this.userId
}
For more info please refer to Angular lifecycle hooks.

When the property menu is declared and assigned a value, this expression
'/Admin/MemberProfile/' + this.userId
is evaluated, using the then-current value of userId. When you do
this.userId = 45;
the value of menu isn't computed again.
There are several possible solutions. Put the menu layout in the template, instead of the TS (and use {{ userId }} there), use RxJS to update the thing dynamically, or simply imperatively recalculate the value of menu when changing userId (the most crude solution, and requiring the least amount of writing on my part), shown here:
https://stackblitz.com/edit/angular-ivy-qpnaqz?file=src/app/app.component.ts
Player's choice.

Related

Clone sapUi5 json model and stop binding

I have seen a similar question, but in my case it doesn't work.
I have a JSON model, called data, which corresponds to a SAPUi5 form with comboboxes. I want to copy the state of the model the first time I open my application and keep it like that. After that I want to use it to reset my form and bring the comboboxes back to their default values.
When I first start my application:
this.getView().setModel(new JSONModel(data)); //create the original model
//copy the original model (copyModel is global variable
copyModel = $.extend({}, data);
Until here everything is fine. The two models are exactly the same. After that I have a button and a reset Function:
resetP: function(){
this.getView().setModel(new JSONModel(copyModel));
console.log(copyModel);
}
The first time I select something in the comboboxes and click the reset button and run the reset function, the copymodel is the right one. Same with the original data model. When I change again the selected value of the combobx, the copyModel, starts taking the selected value. Somehow it's overwritten. I don't know what I am doing wrong. Are there any suggestions? I have also tried to use JSON.strignify instead of extend.
JSON models be default have two way binding. So when you are triggering events like selectionChange on the ComboBox, because of two way binding, the set data to the model keeps getting updated. Also Javascript has objects by reference, so it is the original copyModel object that gets updated.
You can prevent this by setting a copy of the copyModel to the JSON model.
Another thing I would like to mention is that do not keep setting the model again and again.
You can just update the data that is set to the model and update the model.
This can be done in 2 ways.
a.
resetP: function(){
this.getView().getModel().setData(copyModel);
console.log(copyModel);
}
b. You could also update the required property and do a
this.getView().getModel().updateBindings();
We use jQuery.extend(true, {}, object_to_copy); in this way to create a "deep copy" from the object we want an independed copy from.

Angular scope doesn't update when removing an item

I have a simple application where I can add users to a list and remove them.
The form to add a new user binds to $scope.newPerson. On submitting the form, I add the newPerson object to $scope.people which is an array containing person objects.
I loop over the people array with an ng-repeat directive, to print out the people who are currently added to the scope. These rows all have a remove button (Jade snippet):
div.row(data-person, ng-repeat="person in people", ng-model="person")
button(ng-click="removePerson(person)") Remove
When I click the Remove button, I execute this function:
$scope.removePerson = function(person) {
var index = $scope.people.indexOf(person);
if (index > -1) {
$scope.people.splice(index, 1);
person = null;
}
}
This removes the row from the table, and sets the person scope to null. Batarang shows { This scope has no models } afterwards.
However, I have noticed that my people array doesn't update. When I check it's scope in Batarang, the person I just deleted is still in that array. When I start typing to add a new person, it updates. If I submit the whole page to my server without doing this, the array still contains the removed people.
If i put $scope.$apply() after person = null;, I get the expected behaviour, however it throws an error that an apply is in progress. I also read calling $apply() yourself is considered bad practice. (?)
I'm new to Angular and I can't seem to find a lot of information about solving this problem. How would I make it update my array when I remove a person? Thanks.
I did the following to fix this:
No more ng-model on the ng-repeat block:
div.row(data-person, ng-repeat="person in people")
Refactored the ng-click event for removePerson():
<button ng-click="removePerson($index)">
Remove
</button>
and changed the removePerson() code to this:
$scope.removePerson = function(index) {
$scope.people.splice(index, 1);
};
Not sure if this actually fixed anything compared to my previous code, because I noticed that this was also a Batarang issue. When I simply log {{ people }} to my HTML, or console.log($scope.people), I see the people array update. However, in Batarang, the array does not update.
Lesson learned: sometimes, logging stuff out yourself is better than relying on tools ;)
slice method do not update your array, but return New one

KnockoutJS: Stop a particular property from setting dirty flag

With some help from StackOverflow community I was able to get my dirty flag implementation to work, based on this example: http://www.knockmeout.net/2011/05/creating-smart-dirty-flag-in-knockoutjs.html
It does exactly what I want, except for a single use case that I don't know how to solve.
Basically I have a select menu that gets automatically populated from the database. This select menu also has an option to make an Ajax call to my back end and have the list of options refreshed, database updated and return the result. This is where things get hairy for me.
First method works fine, however, it has to re-index and re-apply my entire viewModel and takes about 2-3 seconds, running on a local machine with 16gigs of ram and SSD.
jsondata.component.available_tags = result.available_tags;
ko.mapping.fromJS(jsondata, viewModel);
Second method also works, and pretty much instantaneous, however, it sets of isDirty() flag, which I would like to avoid, because this data is already coming from the database and I wont need to save it. I can not use isDirty.reset() method either, because if isDirty was set by something else before I clicked an menu option to update available_tags, it will reset that too. Which I would also like to avoid.
viewModel().component.available_tags(result.available_tags);
My question is: With the first method, can I force UI refresh with ko.mapping.fromJS() on a particular element and not entire dataset? Or, with a second method, can I avoid setting isDirty flag set when available_tags are updated? The twist is that I still need to keep available_tags as an observable, so the select menu is automatically generate/updated.
UPDATE: I was able to update mapping for that one single element with
ko.mapping.fromJS(result.available_tags, {}, viewModel().component.available_tags);
but that immediately set off isDirty flag... Argh
In addition to Tomalak's suggestions, which I totally agree with, maybe the toJSON method can help you out in similar cases where you don't want to split the model. If your dirty flag implementation uses ko.toJSON as a hash function, as Ryan Niemeyer's does, you can give your model (on which the dirty flag is active) a toJSON method, where you do something like this:
function MyObjectConstructor() {
this.someProperty = ko.observable();
this.somePropertyNotUsedInDirtyFlag = ko.observable();
}
MyObjectConstructor.prototype.toJSON = function () {
var result = ko.toJS(this);
delete result.somePropertyNotUsedInDirtyFlag;
return result;
};
Please be aware that this is also used to serialize the object in some other occassions, such as ajax calls. It's generally a handy function for removing computeds and such from your objects before using them in a different context.

AngularJS binding between variables

I've got a list of users which I retrieve from my service. When I select any user I can see and edit info (email, roles, etc). The problem is that I don't want these changes to affect user's data in the list, I want to update data only after saving (clicking a button).
Now I'm using two variables:
$scope.selected - currently selected user
$scope.editable - variable for storing the data I'm editing
And I exchange data like this:
$scope.initEditable = function ()
{
$scope.editable = {};
$.extend($scope.editable, $scope.selected);
}
Looks like a terrible solution. What is the proper way to do it?
Actually, this is the Angular-way of approaching this problem, you are on the right track. In scenarios like yours one would typically:
Copy an item upon selection (edit start) - this is what you do with editable
Have 2-way data binding changing a copy (or an original element)
Upon edit completion we can propagate changes from a copy to the original
The nice things about this pattern is that we can easily:
Offer the 'cancel' functionality where users can revert their changes
Be able to compare a copy and the original and drive parts of the UI based on this comparison (for example, we could disable a 'Save' button if there were no changes)
So, I don't think at all that this approach is terrible. The only suggestion I could have is to use angular's angular.copy method instead of $.extend.

Get Titanium.App.Properties in another win or make it global available

Please don’t answer this if you don’t take the time to understand my question or have a reasonable answer. I have got a few answers that is far on the side and I think I explain my problem very clear. Shall this problem drive me nuts or is there somebody out there with a straight and clear answer on Titanium.App.Properties?
I have a login form that stores the username in one:
Titanium.App.Properties.setString("userName",e.value);
and the password in another:
Titanium.App.Properties.setString("passWord",e.value);
The forms TextFields holds these values(after a store) even if I close the window, shut down and restarts the app. This because of the App.Properties.getString("value"); I suppose….?!
But when I copy the hole form with its App.Properties.getString("value"); to another window, the TextFields are empty and contains no value at all. I understand that the Properties string must be there some where in App.Properties, but how can grab it and use it another place in the app?
My question is: How to get the value from my
var userNameValue = Titanium.App.Properties.getString("userNameValue");
to be available in another window or for the hole app(global)? How can I grab it and make use of it a another place in the app? I don’t see a good reason to make these, only separate words, into objects(JSON) since the userName only contains a e-mail address and the passWord consist only of continuing letters or numbers. But if you mean I have too, -how do I set this from my TextField and get it in another TextField somewhere else in my app. I have not had any luck so far. I hope you can help me out and I can keep sane.
Titanium.App.Properties.getString("userNameValue");
This is globally Available, any Propertie of the Titanium Object is accessible in each file.
but if for some reason this doesnt work for you and you want to set a global variable,
you could do the following:
Create a file called myGlobals.js //or anything else,
//Put this in there e.g
var myProperties = {};
in any file you want to use it write in the first line
Ti.include('myGlobals.js');
Then you can make a propertie global available, for example write this in app.js somewhere where the app initializes
myProperties.Username = Titanium.App.Properties.getString("userName");
Then you can get the value in each file by accesing the propertie
myProperties.Username
//of course the Propertie has to be set before you can get them
( Titanium.App.Properties.setString("userName",e.value); ) //like you do it
But, Titanium.App.Properties.getString("userName");
should be avilable from any file anyway, (but you can give this a try although i dont think its nice to do it like this)
i had a similar problem where this didnt get any value from a propertie set in the ios settings as default value.
I had to go to the settings and manually change or edit the default value and then after a restart
Titanium.App.Properties.getString("userName");
returned the value as it should,
i hope this helps you =)
Answer to the Comment
I'm glad i could help you =)
Yes you can Use an focus EventHandler like this :
textfield.addEventListener("focus",function(){
textfield.value = "Test";
});
Beside that , are you using the identical Textfield for both windows ? like
var Textfield = Ti.Ui.createTextField({...});
and add it to 2 different windows ?
win1.add(Textfield);
win2.add(Textfield);
That led for me to Problems with labels in TableViewRows, using an identical Row 2 times in the TableView
The Text displayed only on 1 Label, sometimes it switched the Labels
I think you can't add one and the same titanium object to more then one other object
Maybe that could be the culprit,
dunno just an idea =)

Categories