two-way binding Angularjs bug - javascript

My angular App makes use of multiple controllers and all of them utilize the two-way binding perfectly fine except this one. For some reason it's not updating the scope variables in my angular.extend when I change the variable at a later time. Here's what I do:
angular.extend($scope, {
cart: {
products: [
{amount: 1, name: 'product 1', total: 4.95},
{amount: 4, name: 'product 2', total: 4.95},
{amount: 2, name: 'product 3', total: 4.95},
],
deliveryCost: 0,
total: 0,
orderContinue: false,
}
});
And when I want to add a product to the cart I do:
addToCart: function(id) {
var newProduct = {amount: 1, name: 'product 4', total:5.94};
$scope.cart.products.push(angular.extend(newProduct));
}
When I console.log my $scope.cart.products in the addToCart function, it shows the newly added product, but then when I want to have it updated in my view, it just doesn't add the product to the list:
<div class="row" ng-controller="cartController">
<div class="col-md-12">
<h4>Cart</h4>
<div class="row" ng-repeat="products in cart.products">
<div class="col-md-2">
{{products.amount}}
</div>
<div class="col-md-8">
{{products.name}}
</div>
<div class="col-md-2">
X
</div>
</div>
</div>
The product gets added like this
<div ng-controller="cartController" class="col-xs-4 col-sm-2 col-lg-2" style="border: 1px solid black;">
<button ng-click="addToCart(1)">Add to</button>
</div>
What am I doing wrong?

You have used ng-controller twice in html. so there will be 2 different scope objects for each declaration.
Add following cod inside first controller scope.
<button ng-click="addToCart(1)">Add to</button>
If you want to see actually whether there are two different controllers. Just console $scope object in controller. It will be printed twice initially. You can see object id of $scope as
console.log($scope.$id);
you can see different ids.

Whenever you use ng-controller directive, a new child scope is created and controller function is instantiated with new $scope. In your case you are using two ng-controllers, thus they will have different $scope. Try writing same code under single ng-controller, it'll work.
For your reference Angular Docs - Understanding Controllers

Related

ng-repeat not working on a bootstrap row

Problem:
I am trying to repeat an array over a bootstrap row but the DOM is not repeating itself with any value.
My controller:
$scope.createSummary = function(){
$scope.entries = [];
for(var i=0;i<$scope.questions.length;i++){
$scope.entries.push({
question : $scope.questions[i].statement,
correct : $scope.questions[i].correct,
answered : $scope.answers[i]
});
}
console.log($scope.entries);
$state.go("home.summary");
}
My View:
<div class="container">
<div class="row">
<div class="col-sm-12" id="mast-head">
<h2>Match your answers against correct answers.</h2>
</div>
</div>
<div ng-repeat="entry in entries track by $index" class="row">
<div class="col-sm-9">
<p> {{$index+1}} <span ng-bind="entry.question"></span><p>
</div>
<div class="col-sm-1">
{{entry.correct}}
</div>
<div class="col-offset-1 col-sm-1">
{{entry.answered}}
</div>
</div>
</div>
Please note that this view is one of the many child states, and with this controller as a common controller.
What am I doing wrong here? Am I repeating over the wrong div?
You might should pass arguments to the state you are display and initialize the data in this views controller. I made an example using a service but its not exactly the same as what you will have. I think it might provide you with enough guidance to get your issue resolved. The ng-repeat should be fine once you get the data set correctly in the controller.
http://codepen.io/mkl/pen/ZKzMwe/
This is how I defined the service to be able to get and set the questions. I didn't add a set function for the answers but you should get the idea.
angular.module('app', [])
.service('service',
function() {
this.setQuestions = function(data) {
questions = data;
}
this.getQuestions = function() {
return questions;
}
this.getAnswers = function() {
return answers;
}
var questions = [{
'statement': 'A Statement 1',
'correct': false
}, {
'statement': 'A Statement 2',
'correct': false
}, {
'statement': 'A Statement 3',
'correct': false
}];
var answers = [
'Answer 1',
'Answer 2',
'Answer 3'
];
}
)

Unable to use inherited/isolated scope concept in my example plunker

