Using ui-select2 with ng-repeat does not set the model correctly - javascript

First, some background:
When using ui-select2, you have to supply an initSelection function in the select2 config object. If you don't, you'll get an error (but the functionality won't be affected. Everything will still work as expected).
To illustrate, see this plunkr. When you select an item from the dropdown menu, it'll work, but you'll get the following error:
Error: cannot call val() if initSelection() is not defined
Adding an empty function to initSelection fixes the error. You can uncomment it in the above plunkr to see that.
The problem:
When using ui-select2 in conjunction with ng-repeat, it just doesn't update the model.
Controller:
// for this demo, `users` is injected into the controller
$scope.users = users.slice(0, 2);
$scope.select2Config = {
placeholder: 'Select User...',
query: function ( options )
{
// `users` in this demo is injected into the controller.
// in the real world this would be an ajax request
options.callback({ results: users });
},
// Without initSelection, I get the above error.
// Regardless, the model isn't updated.
initSelection: angular.noop,
formatSelection: select2format,
formatResult: select2format,
};
function select2format ( user )
{
return user.first + ' ' + user.last;
}
View:
<ul>
<li ng-repeat="user in users">
<input type="text" ng-model="user" ui-select2="select2Config">
</li>
</ul>
When selecting an item from the dropdown list, the model isn't updated. If there's no initSelection in the config I get the above error, but adding it still doesn't update the model.
Here's a plunkr demonstrating the above.
The question:
How do I make ui-select2 update the model in an ng-repeat?

Try using inheritance:
http://plnkr.co/edit/jyJaYU4DQX1LROD6nQaw?p=preview
Also I you were calling "user in users" on the ng-repeat but then also setting the ng-model to "user". I am not sure why.
As to initSelection behavior, I do not have an answer.
edit
I updated the plunker. The ng-repeat directive creates a new scopes - true, but all of its children (or repeated items) are siblings. You had bound "user" (that was being defined/transcluded by ng-repeat) to the ng-model of each select (and expecting that the "users" array would be updated if you changed the model).
I believe ng-repeat is a one way binding top down
Therefor what I showed was correct but admittedly lazy. I bound both selects to the same model but I just as easily bound them to seperate properties on the "selected" object using $index.
Point is: if you want two way binding then write a new directive, but it is easier to do something similar to what I show.
PS: In practice I populate a "lookups" object that has some arrays for the select/pull-downs and use a seperate object called "user" that holds what the user has selected. This is pretty much pulled right out of the (older) documentation.

Related

How to use reactive forms inside ng-template

I have just started with Angular 4 and I need to develop a CRUD grid, where the user can add, edit or delete rows.
During my research I found this article where it shows how to create the grid and also the actions: Angular 4 Grid with CRUD operations.
Looking at his code, what called my attention was the way he is using the ng-template to toggle between edit/view mode.
<tr *ngFor="let emp of EMPLOYEES;let i=idx">
<ng-template [ngTemplateOutlet]="loadTemplate(emp)" [ngOutletContext]="{ $implicit: emp, idx: i }"></ng-template>
</tr>
On the article he uses template driven forms to edit the row. However, I was trying to change to reactive forms.
In my attempt to do that, I tried to replace the [(ngModel)] to formControlName and I got some errors. My first attempt I tried to add the [formGroup] at the beginning of the template html inside form element. But when I tried to run and edit the row, I got the following error:
Error: formControlName must be used with a parent formGroup directive. You'll want to add a formGroup directive and pass it an existing FormGroup instance (you can create one in your class).
When I tried to move the [formGroup] inside the ng-template it works, however I was not able to bind the value to the fields and I had to set the values in the loadTemplate function:
loadTemplate(emp: Employee) {
if (this.selemp && this.selemp.id === emp.id) {
this.rForm.setValue({
id: emp.id,
name: emp.name
});
return this.editTemplate;
} else {
return this.readOnlyTemplate;
}
}
This works and show the values inside the fields in a read only mode :(
Here is the Plunker of what I have got so far.
How can I make a reactive form work with ng-template and how to set values to edit the entries?
Any help is appreciated! Thanks
Actually your form is not readonly, you are just constantly overwriting the input you are entering. Since you are having a method call in template (which is usually not a good idea), loadTemplate gets called whenever changes happen, which in it's turn means that
this.rForm.setValue({
id: emp.id,
name: emp.name
});
gets called over and over whenever you try and type anything. We can overcome this with instead setting the form values when you click to edit. Here we also store the index so that we can use it to set the modified values in the correct place in array, utilizing the index could perhaps be done in a smarter way, but this is a quick solution to achieve what we want.
editEmployee(emp: Employee) {
this.index = this.EMPLOYEES.indexOf(emp)
this.selemp = emp;
this.rForm.setValue({
id: emp.id,
name: emp.name
});
}
so when we click save, we use that index...
saveEmp(formValues) {
this.EMPLOYEES[this.index] = formValues;
this.selemp = null;
this.rForm.setValue({
id: '',
name: ''
});
}
Your plunker: https://plnkr.co/edit/6QyPmqsbUd6gzi2RhgPp?p=preview
BUT notice...
I would suggest you perhaps rethink this idea, having the method loadTemplate in template, will cause this method to fire way too much. You can see in the plunker, where we console log fired! whenever it is fired, so it is a lot! Depending on the case, this can cause serious performance issues, so keep that in mind :)
PS. Made some other changes to code for adding a new employee to work properly (not relevant to question)

Angularjs: Ng-Model and Ng-Change giving undefined in console

I have a dropdown that is defined using angular's ng-options syntax. I also have ng-model on the element and I am trying to get the selected model to output to the console. I have read a lot of different SO questions, but I just keep getting undefined. When I have the model defined in a service on rootScope then I get null.
Not sure what is going on, but it seems like the object isn't getting into the ng-model.
In the view:
<select ng-model="selected" ng-change="updateCategory()"
ng-options="conference.Name for conference in Adminconferences
| orderBy:['Name'] "></select>
In the controller:
$scope.updateCategory = function () {
console.log($scope.selected);
}
Update: I realized that maybe if i include what i am trying to do then maybe I can get better responses and have a better question.
This dropdown list is located in the header of my site. I want the user to be able to select which conference they want to manage. Based on that, I want all of the data in the site to be based on the conference's Id. I am not sure what the angular way of doing this is. I think i need to use rootScope and just reference it whenever i need the id for data calls. Is this acceptable?
I think you need to initialize $scope.selected in the controller before you use it inside function.
Initialize it like this:
$scope.selected = 0;
and then use it inside the function.

Angular JS delete method to Sinatra

I'm currently trying to work out how to delete a record using Angular JS and Sinatra. At present the code throws an internal 500 server error. I've looked around online for tutorials of how to do this correctly but couldn't find anything that relevant.
My code is as follows below:
app.rb
#Delete download
#not working...
delete '/view1/downloaddelete' do
ng_params = JSON.parse(request.body.read)
#download = Download.get(ng_params)
if #download.destroy
{:success => "ok"}.to_json
#log to console if download delete is successful
puts "download delete successful"
else
halt 500
#log to console if download delete is unsuccessful
puts "download delete unsuccessful halt 500"
end
end
downloads.html
<p>Manage downloads</p>
<ul>
<li ng-repeat="item in items">Title: {{item.title}}, ID: {{item.downloadID}}<a ng-controller="MyCtrl3" ng-click="deletedownload({{item.downloadID}})">Delete</a></li>
</ul>
controllers.js
//not working...
app.controller('MyCtrl3', ['$scope', '$http', function ($scope, $http) {
$scope.downloaddeleteid = {};
$scope.deletedownload = function() {
$http({
method : 'DELETE',
url : '/view1/downloaddelete',
data : $scope.downloaddeleteid
});
}
console.log($scope.deletedownload);
}]);
Any help would be much appreciated please.
You don't need a second controller to do this (MyCtrl3).
Change your link to:
<a ng-click="deletedownload(item.downloadID)">Delete</a>
and add this function to the $scope that this view binds to (the $scope that has the "items" list):
$scope.deletedownload = function(downloadID) {
$http({
method : 'DELETE',
url : '/view1/downloaddelete',
data : downloadID
});
}
Small tutorial:
Your view is associated with a specific $scope which you manipulate from the controller code. There, you define your "items" list and "deletedownload" function. For each item in the list, ng-repeat creates a new $scope that prototypally inherits from the parent $scope (the one mentioned before) all it's properties, plus it defines a new property called "item" which is one item from the list at the time.
When you write:
ng-click="deletedownload(item.downloadID)"
you essentially define a kind of "expression" which evaluates against the associated $scope. This means that the variables you can refer to (the lexical scope) must be properties of the $scope that the anchor DOM element is associated with. Since the anchor element is a child of the list item element, it is associated with the $scope created there. This $scope has a "deletedownload" member inherited from it's parent $scope and an "item" member created by ng-repeat.
I suggest you use Firefox with Firebug + AngScope (a tiny extension i wrote) to inspect $scopes behind DOM elements. You should also verify that HTTP calls to you backend contain the correct data.

Missing view.context or templateContext in an Ember event handler

I'm trying to push the object that populated a view into an array, but the reference is somehow getting lost. I've got an Ember view, with a defined eventManager:
FrontLine.NewProductButton = Em.View.extend({
tagName: 'button',
classNames: ['addtl_product',],
templateName: 'product-button',
eventManager: Ember.Object.create({
click: function(event, view) {
FrontLine.ProductsController.toggleProductToCustomer(event, view);
}
})
})
That view renders a bunch of buttons that are rendered with properties that come from objects in the ProductsController using the #each helper. That part works great. And when I click on any of those buttons, the click event is firing and doing whatever I ask, including successfully calling the handler function (toggleProductToCustomer) I've designated from my ProductsController:
FrontLine.ProductsController = Em.ArrayController.create({
content: [],
newProduct: function(productLiteral) {
this.pushObject(productLiteral);
},
toggleProductToCustomer: function(event, view){
FrontLine.CustomersController.currentCustomer.productSetAdditional.pushObject(view.context);
}
});
I'm trying to use that function to push the object whose properties populated that view into an array. Another place in my app (a simple search field), that works perfectly well, using pushObject(view.context). Here, however, all that gets pushed into the array is undefined. I tried using view.templateContext instead, but that doesn't work any better. When I try console.log-ing the button's view object from inside those functions, I get what I'd expect:
<(subclass of FrontLine.NewProductButton):ember623>
But either view.context or view.templateContext return undefined. How do I access the object I'm after, so I can add it to my array?
The simple answer is that it was one letter's difference:
view.content
or:
view.get('content')
provides the source object in that particular situation, rather than view.context.
(My only real challenge with Ember so far is that accessors for objects and properties vary so much from situation to situation, and there's no real documentation for that. Sometimes the object is at view.context, sometimes it's at view.content, sometimes _parentView.content, etc., etc. It would be awesome if there were a chart with the umpteen different syntaxes for accessing the same data, depending on which particular aperture you're reaching through to get it. I'm still discovering them...)

How to clear/remove observable bindings in Knockout.js?

I'm building functionality onto a webpage which the user can perform multiple times. Through the user's action, an object/model is created and applied to HTML using ko.applyBindings().
The data-bound HTML is created through jQuery templates.
So far so good.
When I repeat this step by creating a second object/model and call ko.applyBindings() I encounter two problems:
The markup shows the previous object/model as well as the new object/model.
A javascript error occurs relating to one of the properties in the object/model, although it's still rendered in the markup.
To get around this problem, after the first pass I call jQuery's .empty() to remove the templated HTML which contains all the data-bind attributes, so that it's no longer in the DOM. When the user starts the process for the second pass the data-bound HTML is re-added to the DOM.
But like I said, when the HTML is re-added to the DOM and re-bound to the new object/model, it still includes data from the the first object/model, and I still get the JS error which doesn't occur during the first pass.
The conclusion appears to be that Knockout is holding on to these bound properties, even though the markup is removed from the DOM.
So what I'm looking for is a means of removing these bound properties from Knockout; telling knockout that there is no longer an observable model. Is there a way to do this?
EDIT
The basic process is that the user uploads a file; the server then responds with a JSON object, the data-bound HTML is added to the DOM, then the JSON object model is bound to this HTML using
mn.AccountCreationModel = new AccountViewModel(jsonData.Account);
ko.applyBindings(mn.AccountCreationModel);
Once the user has made some selections on the model, the same object is posted back to the server, the data-bound HTML is removed from then DOM, and I then have the following JS
mn.AccountCreationModel = null;
When the user wishes to do this once more, all these steps are repeated.
I'm afraid the code is too 'involved' to do a jsFiddle demo.
Have you tried calling knockout's clean node method on your DOM element to dispose of the in memory bound objects?
var element = $('#elementId')[0];
ko.cleanNode(element);
Then applying the knockout bindings again on just that element with your new view models would update your view binding.
For a project I'm working on, I wrote a simple ko.unapplyBindings function that accepts a jQuery node and the remove boolean. It first unbinds all jQuery events as ko.cleanNode method doesn't take care of that. I've tested for memory leaks, and it appears to work just fine.
ko.unapplyBindings = function ($node, remove) {
// unbind events
$node.find("*").each(function () {
$(this).unbind();
});
// Remove KO subscriptions and references
if (remove) {
ko.removeNode($node[0]);
} else {
ko.cleanNode($node[0]);
}
};
You could try using the with binding that knockout offers:
http://knockoutjs.com/documentation/with-binding.html
The idea is to use apply bindings once, and whenever your data changes, just update your model.
Lets say you have a top level view model storeViewModel, your cart represented by cartViewModel,
and a list of items in that cart - say cartItemsViewModel.
You would bind the top level model - the storeViewModel to the whole page. Then, you could separate the parts of your page that are responsible for cart or cart items.
Lets assume that the cartItemsViewModel has the following structure:
var actualCartItemsModel = { CartItems: [
{ ItemName: "FirstItem", Price: 12 },
{ ItemName: "SecondItem", Price: 10 }
] }
The cartItemsViewModel can be empty at the beginning.
The steps would look like this:
Define bindings in html. Separate the cartItemsViewModel binding.
<div data-bind="with: cartItemsViewModel">
<div data-bind="foreach: CartItems">
<span data-bind="text: ItemName"></span>
<span data-bind="text: Price"></span>
</div>
</div>
The store model comes from your server (or is created in any other way).
var storeViewModel = ko.mapping.fromJS(modelFromServer)
Define empty models on your top level view model. Then a structure of that model can be updated with
actual data.
storeViewModel.cartItemsViewModel = ko.observable();
storeViewModel.cartViewModel = ko.observable();
Bind the top level view model.
ko.applyBindings(storeViewModel);
When the cartItemsViewModel object is available then assign it to the previously defined placeholder.
storeViewModel.cartItemsViewModel(actualCartItemsModel);
If you would like to clear the cart items:
storeViewModel.cartItemsViewModel(null);
Knockout will take care of html - i.e. it will appear when model is not empty and the contents of div (the one with the "with binding") will disappear.
I have to call ko.applyBinding each time search button click, and filtered data is return from server, and in this case following work for me without using ko.cleanNode.
I experienced, if we replace foreach with template then it should work fine in case of collections/observableArray.
You may find this scenario useful.
<ul data-bind="template: { name: 'template', foreach: Events }"></ul>
<script id="template" type="text/html">
<li><span data-bind="text: Name"></span></li>
</script>
Instead of using KO's internal functions and dealing with JQuery's blanket event handler removal, a much better idea is using with or template bindings. When you do this, ko re-creates that part of DOM and so it automatically gets cleaned. This is also recommended way, see here: https://stackoverflow.com/a/15069509/207661.
I think it might be better to keep the binding the entire time, and simply update the data associated with it. I ran into this issue, and found that just calling using the .resetAll() method on the array in which I was keeping my data was the most effective way to do this.
Basically you can start with some global var which contains data to be rendered via the ViewModel:
var myLiveData = ko.observableArray();
It took me a while to realize I couldn't just make myLiveData a normal array -- the ko.oberservableArray part was important.
Then you can go ahead and do whatever you want to myLiveData. For instance, make a $.getJSON call:
$.getJSON("http://foo.bar/data.json?callback=?", function(data) {
myLiveData.removeAll();
/* parse the JSON data however you want, get it into myLiveData, as below */
myLiveData.push(data[0].foo);
myLiveData.push(data[4].bar);
});
Once you've done this, you can go ahead and apply bindings using your ViewModel as usual:
function MyViewModel() {
var self = this;
self.myData = myLiveData;
};
ko.applyBindings(new MyViewModel());
Then in the HTML just use myData as you normally would.
This way, you can just muck with myLiveData from whichever function. For instance, if you want to update every few seconds, just wrap that $.getJSON line in a function and call setInterval on it. You'll never need to remove the binding as long as you remember to keep the myLiveData.removeAll(); line in.
Unless your data is really huge, user's won't even be able to notice the time in between resetting the array and then adding the most-current data back in.
I had a memory leak problem recently and ko.cleanNode(element); wouldn't do it for me -ko.removeNode(element); did. Javascript + Knockout.js memory leak - How to make sure object is being destroyed?
Have you thought about this:
try {
ko.applyBindings(PersonListViewModel);
}
catch (err) {
console.log(err.message);
}
I came up with this because in Knockout, i found this code
var alreadyBound = ko.utils.domData.get(node, boundElementDomDataKey);
if (!sourceBindings) {
if (alreadyBound) {
throw Error("You cannot apply bindings multiple times to the same element.");
}
ko.utils.domData.set(node, boundElementDomDataKey, true);
}
So to me its not really an issue that its already bound, its that the error was not caught and dealt with...
I have found that if the view model contains many div bindings the best way to clear the ko.applyBindings(new someModelView); is to use: ko.cleanNode($("body")[0]); This allows you to call a new ko.applyBindings(new someModelView2); dynamically without the worry of the previous view model still being binded.
<div id="books">
<ul data-bind="foreach: booksImReading">
<li data-bind="text: name"></li>
</ul>
</div>
var bookModel = {
booksImReading: [
{ name: "Effective Akka" },
{ name: "Node.js the Right Way" }]
};
ko.applyBindings(bookModel, el);
var bookModel2 = {
booksImReading: [
{ name: "SQL Performance Explained" },
{ name: "Code Connected" }]
};
ko.cleanNode(books);
ko.applyBindings(bookModel2, books);

Categories

Resources