Angular JS - Parent view not updating when scope changes - angularjs

I'm building a simple Angular test app and have run into a problem. I have a list of items and an "add item" link at the bottom of the list (the list is stored on a simple NodeJS server). This "add item" link loads a child view that lets the user set the name of the new item. When the user clicks done the item is added to the list on the server, then tells the parent list to update. In the console I can see that the data is being passed back to the parent but the view isn't reflecting the new list of items. I've looked into $scope.$apply but as I am never using a third party library I have no reason to use it (I have tried it and it always gives the error "$digest is already running" which seems expected after looking at the docs). Here's the code in question:
HTML Parent List
<ul data-ng-controller="listCntrl">
<li data-ng-repeat="item in items">
{{item.name}}
</li>
<li>Add Item</li>
</ul>
<div data-ui-view></div> <!--Child view loaded here-->
HTML Child View
<div data-ng-controller="newItemCntrl">
<input type="text" data-ng-model="name"></input>
<a data-ng-click="addItem()">Done</a>
</div>
Controllers
app.controller('listCntrl',function($scope, ListFactory){
ListFactory.getItems().success(function(data){
$scope.items = data;
});
$scope.update = function(){
ListFactory.getItems().success(function(data){
console.log(data); //The correct list is in data
$scope.items = data; //This doesn't cause the view to update!
});
}
});
app.controller('newItemCntrl',function($scope){
$scope.addItem = function(){
$http({
method: 'POST',
url: '/addItem',
data: JSON.stringify( {name: $scope.name} ),
headers: {'Content-Type': 'application/json'},
})
.success(function(){
$scope.$parent.update();
});
}
});
Factory
app.factory('ListFactory', function($http){
return {
getItems: function(){
return $http({
method: 'POST',
url: '/getItems',
headers: {'Content-Type': 'application/json'}
});
}
}
});
Everything works on the server side and in the update() function it prints out the updated list, but somehow the $scope.items assignment isn't causing the list view to update. If I refresh the page then the list appears correctly. What am I doing wrong here? I'm guessing it's a problem with my understanding of the $scope.$parent but I can't seem to figure it out. Thanks for you help!

The 'listCntrl' controller is on the element, while the 'newItemCtrl' is bound to a child view of data-ui-view, which is a sibling of the element, not a child.
Thus, the scope created in newItemCtrl does not have the scope created by listCntrl in its parent chain.
Try something like this:
<div data-ng-controller="listCntrl">
<ul>
<li data-ng-repeat="item in items">
{{item.name}}
</li>
<li>Add Item</li>
</ul>
<div data-ui-view></div> <!--Child view loaded here-->
</div>
BTW, you don't actually need to use $scope.$parent in this case. All child scopes (but not isolated scopes) have prototypical inheritance from their parents, i.e. scope.__proto__ === scope.$parent. Just using $scope.update() should suffice, as long as no directive between this scope and the parent scope that defined the update function made an isolated scope.

Related

$ctrl in data binding is not working

