Can I access properties of a scope object within itself using "this"? - javascript

I need to get the total of the following example:
$scope.fees = {
basic: 1,
premium: 2,
total: this.basic + this.premium
}
Why won't this work? It says this is undefined. Is there a way to achieve this without having to write out total: $scope.fees.basic + $scope.fees.premium.
I'd love if there was a way to shorten it.
EDIT: I'd actually have to add the total property outside of $scope.fees. $scope.fees.total = ...

You can use function ..
Hello {{ total() }}
function FeeController($scope) {
$scope.fees = {
basic: 1,
premium: 2,
};
$scope.total = function() {
return $scope.fees.basic + $scope.fees.premium;
};
}

Why this.basic doesn't work
this is evaluated in the context of the function that contains this statement. So this doesn't refer to the $scope.fees object, but to the controller.
Why total : $scope.fees.basic + $scope.fees.premium doesn't work either
At the moment that the expression $scope.fees.basic + $scope.fees.premium is evaluated the $scope.fees object doesn't exist yet, because you're in the middle of creating it. Therefore it will result in an error like "Cannot read property basic of undefined".
How to solve this
There isn't any solution other than what you've already found that results in the behaviour you want, so unfortunately you'll have to stick with it.

You might consider using the "controller as class" pattern which lessens your dependency on the $scope. You could do something like this:
app.controller('FeeCtrl', function () {
this.basic =1;
this.premium =2;
this.total = this.basic + this.premium;
});
Then you can inject this controller right into your dom:
<div ng-controller="FeeCtrl as fee">
{{ fee.total }}
</div>
There are more detailed instructions here
http://toddmotto.com/digging-into-angulars-controller-as-syntax/

Related

Is it possible to destructure an object with default key names [duplicate]

I'd like to do something like this:
const vegetableColors = {corn: 'yellow', peas: 'green'};
const {*} = vegetableColors;
console.log(corn);// yellow
console.log(peas);// green
I can't seem to find or figure out how to do this but I really thought I had seen it done somewhere before! :P
NOTE: I'm using Babel with stage set to 0;
CONTEXT: I'm trying to be drier in JSX and not reference this.state or this.props everywhere. And also not have to keep adding properties to destructure if the data changes.
I think you're looking for the with statement, it does exactly what you are asking for:
const vegetableColors = {corn: 'yellow', peas: 'green'};
with (vegetableColors) {
console.log(corn);// yellow
console.log(peas);// green
}
However, it is deprecated (in strict mode, which includes ES6 modules), for good reason.
destructure all properties into the current scope
You cannot in ES61. And that's a good thing. Be explicit about the variables you're introducing:
const {corn, peas} = vegetableColors;
Alternatively, you can extend the global object with Object.assign(global, vegetableColors) to put them in the global scope, but really, that's worse than a with statement.
1: … and while I don't know whether there is a draft to allow such things in ES7, I can tell you that any proposal will be nuked by the TC :-)
I wouldn't recommend it, but you can use eval() to accomplish something similar:
vegetableColors = {corn: 'yellow', peas: 'green'};
function test() {
for ( let i=0; i < Object.keys(vegetableColors).length; i++ ) {
let k = Object.keys(vegetableColors)[i];
eval(`var ${k} = vegetableColors['${k}']`);
}
console.log(corn); // yellow
}
test();
console.log(corn); // undefined (out of scope)
I think you're looking for:
const {corn, peas} = vegetableColors;
Live on Babel's REPL
If Pointy's right that you're asking how to do this without knowing the names corn and peas, you can't with destructuring assignment.
You can at global scope only, using a loop, but I'm sure you don't want to do this at global scope. Still, just in case:
// I'm sure you don't really want this, just being thorough
Object.keys(vegetableColors).forEach((key) => {
Object.defineProperty(this, key, {
value: vegetableColors[key]
});
});
(Throw enumerable: true on there if you want these pseudo-constants to be enumerable.)
That works at global scope because this refers to the global object.
I came upon a situation where the object was user-created and the code that uses the object was also user-created. Because the with statement is deprecated, I made my own, using eval to destructure the entire object and call the function that uses the destructured object. Below is a working example.
const vegetableColors = { corn: 'yellow', peas: 'green' };
function with2(obj, func) {
eval(`
var { ${Object.keys(obj).join(",")} } = obj;
(${func.toString()})()
`);
}
/*
with(vegetableColors) {
console.log(corn);
console.log(peas);
}
*/
with2(vegetableColors, function() {
console.log(corn);
console.log(peas);
})
Let me show you my solution to the problem. I don't agree with those who think that destructuring object properties into local scope without specifying their names is bad idea. For me, this feature, if implemented, would be helpful. This would make our code shorter, and improve code maintenance by making it easy to change property names without changing the processing code. After all, there is the extract() function in PHP that does the same thing. Are PHP developers wrong?
My solution is not ideal as it uses eval but it is one liner and it works. Perhaps in the future we will have a solution from JavaScript developers.
function extract(o)
{
var result = [];
for(var key in o)
if(o.hasOwnProperty(key)) {
var item = 'var ' + key + '=' + JSON.stringify(o[key]);
result.push(item);
}
return result.join(';');
}
var vegetableColors = { corn: 'yellow', peas: { unripe: 'green', ripe: 'yellow' } };
eval(extract(vegetableColors));
console.log(corn); // yellow
console.log(peas); // {unripe: "green", ripe: "yellow"}

