I'm learning AngularJS and am hoping I can get some help with a problem I'm facing.
I have an array of objects(friends) in my controller that I've bound to a select element using ngOptions. Using the dropdown, a user can select a friend and view the friends information (id, name, age, etc).
I have also included a button that allows the user to change the age of the selected friend. When the button is clicked, the friends age is changed and reflected in the UI. However, the age change is not reflected in the array If the user then selects a different friend from the drop down and then selects the original friend, the age shown is the original age before the change.
Hopefully this plunker can clarify my explanation: http://plnkr.co/edit/9yfKSt75BhQDIMZjDLVi?p=preview
angular.module('defaultValueSelect', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.friends = [{id:5, name: "jack", age: 35}, {id:8, name: "jill", age: 38}];
$scope.newAge;
$scope.saveNewAge = function(){
$scope.selectedFriend.age = $scope.newAge;
}
}]);
<!doctype html>
<html ng-app="defaultValueSelect" >
<head>
<meta charset="utf-8">
<title>AngularJS Plunker</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.6/angular.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="ExampleController">
<form name="myForm">
<label for="mySelect">Choose a friend:</label>
<select name="mySelect" id="mySelect"
ng-options="friend.name for friend in friends track by friend.id"
ng-model="selectedFriend"></select>
</form>
<hr>
<tt>selected friend = {{selectedFriend}}</tt><br/>
<input type="text" class="form-control" ng-model="newAge">
<button ng-click="saveNewAge()">Update age</button>
</body>
</html>
My background is in C++ and C#. In those languages, a problem like this would be due to copying an object from the array and changing it rather than changing a reference to the object in the array. I've been searching the internet, and it seems like my problem may have something to do with what scope I'm in, but I'm not sure.
If you just add a for loop into your saveNewAge function to match the selected person to the person in the array (I used id to match) and set the age there it should do what you want.
$scope.saveNewAge = function() {
$scope.selectedFriend.age = $scope.newAge;
for (var i = 0; i < $scope.friends.length; i++) {
if ($scope.friends[i].id === $scope.selectedFriend.id)
$scope.friends[i].age = $scope.selectedFriend.age;
}
}
As is mentioned in the ngOptions documentation
Note: By default, ngModel watches the model by reference, not value. This is important when binding any input directive to a model that is an object or a collection.
Related
I have a pseudo code like this where using the one-way-binding operator(::) I tried to see if angular is watching for changes. So I had to include it inside an input tag. the model data inside the input tag should be resolved one way because of :: before it. However if I make changes to the input and click the button to see the changes it reflects the changes in the log. But it should not watch for the changes.
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-beta.2/angular-animate.js"></script>
</head>
<body class="container" ng-controller="ItemsController">
<ul ng-repeat="item in ::items">
<li>
<!-- in actual code the input will not be included -->
<input type="text" ng-model="::item.name"> {{ ::item.name }}
<!-- actual code -->
<!-- {{ ::item.name }} -->
</li>
</ul>
<button type="btn" ng-click="click()">Button</button>
<script>
angular.module('app', [])
.controller('ItemsController', function ($scope) {
$scope.items = [
{name: 'item 1'},
{name: 'item 2'},
{name: 'item 3'}
];
$scope.click = function () {
for (var obj of $scope.items) {
console.log(obj.name);
}
};
})
</script>
</body>
</html>
A couple of things.
Is one time, no one way bonding. Is useful when you want an expression to be evaluated only once and not watching for changes.
The :: in ng-model is doing nothing, it will still update the scope with the value that you put and update the item name.
At the same time the {{ ::item.name}} should remain unchanged, because is one time binding and it won't watch for additional changes.
So you will see the changes in the log, because the value actually changes, what will not change is the view.
Can any one help me out to understand what exactly two way data binding in AngularJS means with a help of simple code.
One way data binding -
The model values are automatically assigned to the HTML placeholder elements specified through the data binding notation, but the HTML elements don't change the values in the model(one way).
Example :
Controller :
app.controller('MainCtrl', function($scope) {
$scope.firstName = 'John';
});
HTML :
<span>First name:</span> {{firstName}}<br />
Two Way Data Binding -
The model values are automatically assigned to the HTML placeholder elements specified through the data binding notation, where HTML elements can change the value in the model(two way).
Example :
Controller :
app.controller('MainCtrl', function($scope) {
$scope.firstName = 'John';
});
HTML
<span>First name:</span> {{firstName}}<br />
<span>Set the first name: <input type="text" ng-model="firstName"/></span><br />
In above example we can change firstName model value with the help of HTML Input element.
Working example : http://plnkr.co/edit/GxqBiOoNFuECn55R4uJZ?p=preview
Retrieved from the AngularJS homepage (2015.06.02):
<!doctype html>
<html ng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
</head>
<body>
<div>
<label>Name:</label>
<input type="text" ng-model="yourName" placeholder="Enter a name here">
<hr>
<h1>Hello {{yourName}}!</h1>
</div>
</body>
</html>
This is possibly the simplest example of two-way data binding in Angular.
The <input> is associated to a yourName model, and the same model is used to fill the content of the <h1> tag. Modifying one will automatically update the other.
Although the data binding in the example can be seen as one-way, because you can't modify the <h1> directly, this should get you started. The AngularJS docs and tutorials contain a lot of great resources.
I'm using bootstrap and angular to build an application. In a HTML page, i'm using this:
<div class="btn-group-justified" data-toggle="buttons-checkbox">
<button type="button" class="btn fzbtn-consServices" ng-repeat="service in availability.services">{{service.name}}</button>
</div>
It's building a button group with dynamic values. Is there a practical way to obtain the selected buttons inside this button group?
I already tried some solutions, some of them are working but I don't know if it's the best way...
1: On "ng-click" method I would change a attribute value (eg. "checked" attribute) of each service to true or false;
2: I searched about any html attribute for btn-group which could offer me all the selected buttons inside this group, but i had no success;
3: I heard that i could beat this problem using Angular Filter, but i didn't find any similar example;
Anyone with a better idea? Thanks so much :)
This is the best solution I found until now:
<div class="btn-group-justified" data-toggle="buttons-checkbox">
<button type="button" class="btn fzbtn-consServices" ng-repeat="service in availability.services" ng-click="onClickService(service)" ng->{{service.name}}</button>
</div>
Controller:
$scope.onClickService = function (service) {
if (service.checked) {
service.checked = "false"
} else {
service.checked = "true";
}
}
Answer: Bootstrap UI
I feel your pain. Bootstrap is not always angular-friendly. But there is a good solution:
The easiest (and by far the cleanest) approach is to use Bootstrap UI. Built by the Angular Team, it is a rewrite of the javascript-portion of Bootstrap but for an Angular-friendly usage. Here's the section about buttons: http://angular-ui.github.io/bootstrap/#/buttons
Example solution: Checkbox button behavior
In this solution, the initial services array is used to store a boolean field 'selected' to know if any particular service is selected or not. (Similar to the "checked" in the question). This field is 2-way bounded to the checkbox state. Clicking the checkbox changes the field and changing the field changes the checkbox state.
var app = angular.module('plunker', ['ui.bootstrap']);
app.controller('MainCtrl', function($scope) {
var services = [
{ name:'Service A' },
{ name:'Service B', selected:true },
{ name:'Service C' },
{ name:'Service D', selected:true }
];
$scope.availability = { services:services };
});
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS BootStrap UI radios</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.11.2.js"></script>
<script src="app.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body ng-controller="MainCtrl">
<div class="btn-group-justified">
<label ng-repeat="service in availability.services" class="btn btn-primary" ng-model="service.selected" btn-checkbox>{{service.name}}</label>
</div>
<div ng-repeat="service in availability.services">{{service.name}} <span ng-show="service.selected">- selected</span></div>
</body>
</html>
Radio button behavior
I've included a solution for a "single selection" checkbox also known as a "radio-button". The "current selection" is bound to a variable on the scope. It will get updated automatically when the user picks an element. Setting it will, in turn, change the current selection.
var app = angular.module('plunker', ['ui.bootstrap']);
app.controller('MainCtrl', function($scope) {
var services = [
{ name:'Service A' },
{ name:'Service B' },
{ name:'Service C' },
{ name:'Service D' }
];
$scope.availability = { services:services };
$scope.model = {};
// Here we are using the service object instance as the "selection value".
// Depending on what you need, you could also use some sort of identifier or
// even the $index if that's more useful.
// Immediately select the second one.
$scope.model.selectedService = services[1];
});
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS BootStrap UI radios</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.11.2.js"></script>
<script src="app.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body ng-controller="MainCtrl">
<div class="btn-group-justified">
<label ng-repeat="service in availability.services" class="btn btn-primary" ng-model="model.selectedService" btn-radio="service">{{service.name}}</label>
</div>
<div>Current Selection is: {{model.selectedService.name}}</div>
</body>
</html>
NOTE: I used <label> instead of <button>, but I did not have your special CSS styles so it wasn't behaving on the screen, functionality-wise it works equally well with <button> elements.
You don't really specify why you are wanting to "get" the buttons. In general, getting a reference to DOM elements like this is not the "angular way." It is generally better to find a way to use angular's data-binding to manipulate UI elements.
For instance, if you want to show or hide buttons based on data or on another UI event, then use ng-show or ng-hide bound to a property of the service object you are binding these buttons to. Then you can update the object's property to change the UI. You should be able to find a similar way to make other changes (like setting classes, attributes, etc.) with angular's data-binding rather than doing it manually.
Say I have a $scope variable selectedUser, which is set when a user in a list is clicked. I update the properties of selectedUser and send a request to the backend updating the user with the matching id. Of course in the ng-repeat that lists all users, the property is updating just fine, because upon completion of the update, I run a User.get() which re-initializes the ng-repeat list withthe new data. It seems, however, that when I then update the selectedUser variable with the same user but with its newly set properties, the binding to selectedUser does not update.
In essence, my question is: If a $scope variable is changed after page load, how do i get the binding (or whatever you refer to the double curly braces in a view as) to update with the new value?
Again, the $scope.selectedUser object is updated, but {{selectedUser.aProperty}} is not updating.
Thanks for your help!
Normally you run into this issue (as per my comments) when you update a model in a child scope, but the view reference is bound to a parent scope.
I've created an artificial example of that sort of issue here.
When you change an object from a child scope like below, the child scope gets the new object, but the parent scope retains it's old copy, and any changes from the child scope now only touch the new object.
HTML:
<!DOCTYPE html>
<html ng-app="test">
<head>
<script data-require="angular.js#1.3.0-beta.5" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-controller="RootCtrl">
<!-- this is bound to my RootCtrl scope -->
<h1>{{user.name}} ({{user.id}})</h1>
<div ng-controller="ChildCtrl">
<!-- this will inherit it's value from RootCtrl's scope until
we overwrite it: -->
<b>Name:</b> {{user.name}}
<b>Id:</b> {{user.id}}
<button ng-click="newUser()">New Id</button>
</div>
</body>
</html>
Javascript:
angular.module('test', [])
.controller('RootCtrl', function($scope) {
$scope.user = {
name: 'John',
id: 12
};
})
.controller('ChildCtrl', function($scope) {
/* if you move this method to the parent scope,
* both should update as expected. */
$scope.newUser = function() {
$scope.user = { id: 100, name: 'Bob' };
}
});
Ussualy AngularJS should update all the bound elements when the scope changes in an event handler but in the case it doesn't you can manually call $scope.$apply().
Also, check this post about when to use $scope.$apply in your module's lifecycle.
I am looking at this basic example of AngularJS:
<!doctype html>
<html ng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular.min.js"></script>
</head>
<body>
<div>
<label>Name:</label>
<input type="text" ng-model="yourName" placeholder="Enter a name here">
<hr>
<h1>Hello {{yourName}}!</h1>
</div>
</body>
</html>
It is the first example on the website and has no controller or anything. A textfield is attached to the model field yourName.
Now, I was wondering: since editing the text in the textfield changes the model value, how do I change the value of "yourName" programmatically? Thus far, I have found no simple way of doing this without adding a controller and whatnot.
Also, how can I attach a listener to that model to be notified whenever it changes?
In this case, you don't need a controller for this simple example. Angular behind the scenes is "creating"
$scope.yourName
In terms of changing it programmatically, you have to use a controller. In terms of watching for changes, use $scope.$watch
$scope.$watch($scope.yourName, function (newVal, oldVal) {
if (newVal !== oldVal) {
// logic here
}
});