I want to make a dynamic page using angular where the user after he logged-in see his user picture and some information charged by json file throught an http request.
This is the module where i do the http requestes:
'user strict';
angular.
module('userIssue').
component('userIssue',{
templateUrl: 'user-issue/user-issue.html',
controller: 'userIssueCtrl'
})
.controller('userIssueCtrl',['$scope','$http','$routeParams',function userIssueCtrl($scope,$http,$routeParams){
var self = this;
var percorso;
$scope.utente=$routeParams.username;
document.title="Benvenuto"+" "+$routeParams.username;
$http.get('user/'+ $routeParams.username+'.json').then(function(response){
self.utenti = response.data;
});
$http.get('user/'+$routeParams.username+'.info.json').then(function(qualcosa){
self.d = qualcosa.data;
});
self.show = function(){
$scope.showForm='false';
}
}]);
and this is the part of the html page not working:
<div class="col-sm-4">
<img style="position:center;" ng-src="{{$ctrl.d.imgUrl}}" class="img-circle" alt="user image"></div>
I'm using the same approch in this part of the code:
<div class="col-md-9 col-sm-9" id="issueColumn" >
<div id="issue" ng-repeat='utenti in $ctrl.utenti'>
<li>
<dt>CODICE OPERAZIONE</dt>
<dd>{{utenti.id}}</dd>
<dt>DESCRIZIONE PROBLEMA</dt>
<dd>{{utenti.description}}</dd>
<dt>DATA SEGNALAZIONE</dt>
<dd>{{utenti.forward}}</dd>
<dt>IL PROBLEMA SARĂ  RISOLTO ENTRO IL:</dt>
<dd>{{utenti.endstimed}}</dd>
<dt>STATO DEL PROBLEMA:</dt>
<dd>{{utenti.stato}}</dd>
</li> </br>
</div>
</div>
and here it is working! So why does the data binding not work for the image?
Could someone gently explain me why?
Here is the json file I use for the image:
[
{
"imgUrl": "img/Filippo.png",
"ciao": "ciao_calro"
}
]
As you want to use 1st element of d array object, you should be accessing 1st element of imgUrl property explicitly.
ng-src="{{$ctrl.d[0].imgUrl}}"
Hi can you try the below changes from your code and let me know,
Introduced controllerAs with the controller object:
component('userIssue',{
templateUrl: 'user-issue/user-issue.html',
controllerAs: 'userCtrl'
controller: 'userIssueCtrl' })
Accessing through the controller object name:
Try to access through the controller object name like,
{{userCtrl.d.imgUrl}} in template.

Cannot bind response object from POST to my view

I've been trying to solve this for hours, and have tried to find a working solution on stack overflow and other sites, but none worked so far.
The Issue
I am building a travelogue web app that allows users to log and view their journeys (e.g. a road trip). At the moment I am implementing the feature that lets users view a particular journey in a separate view which they have selected from a list of journeys. I pass down the id of the selected journey and retrieve an Object from MongoDB. I implemented this using POST. It works in that the _id of the selected journey is passed in the request, then used to identify the document with Model.findById - then the response yields the data. The data is bound to $scope.selection.
But while $scope.selection contains the data (when logged to console), I cannot seem to bind it to the view (called view_journey). Meaning, whenever I want to access, e.g. selection.name in my view_journey.html, the expression or ng-bind is left empty.
app.js
$scope.viewJourneybyId = function(id) {
var selectOne = { _id : id };
$http.post('http://localhost:8080/view_journey', selectOne).
success(function(data) {
$scope.selection = data;
$scope.$apply();
console.log("POST found the right Journey");
console.log($scope.selection);
}).error(function(data) {
console.error("POST encountered an error");
})
}
server.js
app.post("/view_journey", function(request, response, next) {
Journeys.findById(request.body._id, function(error, selection) {
if (error)
response.send(error)
response.json({ message: 'Journey found!', selection });
});
});
index.html
<tr ng-repeat="journey in journeys">
<td>
<a href="#/view_journey" ng-click="viewJourneybyId(journey._id)">
{{journey.name}}</a>
</td>
<td>...</td>
</tr>
view_journey.html
<div class="panel panel-default">
<div class="panel-heading">
<h2 ng-bind="selection.name"></h2>
<!-- For Debugging -->
ID <span ng-bind="selection._id">
</div>
<div class="panel-body">
<table class=table>
<caption>{{selection.desc}}</caption>
...
</table>
</div>
</div>
Feedback
This is my first question on stack overflow, so please also tell me if I phrased my question in a way that could be misunderstood, and whether or not I should supply more details, e.g. console output. Thank you ;)
After fiddling with your code I can confirm that when triggering the route you are getting a new instance of the controller that has a new, clean scope. This is the expected behavior with AngularJS.
You can verify this by adding a simple log message as the first line of your controller:
console.log($scope.selected);
You will notice that it always logs out "undefined" because the variable has never been set (within viewJourneyById). If you leave that logging in and test the code you will see the logging fire in viewJourneyById but then immediately the "undefined" as it loads the view_journey.html template into ng-view and creates the new instance of mainCtrl. The presence of the "undefined" after the new view loads shows that the controller function is being executed again on the route change.
There are a couple of ways to address this. First you could create a factory or service, inject it into your controller, and have it store the data for you. That is actually one of the reasons they exist, to share data between controllers.
Factory:
travelogueApp.factory('myFactory',function() {
return {
selected: null,
journeys: []
};
});
Controller:
travelogueApp.controller('mainCtrl', ['$scope','$http','$location','myFactory', function ($scope, $http, $location, myFactory) {
// put it into the scope so the template can see it.
$scope.factory = myFactory;
// do other stuff
$scope.viewJourneybyId = function(id) {
var selectOne = { _id : id };
$http.post('http://localhost:8080/view_journey', selectOne)
.success(function(data) {
$scope.factory.selection = data;
console.log("POST found the right Journey");
console.log($scope.factory.selection);
})
.error(function(data) {
console.error("POST encountered an error");
})
}
}]); // end controller
Template:
<div class="panel panel-default">
<div class="panel-heading">
<h2>{{factory.selection.name}}</h2>
</div>
<div class="panel-body">
<table class=table>
<caption>{{factory.selection.desc}}</caption>
...
</table>
</div>
</div>
More or less something like that. Another way to do it would be to construct the link with the journey id as part of the query string and then in the controller check for the presence of the journey id and if you find one, look up the journey. This would be a case of firing the route, loading a new instance of the controller and then loading the data once you're on the view_journey page. You can search for query string parameters in the controller like this:
var journey_id = $location.search().id;
Either way works. The factory/service method allows you to minimize web service calls over time by storing some data. However, then you have to start considering data management so you don't have stale data in your app. The query string way would be your quickest way to solve the problem but means that every route transition is going to be waiting a web service call, even if you are just going back and forth between the same two pages.

