angular.constant like an object take same ref - angularjs

Why if I define a constant object like
app.constant('TEST', { title: 'Hello' });
and I make this in controller
var x = TEST;
x.title = 'Hello World';
console.log(TEST);
The value of constant has been change?? I read angular's doc and it says that constant cannot change their value.

The value of the constant can't be changed if it is a primitive value (ie, 1, true, "appName"). However it is an issue with Angular 1.x that if the constant is an object, it's properties remain assignable. Semantically, yes, we would expect these values to never change, but the framework is not doing any wrapping of the provided object to prevent a developer from mutating it.
You can read a bit more here:
http://ilikekillnerds.com/2014/11/constants-values-global-variables-in-angularjs-the-right-way/

Related

Clearing a text entry field after processing the input

Having completed CodeSchool's AngularJS course, I'm trying to go back through and use what I've learned to build a simple to-do app. I've gotten it to the point where I can add entries to a list, but I'm not able to clear the text entry field after I've used the input. Here's my JSFiddle.
Specifically, here's my addComment() function:
card.addComment = function(newComment) {
card.comments.push(newComment.text);
newComment = {text: ""};
};
And the corresponding Angular HTML:
<form ng-submit="card.addComment(newComment)">
<input name="comment" placeholder="Add comment" ng-model="newComment.text">
</form>
At first I was passing the data in as a string (<input ng-model="newComment"> instead of newComment.text), but then I remembered that strings get passed by value, not by reference. I figured newComment was being blanked in the function but not passed back through Angular to the document. But even after I changed newComment to an object so it would be passed by reference, it made no difference--clearing the value in JavaScript has no effect on the page. What am I doing wrong?
I remembered that strings get passed by value, not by reference.
This line of reasoning is incorrect. Everything in JavaScript gets passed by object reference. See this question: Is JavaScript a pass-by-reference or pass-by-value language?
Replacing the raw string with an object does potentially buy you some affordance, though. However, whether or not it's a string or an object (and indeed, a string is an object!), newComment = someValue will never work.
Instead, you'll need to set the text property on the object. This doesn't create a new object, it just modifies a property, so the reference is preserved. Try this instead:
newComment.text = '';
Try this:
card.addComment = function(newComment) {
card.comments.push(newComment.text);
newComment.text = "";
};
What you're doing is creating a new object on the scope and breaking the binding which was set up on the original object. By updating the text property only, you leave the binding intact and achieve your goal of clearing the input field to which it is bound.
You can rest a form which may do what you require
document.getElementById("myForm").reset();

Access $scope object with protractor

I got an object like:
$scope.project = {name: 'whatever', description: 'blabla', another: 'another'};
To debug this, I enter in repl mode and try to see what "project" has.
When I define project variable as below, and call it, it returns my object, but when I try to access its keys (project.name), I get undefined. If I do Object.keys(project) I am getting the page object methods like click, getAttribute, etc.
Any ideas on how can I have access to the original object keys?
View side:
<h1 id="foo">{{project.name}}</h1>
Test side:
var project = element(by.id('foo')).evaluate('project');
evaluate uses executeScript behind the scenes. It returns an ElementFinder which resolves to the object you are looking for:
var project;
element(by.id('foo')).evaluate('project').then(function(value) {
project = value;
});
The documentation says:
which resolves to the evaluated expression for each underlying
element. The result will be resolved as in
webdriver.WebDriver.executeScript. In summary - primitives will be
resolved as is, functions will be converted to string, and elements
will be returned as a WebElement.
Also, check out Accessing Angular inside Protractor Test
Edit: syntax error

Angular directive link attrs