Trying to make a function that takes in variable names as strings

New to Js, sorry if this is an obvious one.
I have some strings in my code that correspond to the names of variables. I'd like to put them into a function and have the function be able to make changes to the variables that have the same names as the strings.
The best example is where this 'string' is passed through from a data tag in html, but I have some other situations where this issue appears. Open to changing my entire approach too is the premise of my question is backwards.
<html>
<div data-location="deck" onClick="moveCards(this.data-location);">
</html>
var deck = ["card"];
function moveCards(location){
location.shift();};
Thanks!
A script should not depend on the names of standalone variables; this can break certain engine optimizations and minification. Also, inline handlers are nearly universally considered to be pretty poor practice - consider adding an event listener properly using Javascript instead. This will also allow you to completely avoid the issue with dynamic variable names. For example:
const deck = ["card", "card", "card"];
document.querySelector('div[data-location="deck"]').addEventListener('click', () => {
deck.shift();
console.log('deck now has:', deck.length + ' elements');
});
<div data-location="deck">click</div>
I think this can technically be done using eval, but it is good practice to think more clearly about how you design this so that you only access objects you directly declare. One example of better design might be:
container = {
obj1: //whatever this object is
...
objn:
}
function applyMethodToObject(object_passed){
container[object_passed].my_method();
}
I'm not sure I 100% follow what you're trying to do, but rather than trying to dynamically resolve variable names you might consider using keys in an object to do the lookup:
const locations = {
deck: ['card']
}
function moveCards (location) {
// if 'deck' is passed to this function, this is
// the equivalent of locations['deck'].shift();
locations[location].shift();
};
Here's a working demo:
const locations = {
deck: ['card 1', 'card 2', 'card 3', 'card 4']
};
function move (el) {
const location = el.dataset.location;
const item = locations[location];
item.shift();
updateDisplay(item);
}
// update the display so we can see the list
function updateDisplay(item) { document.getElementById('display').innerHTML = item.join(', ');
}
// initial list
updateDisplay(locations['deck']);
#display {
font-family: monospace;
padding: 1em;
background: #eee;
margin: 2em 0;
}
<div data-location='deck' onclick="move(this)">click to shift deck</div>
<div id="display">afda</div>
When you assign a value to an object in javascript you can access with dot or array notation. IE
foo = {};
foo.bar = "bar";
console.log(foo.bar);
console.log(foo["bar"]);
Additionally, global variables are added to the window object, meaning deck is available at window["deck"] or window[location] in your case. That means your moveCards function could do:
function moveCards(location) {
// perform sanity checks since you could change data-location="foo"
// which would then call window.foo.shift()
if (window[location]) {
window[location].shift();
}
}
That said, this probably isn't a great approach, though it's hard to say without a lot more context.