Why is angular not rerendering?

When I call $scope.remove in the html it calls the rerender function then automatically updates the data in the html.
When I call $scope.post it calls the rerender function and logs the new data. However, it doesn't automatically rerender the html. I have to refresh the page in order to see the up to date data.
I've looked into using $apply but I'm puzzled how it's updating for remove but not post.
Controller:
var updateData = function (resp){
$scope.data = resp.data;
console.log($scope.data);
}
$scope.getEntries = function (){
return $http({
method: 'GET',
url: '/allEntries'
}).then(updateData)
};
$scope.post = function (){
return $http({
method: 'POST',
url: '/newEntry',
data: $scope.entry
}).then(function (resp){
$scope.getEntries();
})
};
$scope.remove = function (opp){
return $http({
method: 'POST',
url: '/deleteEntry',
data: opp
}).then(function (){
$scope.getEntries();
})
}
I'm thinking it might be something to do with having the functions called in different html files but since they both have the same controller I would think that wouldn't be the case.
<html>
<body ng-app = "app">
<div ng-controller = "controller">
<form ng-submit = "post()">
<input type="submit" value="Submit">
</form>
</div>
</body>
</html>`
<html>
<body ng-app = "app">
<div ng-controller = "controller">
<ul>
<li ng-repeat = "job in data" ><input type = "button" ng-click = "remove(opp)" value="Delete"></li>
</ul>
</div>
</body>
</html>`
Indeed, you have two different sections, each with their own controller, and thus each with their own scope:
one which displays the list of items
one which diesn't display anything
You're posting from the section which doesn't display anything, so it refreshes the list in the scope of that section, which still doesn't display anything.
So, in short, both sections don't have the same controller. They have different controller instances, and those instances happen to have the same type. But they're still completely independant from each other.
And actually, re-reading your question, it's actually worse than that. It seems you have two browser tabs or frames opened. You are posting from one of the tabs, and hope that the other one will refresh automatically. That won't happen: the two tabs are completely independant applications.

Update directive variable from controller