I was going through the Angular documentation and had a question about directives. This is in regards to the "Creating a Directive that Manipulates the DOM" section of http://docs.angularjs.org/guide/directive
I added some console.log() statements in the following code:
scope.$watch(attrs.myCurrentTime, function(value) {
format = value;
updateTime();
});
like so:
scope.$watch(attrs.myCurrentTime, function(value) {
console.log('attrs.myCurrentTime = ', attrs.myCurrentTime);
console.log('value = ', value);
format = value;
updateTime();
});
When changing the contents of the 'Date format' text box, I was expecting to see the same value in both console.log() statements, namely the actual value of the parent scope's format attribute, but the first console.log() still shows 'format' as a string.
Why do you think this might be?
Link to code: http://plnkr.co/edit/8LkKBiIpqTn0gr5fXQZL?p=preview
First of all, there is no parent scope. Since your directive does not declare a scope (either isolate or "normal") there is no scope created, so the element shares the same scope as its parent-element.
A quick and easy way to check the scope of an element using DevTools is this:
1. Select the element in the "Elements" panel.
2. In the console, execute the command: angular.element($0).scope();
So, what is actually going on ?
attrs.myCurrentTime represents a plain old string value (namely "format").
So console.log('attrs.myCurrentTime = ', attrs.myCurrentTime); is equivalent to console.log('attrs.myCurrentTime = ', 'format');
In the same way, scope.$watch(attrs.myCurrentTime, ...) is equivalent to scope.$watch('format', ...).
According to the docs on Scope, if the first argument of $watch is a string it is "evaluated as expression", which in this case means as scope['format'] (which of course returns the current value of the scope's format property).

$watch not being triggered on array change

I'm trying to figure out why my $watch isn't being triggered. This is a snippet from the relevant controller:
$scope.$watch('tasks', function (newValue, oldValue) {
//do some stuff
//only enters here once
//newValue and oldValue are equal at that point
});
$scope.tasks = tasksService.tasks();
$scope.addTask = function (taskCreationString) {
tasksService.addTask(taskCreationString);//modifies tasks array
};
On my view, tasks is clearly being updated correctly as I have its length bound like so:
<span>There are {{tasks.length}} total tasks</span>
What am I missing?
Try $watch('tasks.length', ...) or $watch('tasks', function(...) { ... }, true).
By default, $watch does not check for object equality, but just for reference. So, $watch('tasks', ...) will always simply return the same array reference, which isn't changing.
Update: Angular v1.1.4 adds a $watchCollection() method to handle this case:
Shallow watches the properties of an object and fires whenever any of the properties change (for arrays this implies watching the array items, for object maps this implies watching the properties). If a change is detected the listener callback is fired.
Very good answer by #Mark. In addition to his answer, there is one important functionality of $watch function you should be aware of.
With the $watch function declaration as follows:
$watch(watch_expression, listener, objectEquality)
The $watch listener function is called only when the value from the current watch expression (in your case it is 'tasks') and the previous call to watch expression are not equal. Angular saves the value of the object for later comparison. Because of that, watching complex options will have disadvantageous memory and performance implications. Basically the simpler watch expression value the better.
I would recommend trying
$scope.$watch('tasks | json', ...)
That will catch all changes to the tasks array, as it compares the serialized array as a string.
For one dimensional arrays you may use $watchCollection
$scope.names = ['igor', 'matias', 'misko', 'james'];
$scope.dataCount = 4;
$scope.$watchCollection('names', function(newNames, oldNames) {
$scope.dataCount = newNames.length;
});

Arrays in a Backbone.js Model are essentially static?

Why are arrays in a Backbone.js Model essentially static variables?
class exports.Content extends Backbone.Model
tags: []
then if i make a few models:
contentA = new Content()
contentB = new Content()
and add one string to each models' array:
contentA.tags.push('hello')
contentB.tags.push('world')
they both end up with the same array:
contentB.tags // ['hello','world']
but if it's a string, then there is no problem:
contentA.name = "c1"
contentB.name = "c2"
contentA.name // "c1"
The short answer
When you call extends to define your object, you are passing the new object's configuration in as an object literal. Objects are passed by reference, and the extends function only passes a reference to the tags array in to the new type definition.
As noted by others, you can correct this by assigning tags to a function. This works because a function delays the evaluation of the tags until the object is instantiated. There's nothing native in JavaScript that does this, but it's Backbone itself that recognizes tags as a function or a value.
The long answer
In spite of your code being in CoffeeScript, this comes down to a combination of a few things in JavaScript:
There are no classes in JavaScript
Object literals are evaluated immediately
JavaScript objects are passed around by reference
In JavaScript, there are no classes. Period. CoffeeScript gives you the notion of a class, but in reality, it gets compiled down to JavaScript which has no classes.
You can have types and type definitions (constructor functions) but not classes. Backbone provides a class-like definition, that looks similar to Java's "extend" class-based inheritance. It's still just JavaScript, though, which has no classes.
What we have, instead, is an object literal being passed in to the extends method. It's as if you write this code:
var config = {
tags: []
}
var MyModel = Backbone.Model.extends(config);
In this code, config is an object literal, or a hash, or a key/value pair, or an associative array. Whatever name you call it, it's the same basic idea. You end up with a config object that has a tags attribute. The value of config.tags is an empty array, [], which is itself an object.
Which brings us back to the short answer:
When you call extends to define your object, you are passing the new object's configuration in as an object literal. Objects are passed by reference, and the extends function only passes a reference to the tags array in to the new type definition.
As noted by others, you can correct this by assigning tags to a function. This works because a function delays the evaluation of the tags until the object is instantiated. There's nothing native in JavaScript that does this, but it's Backbone itself that recognizes tags as a function or a value.
"tags" is being declared on the Content.prototype - which is shared across all instances of Content. You probably need to use defaults instead: http://backbonejs.org/#Model-defaults. However, it should be noted that you must use a function (instead of a hash) when defining defaults, otherwise you still run into the same problem when using types which are passed by reference (e.g. Array).
var Content = Backbone.Model.extend({
defaults: function() {
return {
tags: []
};
}
});
benpickles is correct when it comes to Backbone models, and the way to do this in coffeescript in general is to initialize instance properties in the constructor:
class Foo
constructor: ->
#bar = []
As Derick Bailey mentioned, pass by reference is the problem. I solved the problem by cloning the Array (passing-by-value essentially):
var _events = window.active_model.get('events').slice(0);
_events.push({ key: "xxx", value: "yyy" });
window.active_field.set('events', _events);

Resources