Vue.js: How can I update a single item in a "list" - computed property?

I'm trying to determine the best pattern to solve the following:
I want to show a list of people in a specific department
I made the department index a regular, reactive Vue property
I made the list of people in the department a computed property
Now:
My backend (a Mac app) can dispatch a "Person at index changed" event and I must reload the name of a single person.
But: the person property is an item in a computed property (i.e. "people", see code below).
How can I update the name of a person in the list list of people, which in turn is a computed property (although it is "computed" from the departmentIndex + backend call)?
I assume that I have a mistake in my original setup. Maybe the people list should not be a computed property in the first place?
Here is the code:
function pretendUpdateEventFromBackend() {
var event = new CustomEvent('PersonUpdated', {detail:{index:1, name:'Jimmy'}});
document.dispatchEvent(event);
}
var backend = {
// The actual backend is a Mac app with an embedded WebView...
listPeopleInDepartment(departmentIndex) {
return [
{name:'John'},
{name:'James'},
{name:'Jane'}
];
}
}
var app = new Vue({
el: '#app',
data: {
message:'',
departmentIndex:0,
},
computed: {
people() {
return backend.listPeopleInDepartment(this.departmentIndex);
}
},
created() {
const me = this;
document.addEventListener('PersonUpdated', function(e){
me.message += 'Updated ';
var personIndex = e.detail.index;
var newName = e.detail.name;
// How can I update person in the list of computed people here?
// Or how can I force a reload of the people list?
me.message += 'Person at: ' + personIndex + ' new name: ' + newName + "\n";
});
},
});
Html:
<button onclick="pretendUpdateEventFromBackend()">Trigger Update Event from Backend</button>
<div id="app">
<div class="person" v-for="person in people">
Name: {{ person.name }}
</div>
<pre>{{message}}</pre>
</div>
EDIT:
Try it on jsbin: http://jsbin.com/guwezoyena/1/edit?html,js,output
I just thought I should give you some further insight on your setup:
If listPeopleInDepartment is an ajax call in your actual code, this might be a bad pattern. See, Vue will identify every property (including other computed properties) that is used within a computed property and make it sure to re-compute it whenever any of them changes. In your case, this would mean whenever departmentIndex changes it'll recompute people.
Here's why it might be problematic, since you must return the result of the ajax request, it can't be asynchronous so it'll be a blocking call that would render the page unresponsive while it runs. Also, let's say you were viewing department 1, then you change to 2, if you go back to 1 it'll have to reload the people in department 1 because it's not stored anywhere.
You should probably implement some caching strategy and have ajax loads only when that data isn't already available, also in a non blocking fashion. You could achieve this with an array that stores the arrays of peopleInDepartment indexed by department id, and a watcher for departmentIndex that would do something like this:
function (newValue){
if (!this.peopleCache[newValue]){
var that = this;
//Load it via ajax
loadPeopleInDepartment(newValue, function(result){
that.peopleCache[newValue] = result;
});
}
}

Why is length of array not displaying in browser?

