Angularjs + call methods by string - angularjs

I have dynamically generated input elements with unique Id. I need to bind click event for each input element which will perform different action.
Is there any better way to achieve this?

Assuming you mean call methods on the controller scope dynamically, you want something like this:
$scope.callFn = function(fn) {
$scope[fn]();
}
then
<input ng-click="callFn(foo)" />
but you really need a lot more detail in your question to give a better answer.

Related

ng-repeat how to handle a button element separately

I am using ng-repeat to populate data in a table so far so good. What i am trying to accomplish is to use a button and change it's text according to the userId (cust.id). I am trying to understand how to use a $scope inside a repeat method and modify it separately from the other elements.
On the following demo when i click to button with (userid value = 1) then i would like to change the specific button text and NOT every button in my ng-repeat
<button ng-click="perLineText(cust.id)">{{buttonText}}</button>
Live Demo
I am trying to figure out how to handle specific elements in my ng-repeat. Any help much appreciated.
You can do that by just using the this in you controller function instead of $scope.
$scope.perLineText = function(customerId){
if (customerId === 1) {
this.buttonText = 'Stop';
};
See the updated fiddle http://jsfiddle.net/u5swjwv1/
On a click callback this points to the scope of nested repeat element. If you use $scope you are actually referring to the parent scope object.
Look at this explanation too 'this' vs $scope in AngularJS controllers
A good way to handle this problem is using $index. Just pass it when calling your perLineText($index) function so you know which row you are in. Now you can simply work with the appropriate index object in your array which might my $scope.customers[index]
You can also use the ng-bind="perLineText(cust.id)" instead of the {{buttonText}} so the ng-click will toogle something and the perLineText will return Start or Stop.

how to always use a 'set-method' to change the property of an object used as ng-model

in the controller I have:
$scope.foo = new Foo('test'); // foo.property now 'test'
in the view I have
<input type="text" ng-model="foo.property" />
But I don't really want foo.property to be changed directly.
I want it to be changed by calling a 'set method' for it, like
foo.setProperty('new-value');
So I know I could do something with watch like so:
$scope.$watch('myObject.property', function(newValue,oldValue) {
myObject.setProperty(newValue);
}
But I'll have this situation many times over again, so my question is if I can do this more automatically for every Foo?
or: How can I best implement something in Angular so that every time someFoo.property is used as a ng-model it is changed by calling someFoo.setProperty(newValue)?
You can call the setter method on blur event:
<input type type="text" ng-model="data.property" ng-blur="foo.setProperty(data.property)">
or use ng-change="foo.setProperty(data.property)"
You basically want the foo.property model to not be changed due to User Interaction, yes? Why don't you disable the input box using HTML, which will make sure no user interaction ever changes the input value, and then use $watch? $watch is the perfect way to handle what you require. And as long as it is not a deep watch, there's no performance concern either.

how can I bind an input to a property deep within a complicated data structure?

I have an angular-rails resource with a property that consists of irregular data that is potentially quite complicated-- something like:
{ foo: [ { bar: 'baz', lol: [ { 'omg': ... etc
I built a directive which takes this data and drills down into it, dynamically rendering form fields for each object... I've got the data displaying perfectly, however the piece of the puzzle that's missing is, how can I take advantage of Angular's binding so that changing the value on the form input will actually update that attribute in the model?
Originally I was thinking this should be simple, as my code drills through the data structure, it can just be maintaining a path, so I'd end up with something like: 'myObject.foo.bar'
Then I could just pass that to the form input's ng-model attribute...... however, I could not get angular to recognize ng-model="path" where $scope.path = "myObject.foo.bar"... ng-model="{{path}}" did not work either.
My directive is using angular.forEach to drill down into this datastructure, and someone had mentioned to me that I should perhaps be using ng-repeat instead, but I wasn't sure if this is the correct way to go or not? I still feel like there should just be a way to do ng-model="path" and have that work...
Any guidance would be greatly appreciated.
To use dynamic property names, use array notation. I.e. myObject["foo"]["bar"]. Plunkr: http://plnkr.co/edit/W60F75?p=preview
Can you try setting an property on the scope with the value of the object itself and then refer it in the form element? Like below:
// In your script
$scope.obj = myObject;
// In your form
<input ng-model="obj.foo.bar" type="text" />

Angularjs reuse calculated values

I need to use a calculated value to hide elements and do other things in the template.
<button ng-hide="expensive()" ng-click="foo()">foo</button>
<button ng-show="expensive() && otherFunction()" ng-click="bar()">bar</button>
<span ng-show="expensive()">Bas</span>
This causes the excecution of expensive() multiple times for each $digest cycle.
I want to reuse its result, but it needs to be executed for each digest - just not multiple times per digest.
Is there any best practice for reuse function results that needs to be re-calculated each digest?
* update *
This function works with a huge object and its properties can be changed with a lot of input fields and sub-formulars on the page. It has multiple one to many relations. If I have to add events / ngChanges to each field and if I miss only one, this will not work correcly.
You didn't really give us enough information to give you the best option for your situation.
Very generally speaking, the best option is to have whatever interactions that cause the return value of expensive() to change update a $scope.property and just use that in your view.
In other words, don't use functions on scope in your view unless you're setting up a binding like ng-click or something. Instead, update properties on your scope whenever they need updating and just reference them directly.
Caution: This might tempt you to use $watch... don't do that. There are cheaper, more effective ways to trigger updates, like ngChange, or other such events.
You have a few options:
make expensive() a $q promise. Angular templates understand $q promises and resolve them accordingly
store the value when running the function, if the value exists return it first thing in the function. You can add a flush param to it in case you want to flush the cached value.
If the values truly never change once computed, you could put them in a resolve
If these values are the result of some DOM work, create a directive to do this and $emit actual changes to the parent scope.
Try this.
<button ng-hide="result" ng-click="foo()">foo</button>
<button ng-show="result && otherFunction()" ng-click="bar()">bar</button>
<span ng-show="expensive()">Bas</span>
Controller:
$scope.expensive = function() {
... do stuff ...
$scope.result = ...;
return $scope.result;
}

Having a set of checkboxes map to a nested array

I am working on a SPA that pulls in customer data from one $resource call, and gets some generic preference data from another $resource call.
The preference data is sent as an array, which I want to use to populate a series of checkboxes, like so:
<div ng-repeat="pref in fieldMappings.mealPrefs">
<input type="checkbox"
id="pref_{{$index}}"
ng-model="customer.mealPrefs"
ng-true-value="{{pref.name}}" />
<label class="checkbox-label">{{pref.name}}</label>
</div>
When a user clicks one or more checkboxes, I want the values represented in that array of checkboxes to be mapped to an array nested inside a customer object, like so:
.controller( 'AppCtrl', function ( $scope, titleService, AccountDataService ) {
// this is actually loaded via $resource call in real app
$scope.customer = {
"name": "Bob",
"mealPrefs":["1", "3"]
};
// this is actually loaded via $resource call in real app
$scope.fieldMappings.mealPrefs = [
{'id':"1", 'name':"Meat"},
{'id':"2", 'name':"Veggies"},
{'id':"3", 'name':"Fruit"},
{'id':"4", 'name':"None"}
];
});
I have tried setting up ng-click events to kick off functions in the controller to manually handle the logic of filling the correct part of the customer object model, and $watches to do the same. While I have had some success there, I have around 2 dozen different checkbox groups that need to be handled somehow (the actual SPA is huge), and I would love to implement this functionality in a way that is very clean and repeatable, without duplicating lots of click handlers and setting up lots of $watches on temporary arrays of values. Anyone in the community already solved this in a way that they feel is pretty 'best practice'?
I apologize if this is a repeat - I've looked at about a dozen or more SO answers around angular checkboxes, and have not found one that is pulling values from one object model, and stuffing them in another. Any help would be appreciated.
On a side-note, I'm very new to plunkr (http://plnkr.co/edit/xDjkY3i0pI010Em0Fi1L?p=preview) - I tried setting up an example to make it easier for folks answer my question, but can't get that working. If anyone wants to weigh in on that, I'll set up a second question and I'll accept that answer as well! :)
Here is a JSFiddle I put together that shows what you want to do. http://jsfiddle.net/zargyle/t7kr8/
It uses a directive, and a copy of the object to display if changes were made.
I would use a directive for the checkbox. You can set the customer.mealPrefs from the directive. In the checkbox directive's link function, bind to the "change" event and call a function that iterates over the customer's mealPrefs array and either adds or removes the id of the checkbox that is being changed.
I took your code and wrote this example: http://plnkr.co/edit/nV4fQq?p=preview

Resources