Angularjs One way Binding Binds Data Two Way - angularjs

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.

Related

Watch the object and compare in angularjs to trigger css class change if the value is changed

Im using AngularJS 1.5x
I have a Web Form to submit Car information 1)model, 2)make to Backend(Api: SaveCarDetails).to save this form information in the Backend DB.
On success, I have to make a API call(GetCarsInfo) to get the array list of cars information(so far saved in the BE).
I need to display car information Cards showing the make & model and with EDIT button so that user can update exactly that particular car information and we use (SaveCarDetails same API as used for adding new details).
On the succesfull update, I need to again make to call to (GetBanks) to get the updated list of bank account information.
Now I need to show Tickmark Icon ONLY on the Car info card that has been updated or newly added.
What is the best way to do this? (show the icon only on the card that got updated or newly added).
How to find out exactly which card is updated by comparing or watching an Object? there is no unique identifier that is being passed for any updates to the response object.
I tried to do an $watch on the object but I couldn't get it to work.
Here is the code: http://plnkr.co/edit/RztTjy?p=preview
// Code goes here
angular.module('app', [])
.controller('MyCtrl', function($scope) {
var vm = this;
vm.carobj = [{
'make': 'acura',
'model': "TL",
'name':'joe'
},
{
'make': "bmw",
'model': '5series',
'name':'doe'
}];
console.log(vm.carobj);
});
/* Styles go here */
.card{
border: 10px solid #cccccc;
width: 200px;
height:160px;
margin:20px;
position:relative;
}
.tickWhenUpdated{
float:right;
position:absolute;
right:0;
bottom:0;
}
.editbtn{
width:100px;
}
<!DOCTYPE html>
<html>
<head>
<link data-require="font-awesome#4.3.0" data-semver="4.3.0" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" />
<link data-require="bootstrap#4.0.5" data-semver="4.0.5" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" />
<script data-require="bootstrap#4.0.5" data-semver="4.0.5" src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.5/js/bootstrap.min.js"></script>
<script data-require="angularjs#1.5.7" data-semver="1.5.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="app">
<h1>Hello Plunker!</h1>
<div ng-controller="MyCtrl as mc">
<div class="card" ng-repeat="car in mc.carobj">
<p>Car Info Box </p>
<i class="fa fa-check tickWhenUpdated" aria-hidden="true"></i>
Account num:{{car.make}} <br />
Routung num: {{car.model}} <br />
<input value="edit" class="btn btn-primary editbtn" type="button" />
</div>
</div>
</body>
</html>
First of all, instead of dynamically updating the class using ng-class or something, you could just set a variable on the car objects called updated that is truthy when the car was updated. Then place ng-show="car.updated" on the tag.
Regarding how to set that updated variable on the object, you can either try to manipulate it directly when you click the edit button. Just pass the element to a function which will modify the updated field. Something like;
Html:
<div ng-repeat="car in cars">
<input type="button" ng-click"update(cars, car.id)">
</div>
Js:
$scope.update = function(cars, id) {
cars = cars.map(function(car) {
if (car.id == id) {
car.updated = true
} else {
car.updated = false
}
})
}
If you absolutely don't have an ID, the only thing I can think of is to put something that increments on the object (unix timestamp, incrementor). Something like
Js:
$scope.itr = 0;
$scope.update = function(car) {
$scope.itr++
car.itr = $scope.itr
}
Then have an ngChange on the array that calls a function and sets car.updated to false on all objects except the one with the largest itr value, which it sets to true.
This is a hardcore hack, and I would really try to just have some sort of unique ID.

How to assign dynamic value in angularJs Md Tab "ng-disabled"?