I am learning inherited/isolated scopes in angular directives and struck with some basic concepts. please see the plunker below.
Example.
Scenario1:
I have 2 directives (book and details). I am displaying two "Book Details" containers and toggling book name by sending custom attributes like this.
<book bookdetails="book" collapsed="yes" list="list"></book>
<book bookdetails="book1" collapsed="no" list="list"></book>
Question: Is this the right way to handle displaying things in 2 different containers?
Scenario 2:
I want to hide the author details section in container 1 but show in container2 on load. How to accomplish that?
When I use this line below it will hide and show both author details section but I want to keep it separate.
<details collapsed="yes"></details>
I know I am lacking basic skills using inherited/isolated scopes. Can someone educate me?
It's OK to use nested directives like you've used so you can do everything related to the details pane in the details controller like removing items from the books list.
If you wouldn't do any logic in details controller and just include some html I would just use ng-include.
Some points I've detected during improving your code:
Template markups are partial html files, so no need to add header, body etc. Just add your markup that you need in your directive.
I've created one model array books that you can iterate with ng-repeat and not two separate scope variables. That's easier to add more books.
I wouldn't pass the collapsed state to directive isolated scope. I would add it to the book model then you can have independent states of the details panes.
You could also create a collapsed array scope variable separate from your model and use it like ng-hide='collapsed[$index]' if you don't like to add it to your model.
Don't compare to the string yes. It makes things more complicated. It's better to use true or false.
The list you're passing is OK if you'd like to use one list for every details pane. But I think you need them independent from each other so add it to your book model.
For toggeling a value you can use the js shorthand: collapsed = !collapsed;. It takes the value of collapsed and inverts it and re-asigns it to collapsed.
Details directive: You don't need to pass attributes to a directive that doesn't use isolated scope. Instead you can directly use the inherited scope of the parent.
Note: I think you should have a look at angular-ui-bootstrap and use an accordion instead of your manually created panes later. But for learning directives your code is OK.
Please have a look at your updated code below or in this plunker.
If something is not clear, feel free to add a comment and I'll try to help.
angular.module('plunker', [])
.controller('MainCtrl', function($scope) {
$scope.books = [{
id: 0,
name: 'Building modern ASP.NET 5',
author: {
name: 'name1',
age: 31,
country: 'USA'
},
collapsed: false,
list: [{
id: 0,
name: 'book1'
}, {
id: 1,
name: 'book2'
}, {
id: 2,
name: 'book3'
}]
}, {
id: 1,
name: 'AngularJS',
author: {
name: 'name2',
age: 27,
country: 'USA'
},
collapsed: true,
list: [{
id: 0,
name: 'book1'
}, {
id: 1,
name: 'book2'
}, {
id: 2,
name: 'book3'
}]
}];
//$scope.list = ["book1", "book2", "book3"];
}).directive('book', function() {
return {
restrict: 'E',
templateUrl: 'book.html',
scope: {
bkdet: "=bookdetails"
//list: "="
//collapsed: "#"
},
controller: function($scope) {
$scope.toggleDetails = function() {
$scope.bkdet.collapsed = !$scope.bkdet.collapsed;
updateCaption();
};
function updateCaption() {
$scope.hypshowhide = $scope.bkdet.collapsed ? 'show details' : 'hide details';
}
// first run
updateCaption();
/*if ($scope.collapsed == 'yes')
{
$scope.dethide = true;
}
else {
$scope.dethide = false;
} */
//$scope.hypshowhide = 'show details';
}
}
})
.directive('details', function() {
return {
restrict: 'E',
templateUrl: 'details.html',
controller: function($scope) {
/*console.log($scope.bkdet.collapsed);
if (!$scope.bkdet.collapsed) { //== 'yes') {
$scope.dethide = true;
}
else {
$scope.dethide = false;
}*/
$scope.removeItem = function(index) {
$scope.bkdet.list.splice(index, 1);
}
}
}
})
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="plunker">
<div ng-controller="MainCtrl">
<div class="container">
<book bookdetails="book" ng-repeat="book in books"></book>
</div>
</div>
<script type="text/ng-template" id="book.html">
<div class="row">
<div class="panel panel-default">
<div class="panel-heading">
<h1>Book Details</h1>
</div>
<div class="panel-body">
<a class="pull-right" href="#" ng-click="toggleDetails(collapsed)">{{hypshowhide}}</a>
<div>
<!--ng-hide="dethide">-->
{{bkdet.name}}
</div>
<!--<details collapsed="no"></details>-->
<details></details>
</div>
</div>
</div>
</script>
<script type="text/ng-template" id="details.html">
<div class="container" ng-hide="bkdet.collapsed">
<div class="row">
<ul class="list-group list-unstyled">
<!--<li>
<h1>Author:</h1>
</li>
<li>
<ul>-->
<li>
<strong>Author</strong>
{{bkdet.author.name}}
</li>
<li>
<strong>Age</strong>
{{bkdet.author.age}}
</li>
<li>
<strong>Country</strong>
{{bkdet.author.country}}
</li>
<li>
<div ng-if="bkdet.list.length == 0">
<p>No books here!</p>
</div>
<div ng-repeat="c in bkdet.list">
<p>
{{c.name}}
<button class="btn btn-danger" ng-click="removeItem($index)">X</button>
</p>
</div>
</li>
<!--</ul>
</li>-->
</ul>
</div>
</div>
</script>
</div>

AngularJS: I clearly do not understand the use of ng-init