New to AngularJS I have a simple directive, service and controller. I loop through a list of items from a database in the controller embedded in the directive to render a checkbox list of items. From a form I am able to update my list of items in the database. I would at the same time like to update my list of displayed items with the newly added item and was hoping to benefit from Angulars two-way binding but I can't figure out how...
My directive:
angular.module('myModule').directive('menu', function (menuService) {
return {
restrict: 'E',
templateUrl: '../templates/menu.html',
controller: function () {
var me = this;
menuService.getMenuItems().then(function(data) {
me.items = data;
});
},
controllerAs: 'menu'
};
});
and corresponding html:
<div ng-repeat="item in menu.items">
<div class="col-md-4" ng-if="item.menuItem">
<div class="checkbox">
<label for="{{item._id}}">
<input id="{{item._id}}" type="checkbox" name="menuItems" ng-model="menuItems[$index]" ng-true-value="{{item._id}}">
{{item.menuItem}}
</label>
</div>
</div>
</div>
My issue is now that if I add a new item using this controller
EDIT: On my page I have two things. 1: A list of items rendered using the directive above and 2: A simple form with a single input field. I enter a new menuItem and click "Save". That triggers the controller below to be called with the new menuItem. The menuItem is then added to a collection in my database but I would like the list on my page to update with the newly added item. Preferably without having to reload the entire page.
$scope.insertMenuItem = function () {
$http.post('/menuItems', $scope.formData)
.success(function (data) {
$scope.items = data;
});
};
then the "me.items" in my directive remains unchanged hence so does my list in my browser.
How do I "tie it all together" so that when I call insertMenuItem then my.items are updated automagically?
This didn't come out as well as I had hoped but I hope you get the meaning...
Thanks in advance
take a look at this http://jsbin.com/rozexebi/1/edit, it shows how to bind a list of items in a directive to a controller, hope it helps.

Refresh data on click outside controller in angular

I am trying to grasp the idea behind angular and ran into my first obstacle involving accessing data from outside the scope (the app?)
Here is a very simple example of what I'm trying to do:
HTML:
<div class=logo>
<a href='/refresh'>Refresh</a>
</div>
<div ng-app ng-controller="threadslist">
<div class='thread_list_header'>
<table class='thread_list_table'>
<tr class='table_header'>
<!--1--><td class='table_c1'></td>
<!--2--><td class='table_c2'>{{name}}</td>
<!--3--><td class='table_c3'>Cliq</td>
<!--4--><td class='table_c4'>Last Poster</td>
<!--5--><td class='table_c5'><a class="clickme">Refresh</a></td>
</tr></table>
</div>
<table class='thread_list_table' >
<tr class="thread_list_row" ng-repeat="user in users">
<!--1--><td class='table_options table_c1'></td>
<!--2--><td class='table_subject table_c2' title="">{{user.subject}}</td>
<!--3--><td class='table_cliq table_c3'></td>
<!--4--><td class='table_last table_c4'><span class="thread_username"><a href=#>{{user.username}}</a></span></td>
<!--5--><td class='table_reply table_c5'><abbr class="thread_timestamp timeago" title=""></abbr></td>
</tr>
</table>
</div>
JS:
function threadslist($scope, $http) {
$scope.name = 'Ricky';
// Initialising the variable.
$scope.users = [];
$http({
url: '/cliqforum/ajax/ang_thread',
method: "POST",
}).success(function (data) {
console.log(data);
$scope.users = data;
});
// Getting the list of users through ajax call.
$('.table_header').on('click', '.clickme', function(){
$http.get('/cliqforum/ajax/ang_thread').success(function(data){
$scope.users = data;
});
});
}
This is the part I can't figure out. My logo is supposed to clear whatever filter is on the current 'user' data. However, it sits outside the scope (and I imagine I shouldn't expand the scope to be the entire page?)
I have read something about scope.$spply but can't quite figure out what I'm supposed to do:
$('.logo').on('click', 'a', function() {
scope.$apply(function () {
$scope.users = data;
});
});
It's not quite necessary that I do it THIS way...I would just like to do what is correct!
Thanks!
and I imagine I shouldn't expand the scope to be the entire page?
Why not? That's definitely the way to do it. Just include the logo into the scope and you can then access it from your application, and use ng-click to add a click handler.
In fact, you should avoid using jQuery click handlers within your application. You could transform your JavaScript like so:
$scope.tableHeaderClick = function() {
$http.get('/cliqforum/ajax/ang_thread').success(function(data){
$scope.users = data;
});
});
Then update the HTML like so:
<tr class='table_header' ng-click="tableHeaderClick()">
it is an angular anti-pattern to include DOM elements in controller. you want to use the ng-click directive to respond to click events
see this plnkr:
http://plnkr.co/edit/KRyvifRYm5SMpbVvWNfc?p=preview

Resources