AngularUI view not updating after model changes - angularjs

Beginner question on AngularUI updating a view after the model changes. I have a list of recipes and each recipe has a list ingredients. After a $http GET for ingredients, my nested loop isn't updating. Because other ng-repeat posts aren't helping I'm wondering if this is my AngularUI (UI) code.
How should subviews get updated after model changes? Is a scope trigger needed?
index.html:
<body>
<div ui-view></div>
....
#Outer
<script type="text/ng-template" id="front.html">
<div ng-repeat="search in searches" ng-class="{starred: search.isStarred, active: search.isActive, editing: search.isediting}" class="row">
<ng-include src="'grid.html'"></ng-include>
....
#Inner
<script type="text/ng-template" id="grid.html">
<ng-repeat="item in search">
<!-- item in $scope.searches[$index].results -->
<!-- item in $parent[$index].results -->
<li>{{item.searchId}}</li>
<!--<ng-include src="'image-item.html'"></ng-include>-->
app.js:
angular.module('tycho', ['ui.router', 'ui.bootstrap', 'tychoControllers', 'tychoDirectives', 'tychoServices'])
.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/front");
$stateProvider
.state('front', {
url: "/front",
controller: 'tychoController',
templateUrl: 'front.html'
})
controllers.js:
var controllers = angular.module('tychoControllers', []);
controllers.controller('tychoController',
['$scope', '$filter', 'tychoStorage', 'tychoCapi', 'capiSearch', '$http', '$timeout',
function tychoController(
$scope, $filter, tychoStorage, tychoCapi, capiSearch, $http, $timeout)
{
var searches = $scope.searches = tychoStorage.getSearches();
$scope.capi = new tychoCapi();
//.....
$scope.$watch('searches', function (newValue, oldValue) {
$scope.searchCount = searches.length;
$scope.starredCount = $filter('filter')(searches, { starred: true }).length;
$scope.completedCount = searches.length - $scope.starredCount;
if (newValue !== oldValue) { // This prevents unneeded calls to the local storage
tychoStorage.putSearches(searches);
}
}, true);
$scope.runSearch = function (search) {
capiSearch.getResults(search).then(function (data) {
// fix for $$digest busy
$timeout(function () {
var i = $scope.searches.indexOf(search);
if (i > -1 && data.Results && data.Results.length) {
var arr = $scope.searches[i].results || [];
$scope.searches[i].results = arr.concat(data.Results);
console.log('appending to', $scope.searches[i])
}
});
});
};
The app is needs to update $scope.searches[ n ].results=.... in a controller and have the view (view's nested repeat) update too. I feel like scope is wrong.

Try above code in subview:
<ng-repeat="item in search.results as search in searches">
<li>{{item.searchId}}</li>
<!--<ng-include src="'search-grid-item.html'"></ng-include>-->
</ng-repeat>

Thanks Alexand for pointing out a tag issue. Changing code from <ng-repeat... to <div ng-repeat="item in search.results... allowed $scope updates to work from the controller. Awesomeness.

Related

AngularJS: ng-repeat track by obj.id doesn't reinitialise transcluded content when obj.id changes

function ComponentController() {
var vm = this;
this.$onInit = function() {
vm.link = 'http://example.com/obj/' + vm.obj.id;
}
}
function MainController($scope, $timeout) {
$scope.arrObjs = [{id: 1, name: "object1"},{id: 2, name: "object2"}];
console.log('object1\'s id is ', $scope.arrObjs[0].id);
$timeout(function() { // simulates a call to server that updates the id
$scope.arrObjs[0].id = '3';
console.log('object1\'s new id is ', $scope.arrObjs[0].id, '. Expected the link above to be updated with the new ID');
}, 1000);
}
var someComponent = {
bindings: {
obj: '='
},
template: '<div>URL: <span>{{$ctrl.link}}</span></div>',
controller: ComponentController
};
angular.module('myApp', []);
angular
.module('myApp')
.controller('MainController', MainController)
.controller('ComponentController', ComponentController)
.component('someComponent', someComponent);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MainController">
<div ng-repeat="obj in arrObjs track by obj.id">
<some-component obj="obj"></some-component>
</div>
</div>
</div>
After the ng-repeat and its someComponent are rendered one of the objects' obj.id changes (using a $timeout in the above example). The vm.link for that object still carries the old id!
Now, I know that the $onInit() is only run once inside someComponent, but why doesn't track by re-initialise the component because the obj.id changed?
If Angular truly tracked an array by the obj.ids, it should treat an obj whose id changes as a completely different object and re-initialise it, no?
Obviously a $watch on vm.obj.id within someComponent will fix it, but is there a way without adding yet another $watch?
NOTE: previously I was using
<div ng-repeat="objID in vm.arrObjIDs track by objID" ng-init="obj = vm.fnLookUpObjByID(objID)">
<someComponent obj="obj"></someComponent>
</div>
And that works perfectly! This is exactly how I expected the track by obj.id to work. But I'm trying to move away from the ng-init pattern.
You're missing something somewhere in your code that you're not showing us.
The following snippet works.
init() is not a standard function though. You probably mean $onInit()
function ComponentController() {
var vm = this;
console.log("not in init: " + this.obj.id);
this.$onInit = function() {
console.log("in init: " + vm.obj.id);
}
}
function MainController($scope) {
$scope.arrObjs = [{id: 1, name: "object1"},{id: 2, name: "object2"}];
}
var someComponent = {
bindings: {
obj: '='
},
template: '<div>ID: <span>{{$ctrl.obj.id}}</span></div>',
controller: ComponentController
};
angular.module('myApp', []);
angular
.module('myApp')
.controller('MainController', MainController)
.controller('ComponentController', ComponentController)
.component('someComponent', someComponent);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MainController">
<div ng-repeat="obj in arrObjs track by obj.id">
<some-component obj="obj"></some-component>
</div>
</div>
</div>
EDIT:
If you add an asynchronous function in the middle, the compiling of the code will always be faster than the return of the async function.
Using $watch is the standard way of updating the view when data changes.
There's not other way.
Note: With components you can use $onChanges() but in this particular case it won't trigger since you have to change the reference of the object for it to update. $onChanges() calls $watch in any case.

Factory value not updated in model ...what I am doing wrong?

I am new to angular-js. I have two controllers (welcomeContoller,productController) and both handling the same model within the factory.
When the model getting updating by one controller(productController) it should reflect the update in another controller. (welcomeContoller)
But its not happening now.
HTML code :
<body ng-app="myApp">
<div ng-controller="welcomeContoller">
{{totalProductCnt}}
</div>
<div ng-controller="productController">
<div class="addRemoveCart">
<span class="pull-left glyphicon glyphicon-minus" ng-click="removeProduct()"></span>
<span class="pull-right glyphicon glyphicon-plus" ng-click="addProduct(1)"></span>
</div>
</div>
JS code
var myApp = angular.module("myApp", ['ui.bootstrap']);
myApp.factory("productCountFactory", function() {
return {
totalProducts:0
};
});
myApp.controller("welcomeContoller", function($scope, productCountFactory)
{
$scope.totalProductCnt = productCountFactory.totalProducts;
});
myApp.controller("productController", function($scope, productCountFactory) {
$scope.addProduct = function() {
productCountFactory.totalProducts++;
alert(productCountFactory.totalProducts);
};
$scope.removeProduct = function() {
if(productCountFactory.totalProducts >=1)
productCountFactory.totalProducts--;
alert(productCountFactory.totalProducts);
};
});
Even after the addProduct is called the totalProductCnt is displaying as zero. I want to display the value for each increment.
Plunkr Link
Put the factory object reference on scope:
myApp.controller("welcomeContoller", function($scope, productCountFactory) {
$scope.productCountFactory = productCountFactory;
});
Watch the property of the object.
{{productCountFactory.totalProducts}}
The DEMO on PLNKR.
By putting a reference on scope, on every digest cycle the watcher looks up the value of the property and updates the DOM if there is a change.
The totalProductCnt from your welcomeController isn't updated because it is assigned only once when the controller is created.
You can use several solutions to refresh the displayed value. Use a getter for your totalProducts in the factory :
myApp.factory("productCountFactory", function() {
var totalProducts = 0;
return {
getTotalProducts: function() {
return totalProducts;
},
addProduct: function() {
totalProducts++;
},
removeProduct: function() {
totalProducts--;
}
};
});
myApp.controller("welcomeContoller", function($scope, productCountFactory) {
$scope.getTotalProducts = productCountFactory.getTotalProducts;
});
myApp.controller("productController", function($scope, productCountFactory) {
$scope.addProduct = function() {
productCountFactory.addProduct();
};
$scope.removeProduct = function() {
if (productCountFactory.getTotalProducts() >= 1)
productCountFactory.removeProduct();
};
});
And update the view accordingly:
<div ng-controller="welcomeContoller">
{{getTotalProducts()}}
</div>
Plunkr Link

Angularjs model not update view when modified by child controller

I have to update a parameter of the parent controller from a nested controller.
I'm able to read the parameter, but when I change it it does not update into the view (webpage)... help plz :)
This is my js:
app.controller('signalCtrl', [ '$scope', 'DB', function($scope, service) {
this.address = null;
}]);
app.controller('reportMap', ['$scope', function($scope) {
this.updateParent = function() {
$scope.$parent.tab.address = 'something';
}
}]);
And this is my HTML:
<div ng-controller="signalCtrl as signal">
<input type="text" ng-model="signal.address">
[...]
<div ng-controller="reportMap as map">
[...]
</div>
</div>
The address property in parent controller is not bound to $scope, but to this object, so you even can't reach that property that way. I suggest you to move your updating function to parent controller:
app.controller('signalCtrl', [ '$scope', 'DB', function($scope, service) {
var self = this;
self.address = null;
self.update = function(newValue) {
self.address = newValue;
}
}]);
app.controller('reportMap', ['$scope', function($scope) {
var self = this;
self.someValue = 'something';
}]);
HTML:
<div ng-controller="signalCtrl as signal">
<input type="text" ng-model="signal.address">
[...]
<div ng-controller="reportMap as map">
<button type="button" ng-click="signal.update(map.someValue)">Click!</button>
[...]
</div>
</div>
You don't call updating function in the presented code, so I added a button to show how to use it.
use signal instead of tab
app.controller('reportMap', ['$scope', function($scope) {
this.updateParent = function() {
$scope.$parent.signal.address = 'something';
}
}]);
see this link : https://plnkr.co/edit/RhQLfBy2heecJDuCcq9s?p=preview
Ok, probably the error is into te HTML:
(function () {
var app = angular.module('segnalazioni', ['filters', 'smart-table', 'smart-table-server']);
app.controller('nuovaSegnalazioneCtrl', [ '$scope', 'DB', function($scope, service) {
var self = this;
this.activatedTab = "tab_animale";
this.chipNumber = null;
this.indirizzo = 'ind';
this.setIndirizzo = function() {
self.indirizzo = "VIA ROMA";
console.log("AGGIORNAMENTO INDIRIZZO");
}
I call setIndirizzo from the child (i don't pass a value I know, but it is not the problem): into the console i read "AGGIORNAMENTO INDIRIZZO", but the value into the VIEW does not change...
this is a link to the complete html file.
https://www.dropbox.com/s/gzlm997ub8fot7r/html.txt?dl=0

bind to scope when inside an if/else-statement not working

Inside my controller, I have:
$scope.friends = userService.data.user.friends
However, if user is null, I obviously get an error because userService.data.user.friends cannot be accessed.
So, now I use:
if(userService.data.user){
$scope.friends = userService.data.user.friends;
}else{
$scope.friends = null;
}
Problem is: if statement is not bound to $scope, and therefore is evaluated only once. So, if userService.data.user is initally null $scope.friends is also evaluated to null, but stays null even if friends are added touserService.data.userlater on (by $http request, for example)
I need to reload the route to get the new value of userService.data.user.friends.
How did I get scope bound to userService.data.user.friends, but also handle the null case ?
Service $parse – https://docs.angularjs.org/api/ng/service/$parse
angular.module('app', [])
.controller('ctrl', function($scope, $parse, userService) {
$scope.friends = $parse('data.user.friends')(userService)
$scope.someDeepProperty = $parse('data.user.friends.data.deep')(userService) || 'we are so deep';
})
.factory('userService', function($timeout) {
var userService = {}
userService.data = {
user: {
friends: []
}
}
$timeout(function() {
userService.data.user.friends.push({name: 'foo'})
}, 2000);
return userService;
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='app'>
<div ng-controller='ctrl'>
Friends: {{ friends }} - wait 2 seconds for update <br>
Deep property: {{ someDeepProperty }}
</div>
</div>

How should you access controller functions from other modules

I am having a hard time understanding how Modules should interact with each other in Angularjs. I would like to break the application into nice small modules, but I cannot seem to find the correct way to have these modules interact with each other.
JSFiddle:
http://jsfiddle.net/jwest80/o5o3sr8q/4/
The code shows a breadcrumb I would like to have at the top of the page. The BreadCrumb is in its own module 'bread' and included inside a parent module 'ngFSCH'.
There is a list outside BreadCrumb controller section whose actions should add breadcrumbs. However, I do not understand the correct way to access this addCrumb function. I can only make it work if it is called from inside the breadcrumb controller section in the markup.
Markup:
<div ng-app="ngFSCH">
<section ng-controller="BreadCrumbsCtrl">
<span ng-repeat="crumb in crumbs" class="breadcrumbs">
<span ng-hide="isLast($index)" ng-click="selectCrumb($index)">{{crumb.text}} > </span>
<span ng-show="isLast($index)">{{crumb.text}}</span>
</span>
</section>
<section>
<h4>Add Some Crumbs</h4>
<ul>
<li>Company</li>
<li>Department</li>
<li>User</li>
</ul>
</section>
</div>
Script:
var ngFSCH = angular.module('ngFSCH', ['bread']);
(function () {
var app = angular.module('bread', []);
app.controller('BreadCrumbsCtrl', ['$scope', '$log', function ($scope, $log) {
$scope.crumbs = [{ text: "Crumb 1", url: "url1" }, { text: "Crumb 2", url: "url2" }];
$scope.isLast = function(index) {
return index === $scope.crumbs.length-1;
}
$scope.addCrumb = function (newCrumb) {
$scope.crumbs.push({ text: newCrumb, url: "TestURL" });
}
$scope.selectCrumb = function (index) {
$log.info($scope.crumbs[index].url);
$scope.crumbs = $scope.crumbs.slice(0, index + 1);
}
}]);
})();
I would encapsulate the bread crumb functionality in a service and create a controller for the section with the links (that add the breadcrumbs). The new controller can then use the service to add and remove crumbs from the array. You can also add the crumbs array into a value.. Your controllers can then expose the add and select features to the tiny portions of html they control without polluting other sections of your page.
Here is the result. Hope it helps!
JSFiddle
Here is the code:
var app = angular.module('bread', []);
app.value('crumbs', [
{ text: "Crumb 1", url: "url1" },
{ text: "Crumb 2", url: "url2" }
]);
app.factory("BreadCrumbsService", ['$log', 'crumbs', function ($log, crumbs) {
var service = {
getCrumbs: getCrumbs,
addCrumb: addCrumb,
selectCrumb: selectCrumb
};
return service;
//I did not add a set crumbs because you can set it directly.
function getCrumbs(){
return crumbs;
}
function addCrumb(newCrumb) {
crumbs.push({
text: newCrumb,
url: "TestURL"
});
}
function selectCrumb(index) {
$log.info(crumbs[index].url);
crumbs = crumbs.slice(0, index + 1);
}
}]);
app.controller('BreadCrumbsCtrl', ['$scope', 'BreadCrumbsService', function ($scope, BreadCrumbsService){
$scope.crumbs = BreadCrumbsService.getCrumbs;
$scope.selectCrumb = BreadCrumbsService.selectCrumb;
$scope.isLast = function (index) {
return index === BreadCrumbsService.getCrumbs().length - 1;
}
}]);
app.controller('AddLinksCtrl', ['$scope', 'BreadCrumbsService', function ($scope, BreadCrumbsService) {
$scope.addCrumb = BreadCrumbsService.addCrumb;
}]);
Here is the links section with the new controller:
<section ng-controller="AddLinksCtrl">
<h4>Add Some Crumbs</h4>
<ul>
<li>Company</li>
<li>Department</li>
<li>User</li>
</ul>
</section>
That is intended because you are working within the scope of the controller. How about moving the ng-controller directive to the containing div where ng-app is?
<div ng-app="ngFSCH" ng-controller="BreadCrumbsCtrl">

Resources