Beginning here, I am attempting to sum a specific property for an array of objects by using the javascript reduce function within angular bindings.
Component
totals: total[] = [{itemCount:1},{itemCount:2},{itemCount:3}, {itemCount:4}];
HTML
<div>{{ totals.map(t => t.itemCount).reduce((a, b) => a + b, 0) }}</div>
With the above, I'm getting the Error: "Bindings cannot contain assignments"
stackblitz mockup
Thanks in advance
EDIT
I have marked the answer provided by #quirimmo as correct but I also wanted to address #jo_va answer.
I felt providing a method for angular inside my component is the right approach #quirimmo, however in my case, the component was JIT compiled on client request and at this time, I'm still uncertain as how to add that method dynamically during that component construction. This lead me to the Expression approach inside my HTML and knowingly avoiding #jo_va's answer of best practice.
My resolution was to pass along a service as an object to the component and place the method within that service for reference.
Thanks for the help S.O.
Define a function inside your component which returns the reduced value and inside the binding call that function of the component:
const totals = [{itemCount:1},{itemCount:2},{itemCount:3}, {itemCount:4}];
console.log(totals.reduce((acc, val) => acc += val.itemCount, 0));
Your reduce function works well. However it's a bad practice to use expressions in angular bindings.
Move your logic to your component and call a method or getter.
Map and reduce return a new array. This is where your error comes from.
You can use a getter like this https://stackblitz.com/edit/angular-whrfxp. Template shouldn't contain calculations.
totals.reduce((a, b) => a += (b. itemCount), 0) works
Related
I am working with this piece of code in React where I am mapping over the array "householdNotes", passing "nt" to each component being created. "staffUser" is a hook/variable being stored elsewhere in the code. I would like to pass this value down to the components as a prop. Is this possible in any way?
householdNotes.map(nt => <NoteCard note={nt} user={staffUser} />
)
An option I thought about was passing it through the slug and then grabbing it from match.params, but I'd like to avoid that if possible because staffUser can contain spaces. I don't mind passing the static value of "staffUser" if that also makes this doable. Any ideas?
Thanks for your help, friends!
A little background on the problem
I've built a custom system to automatically watch some "store" properties that comes from a JSON of a nosql database. Nothing too complicated except the nesting (required for several reasons not discussed here) of objects.
The data structure looks like this:
{
store: {
objA: {
objB: {
prop1: 'some value'
}
}
}
}
However, since it's a nosql database that provide that store property, objB can just be NOT present after the load from the database.
Example of the template used
I have custom components that have props bound directly to that data store
<my-selector :value.sync="store.objA.objB.prop1">
</my-selector>
However, it crashes when "objB" is not present with the usual javascript error saying that it cannot get "objB" of undefined, but that is normal.
I'm trying to find a vuejs way to prepare the data for me.
Ideas
In order to counter that crash :
I can NOT use v-if in that case to mask the selector. Because that selector can be used even if the data is not yet set (example: for optional data).
I could fix the "load" function that gets the data from the database so it initialize the required properties (like objB) before assigning data to the data store. However, that would imply that my initial VueJS data object would contain these required properties as well. It's probably that I will use if I can't find any alternative solution, but I don't think that's the easiest way around because I would have to fix any incoming data before assignment.
My preferred choice would be to create a directive (or any other thing built in the template) that would add them for me if they are missing.
VueJS always evaluates the bindings value
I thought of that solution:
<my-selector v-autocreate="'store.objA.objB.prop1'" :value.sync="store.objA.objB.prop1">
</my-selector>
However the directive binding "v-autocreate" is not picked up first (checked with the debugger).
I did not find documentation relative of the order of load of directives or attributes.
I was also hoping to get all bindings of a node with the directive "bind"
function in order to NOT repeat the string, but it seems we can't get that information (I'm used to knockoutjs where we can pick all bindings assigned to a node in order to behave differently).
I would like to reach that goal but I'm not sure that it's possible (I would need something like a pre-bind / beforeBind event on directive haha) :
<my-selector v-autocreate :value.sync="store.objA.objB.prop1">
</my-selector>
Where v-autocreate would assure to do the vm.$set of the missing properties.
You could create a method that checks each property in an object path, creates it if it doesn't exist, then returns the value of the last property.
Example (not tested):
get(object, path) {
path = path.split('.')
let index = 0
const length = path.length
let val
while (object != null && index < length) {
let key = path[index++]
if(object[key] == null) {
this.$set(object, key, {})
}
object = object[key]
}
return val
}
Usage:
<my-selector :value="get(store,'objA.objB.prop1')">
</my-selector>
You may be interested in lodash's get function, which is what the code example is based on.
https://github.com/lodash/lodash/blob/master/get.js
I'm going through the Vue.js guide and I came across this paragraph. It says:
Note in the method we simply update the state of our app without touching the DOM
Basically the method simply reverses a string on click of a button. What I'm unable to understand is the concept of Updating the state of app without touching the DOM. What does the state mean here? Can somebody explain this in layman terms?
One of the cornerstones of Vue.js is its simply implemented two-way data binding. This means that when a state or data value is changed within the Vue object/instance, it is also synced and changed in the DOM and vice versa without having to manually update both.
In a pure Javascript situation such as:
function changeData() {
document.getElementById('data').innerHTML = "Second";
}
<div>
<span id="data">First</span>
</div>
<button onclick="changeData()">Change Value</button>
In this, we are directly manipulating the DOM to change the text value of the span element, however, with Vue's two-way data binding, only the instance's state/data must be changed for both to update.
If I really simplify it, then I would say that it's the data that's used for your application. Which includes, for example, your reversed string.
Or for example:
State: { isSwitchedOn: false }
UpdateStateFunc: (state, value) => state.isSwitchedOn = value;
So, we can update isSwitchedOn using UpdateStateFunc, but that doesn't mean we push that data to the DOM (ie. what's visible to the user). Making it visible to the user would be, perhaps, another function.
I have json data, an array of 50 objects representing people. Each one has parameters like id and lastName.
I load this into my controller via a resolve, EmployeeResolve, and into a variable _this.employees
I also load via $state params from a previous page a rowNumber variable that holds the ID of the record the user clicked on: _this.rowNum = $stateParams.id;let's say the id is 5.
I would like to assign to a variable now the object number 5 (for want of a better way of explaining) so that in my HTML I can bind to it as in {{controller.lastName}}
What's the syntax for getting the 5th item out of employees?
UPDATE
After several helpful comments and answers, I've gotten this far (people are now packages):
_this.recordNum = Number($stateParams.id);
_this.packages = PackagesResolve;
_this.currentPackage = _this.packages.filter(function(pkg) {
return pkg.id === _this.recordNum;
});
$log.debug('package from filter', _this.currentPackage[0].status);
Note though, I expected after all this for _this.currentPackage to contain an object, so I could simply bind to its props in the html as in currentPackage.last_name But it does not. It's a resource and I need to use the above _this.currentPackage[0].status in the log statement to get anything. And that's not going to allow binding.
A colleague suggested modifying my resolve as such
PackagesResolve: function($log, MockDataFactory) {
return MockDataFactory.query({filename: 'packages'}).$promise.then(function(response) {
return response;
});
}
Adding the whole $promise.then part. No real difference.
To reiterate what I am trying to do:
PackagesResolve is getting a json array of 50 objects. I want to be able to get the CURRENT object when its row in a table of that json is clicked.
And no, #jdvp it's not a duplicate of that other post at all. I need to do this with Angular, not jquery or straight js.
If I'm understanding your issue correctly: the object returned by resolve is the resolved promise. The "data" of the resolved promise, which in this case would be the expected array of people info, is stored inside resolve.data. So for e.g. you have EmployeeResolve, you can reference the array and store it using:
Editing based on comments:
// Assuming you've done all error checking...
_this.employees = EmployeeResolve.data;
// Now _this.employees has the array of people info.
$scope.controller = {};
$scope.controller.variableName = _this.employees[$stateParams.id];
// Now, you can access your object in your template using controller.variableName.
Now although I wouldn't recommend writing code like that in your final version, I'm sure you get the gist. ;)
Additional notes: The reason I'm creating an empty object and storing it as controller on the scope is because your question stated it. I am assuming you have your own reasons for wanting to namespace your variable inside of controller.
Hope this helps!
I'm trying to write a generic filter function like this:
var filterGenerator = function(ids) {
return function (o) {
return _.contains(ids, o.id); // using lodash _.contains
}
}
I expose this on the scope like so:
$scope.myFilter = filterGenerator($scope.listOfIds);
It works on initial page load in the partial (using o in objects | filter:myFilter), but when $scope.listOfIds changes, I would expect the filtered values to change as well - however, they do not. I suspect that when I call filterGenerator($scope.listOfIds), the returned lambda is getting a separate scoped version of $scope.listOfIds that never changes, but I'm unsure how to get around this. How would I go about making sure that the lambda sees any changes to the list?
You're right, the filter is taking the value of $scope.listOfIds at the time it is run, and then won't update because that line of code doesn't run again.
You should have the filter take listOfIds as an argument, then pass that argument in the view.
https://stackoverflow.com/a/16227326
ng-repeat="o in objects | filter:myFilter:listOfIds"
After working with Mr. Baritonehands, we decided on a better solution: We can set a particular filter on the fly like this:
$scope.filterFn = function(o) {
return filterGenerator($scope.listOfIds)(o);
}
That way the generated function always sees what's current on the scope.