I have an extremely hierarchical JSON structure as a scope variable in my AngularJS controller. I want to loop around different sections of that variable. I thought about using ng-init to specify where in the structure I am. Here is some code:
my_app.js:
(function() {
var app = angular.module("my_app");
app.controller("MyController", [ "$scope", function($scope) {
$scope.things = {
name: "a",
children: [
{
name: "a.a",
children: [
{ name: "a.a.a" },
{ name: "a.a.b" }
]
},
{
name: "a.b",
children: [
{ name: "a.b.a" },
{ name: "a.b.b" }
]
}
]
}
}]);
});
my_template.html:
<div ng-app="my_app" ng-controller="MyController">
<ul>
<li ng-init="current_thing=things.children[0]" ng-repeat="thing in current_thing.children>
{{ thing.name }}
</li>
</ul>
</div>
I would expect this to display a list:
a.a.a
a.a.b
But it displays nothing.
Of course, if I specify the loop explicitly (ng-repeat="thing in things.children[0].children") it works just fine. But that little snippet of template code will have to be run at various points in my application at various levels of "things."
(Just to make life complicated, I can get the current thing level using standard JavaScript or else via Django cleverness.)
Any ideas?
ng-init runs at a lower priority (450) than ng-repeat (1000). As a result, when placed on the same element, ng-repeat is compiled first meaning that the scope property created by ng-init won't be defined until after ng-repeat is executed.
As a result, if you want to use it in this manner, you'd need to place it on the parent element instead.
<div ng-app="my_app" ng-controller="MyController">
<ul ng-init="current_thing=things.children[0]">
<li ng-repeat="thing in current_thing.children>
{{ thing.name }}
</li>
</ul>
</div>

How to index variables in html using angularjs

I am new to angularjs and still learning the language.
I created a select box in an html and I want to populate it with a variable in my controller.
I am able to get the variable in the html using {{variablename}}, but I am not able to get the sub objects within it.
Please see my code here.
You can see that it displays "repeatSelect" in the html but if i try to index an object within it, it doesn't show.(getID is always empty)
Controller has a $scope variable as follows
controller('ExampleController', ['$scope', function($scope) {
$scope.repeatSelect = null;
$scope.data = {
availableOptions: [
{id: '1', name: 'Option A'},
{id: '2', name: 'Option B'},
{id: '3', name: 'Option C'}
],
};
}]);
In the html code,
<tt>repeatSelect = {{repeatSelect}}</tt><br/>
<tt>getID = {{repeatSelect.id}}</tt><br/>
repeatSelect works fine, but repeatSelect.id doesn't.
Please guide
Use ng-options built in directive instead https://docs.angularjs.org/api/ng/directive/ngOptions.
It is designed specifically to work with HTML select lists and has plenty of powerful options.
<div ng-controller="ExampleController">
<label> Repeat select: </label>
<select ng-options="option as option.name for option in data.availableOptions track by option.id" ng-model="repeatSelect"></select>
<hr>
<tt>repeatSelect = {{repeatSelect}}</tt><br/>
<tt>getID = {{repeatSelect.id}}</tt><br/>
</div>
Please see plunkr here: http://plnkr.co/edit/kIcH5fF7suhN0rhVDez8?p=preview

ng-repeat called from controller function

I would like to write out list of some items in my app using ng-repeat but i faced with this problem:
Template
<ion-slide-box on-slide-changed="slideHasChanged($index)" ng-controller="AccountCtrl" ng-init="getData()">
<ion-slide ng-repeat="statitem in stats">
<div class="box vh80">
<h1>{{statitem.name}}</h1>
<h1>Test</h1>
</div>
</ion-slide>
</ion-slide-box>
And in AccountCtrl
angular.module('starter.controllers', [])
.controller('AccountCtrl', function($scope, Friends) {
$scope.getData = function() {
console.log('test');
var friends = [
{ id: 0, name: 'Scruff McGruff' },
{ id: 1, name: 'G.I. Joe' },
{ id: 2, name: 'Miss Frizzle' },
{ id: 3, name: 'Ash efe' }
];
$scope.stats = friends;
}
});
Problem is, that no item is obtained in template.
Where can be problem please?
Thanks for any help.
EDIT:
I solved it by moving the method calling to the parent div.
<ion-content class="padding" ng-controller="AccountCtrl" ng-init="getData()">
try to clear the template a little bit to test to be sure that your HTML is 100% right, so you may make such this:
<div ng-controller="AccountCtrl" ng-init="getData()">
<div ng-repeat="statitem in stats">
<h1>{{statitem.name}}</h1>
</div>
</div>
and if you see any results you have then you have to check your HTML and CSS template.
I solved it by moving the method calling to the parent div.
<ion-content class="padding" ng-controller="AccountCtrl" ng-init="getData()">

Categories