I'm using angularjs md-tab there could be approx 10 tabs so I want to enable and disable those tags as per some situations, Here is the code snippet which I'm using :
In View
<md-tab label="{{video.name}}" ng-repeat="video in vm.videos" md-on-select="tabClicked()" ng-disabled="data.locked">
In Controller
$scope.data = {
selectedIndex: 1,
locked: true
};
now as you can see in md-tab "ng-disabled='data.locked'" by using controller "locked: true" it disabled all of the md-tabs.
What I want to do here is to enable 4 tabs and disable rest of the tabs.
What I'm planning to do here is to assign dynamic value under
ng-disabled="data.locked"
e.g.
ng-disabled="data.firstTab",
ng-disabled="data.secondTab" and so on and disable them in controller.
how can I do that and If there is any other way then please let me know.
Thanks
The example below demonstrates that each tab has its own locked setting.
angular.module('MyApp', ['ngMaterial']).controller('AppCtrl', function($scope) {
$scope.videos = [
{name: 'Video1', locked: true},
{name: 'Video2', locked: false},
{name: 'Video3', locked: true},
{name: 'Video4', locked: false}
];
});
<link rel="stylesheet" href="https://material.angularjs.org/latest/angular-material.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-animate.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-route.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-aria.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-messages.min.js"></script>
<script src="https://material.angularjs.org/latest/angular-material.min.js"></script>
<div ng-controller="AppCtrl" ng-cloak="" ng-app="MyApp">
<md-tabs md-dynamic-height md-border-bottom>
<md-tab label="{{video.name}}" ng-repeat="video in videos" md-on-select="tabClicked()" id="tab" ng-disabled="video.locked">
</md-tabs>
</div>
Another way to do it is to have a function which will take the video object as parameter and decides if the corresponding tab is locked or not. The function should return true/false value and be used as:
ng-disabled="isLocked(video)"
Have an extra variable in videos array and based on that value you can disable it,
$scope.videos =[];
$scope.video = {};
/*your params*/
$scope.video.tabStatus = true;
$scope.videos.push($scope.video);
In view
<md-tab label="{{video.name}}" ng-repeat="video in vm.videos" md-on-select="tabClicked()" id="tab" ng-disabled="video.tabStatus">
in your view you are using this:
ng-disabled="data.locked"
This will be global to all tabs so you want individualize this. For this task you can use a function on the ng-disabled attribute and pass it your actual card:
ng-disabled="disabledCard(video)"
This will pass the actual video element and in the funcion you can handle if you want disable it or not.

Get selected buttons of btn-group (buttons-checkbox) with angularjs

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.

How do I update data bindings once a variable changes value in AngularJS?

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.

AngularJS not getting activated when loading HTML from script

I have this scenario, I am loading part of HTML (which has AngularJS directives) dynamically via script and I see AngularJS is not getting activated.
here is the example I am looking at. Is there anyway I can tell AngularJS to start bind on document ready? Loading an aspx page containing this widget1 content via a iframe seems to work but I am trying to avoid iframe and use client side script.
Appreciate any help.
<body ng-app>
main content page
<br />
<!-- widget1 -->
<b>Widget1</b>
<div id="widget1">
<!-- load below div via jquery/or any method from a remote html file-->
<script type="text/javascript">
$("div#widget1").load("/widgetsfolder/widget1.htm");
</script>
</div>
widget.htm file has below content.
<div ng-controller="TodoCtrl">
Total todo items: {{getTotalItems()}}
<ul class="unstyled">
<li ng-repeat="todo in todos">
<input type="checkbox" ng-model="todo.done" />
<span class="done-{{todo.done}}">{{todo.text}} </span></li>
</ul>
</div>
my controller code below.
`function TodoCtrl($scope) {
$scope.totalItems = 4;
debugger;
$scope.todos = [{
text: 'learn angularjs',
done: false
}, {
text: 'implement angularjs',
done: false
}, {
text: 'something else',
done: false
}, ];
$scope.getTotalItems = function () {
return $scope.todos.length;
}
}`
sample code here
http://jsfiddle.net/devs/RGfp4/
Apero's answer describes what is going on. I believe you are going to want to use ng-include. Your html would look something like this:
<body ng-app>
main content page
<br />
<!-- widget1 -->
<b>Widget1</b>
<div ng-include="'/widgetsfolder/widget1.htm'">
</div>
</body>
AngularJS evaluates the scope and renders the page after it is loaded.
Here, your js script loads the widget html but after Angular already compiled the scope etc.
I believe this will not work this way.
You can use angulars ngINclude to fetch outside documents, but I don't suggest it, it can be buggy. You can get the partials using either $http or $resource, this will fetch the data and compile the angular directives inside.
If you want to load the script using some other method, you can store the data as a string inside the controller and use a $compile directive in order to execute the angular code inside it.

Resources