Code:
initialize: function() {
this.todos = [
{id: 100, text: 'Rich'},
{id: 200, text: 'Dave'}
];
},
activeTodos: function() {
this.todos = this.todos.length(function() {
return this.todos;
});
this.emitChange();
}
<p>Todo's Remaining: {this.activeTodos} </p>
activeItems: function(){
this.context.executeAction(activeToDosAction);
},
Explanation:
I am trying to print out the size of the array to the browser window (this can be seen in the <p> tags within the code). So far nothing is displaying and I cant figure out why. activeTodos should be calling the length of todos.
i can post more code if people require it. i am using reactjs hence the { } brackets within HTML
There are a couple of weird things there. If you only need to display the length of this.todos you can do something like this:
<p>Todo's Remaining: {this.todos.length} </p>
I think the problem here is that you pass a function to length. That is not required.
Your code should work if you change it to:
activeTodos: function() {
return this.todos.length;
}
I'm not sure what your function emitChange should be doing, so I omitted it for now in the code example. If you need it to be called, put it before the return as that is the last thing that will run in your function.
First, to access the length of an array you just have to do
myArray = [1, 2, 3];
myArray.length; //3
Moreover you have 2 fonctions : initialize and activeTodos where you use this.todos = ...
Without further explanations I assume these variables created with the keyword 'this' inside 2 different functions are out of score.
Verify that your this.todos refers to the same thing.
You do not need active.Todos() as .length is a built in function by itself.
Just do
Todo's Remaining: {this.todos.length}
Cheers.

Unable to call Object.keys in angularjs

I'm using a UI.Bootstrap accordion and I've defined my heading like so:
<accordion-group ng=repeat="(cname, stations) in byClient">
<accordion-heading>
{{ cname }} <span class="pull-right"> {{ Object.keys(stations).length }} Stations</span>
</accordion-heading>
When that displays the Object.keys(stations).length resolves to nothing. If I put that same length call in my controller I get back the expected count. Is there something preventing the method call from working in AngularJS?
The rest of the accordion that uses stations acts as expected, so I know that it's being populated properly. The byClient data structure basically looks like so:
{
"Client Name" : {
"Station Name": [
{...},
{...}
]
}
}
Yes, That is because Object is a part of window/global and angular cannot evaluate that expression against the scope. When you specify Object.keys in your binding angular tries to evaluate it against the $scope and it does not find it. You could store the reference of object.keys in some utility in rootScope and use it anywhere in the app.
Something like this:-
angular.module('yourApp',[deps...]).run(function($rootScope){
//Just add a reference to some utility methods in rootscope.
$rootScope.Utils = {
keys : Object.keys
}
//If you want utility method to be accessed in the isolated Scope
//then you would add the method directly to the prototype of rootScope
//constructor as shown below in a rough implementation.
//$rootScope.constructor.prototype.getKeys = Object.keys;
});
and use this as:-
<span class="pull-right"> {{ Utils.keys(stations).length }} Stations</span>
Well this will be available to any child scopes except for isolated scopes. If you are planning to do it on the isolated scope (eg:- Isolated scoped directives) you would need to add the reference of Object.keys on the scope, or as you expose a method on the scope which will return the length.
Or better yet , create a format filter to return the keylength and use it everywhere.
app.filter('keylength', function(){
return function(input){
if(!angular.isObject(input)){
throw Error("Usage of non-objects with keylength filter!!")
}
return Object.keys(input).length;
}
});
and do:-
{{ stations | keylength }}
Demo
Use the function to determine the number of object properties:
$scope.keyLength = function (obj) {
return Object.keys(obj).length;
}
and use:
{{ keyLength(myObj) }}
I think filters are the most AngularJS way of handling structures in template code:
angular.module('app.filters').filter('objectKeysLength', [function() {
return function(items) {
return Object.keys(items).length;
};
}]);
angular.module('app.filters').filter('objectKeys', [function() {
return function(item) {
if (!item) return null;
var keys = Object.keys(item);
keys.sort();
return keys;
};
}]);
In case someone searches for angular 2 and higher solution. It now hat keyvalue pipe, which can be used to interate over objects
I could not get any of the other answers to work in AngularJS 1.6. What worked for me
using $window to acccess Object.keys like this $window.Object.keys({ 'a': 1, 'b': 2 })
Here's a solution that worked for me :
export class xyzcomponent{
Key = Object.keys;
}
Now in the component html file, you can use something like that :
<li *ngFor="let i of Key(stations)">.........</li>

Categories