AngularJS - Not able to update model programatically - javascript

When I update my model through method, model does not update. Below is my AngularJS code:
var app = angular.module('MyApp',[]);
app.controller('businessCtrl', function($scope, Business) {
$scope.businesses = Business.query(); // getting data using REST
$scope.currentBusinessIndex = 0;
$scope.changeBusinessIndex = function (indx) {
if (indx) {
$scope.currentBusinessIndex = indx;
} else {
$scope.currentBusinessIndex = 0;
}
};
});
Below is the HTML:
<ul class="signups">
<li ng-controller="businessCtrl" ng-repeat="business in businesses">
<div class="user pull-left" ng-click="changeBusinessIndex($index)">
<img ng-src="http://localhost:8081{{business.logo.uri}}" alt="business logo" >
</div>
<div class="info" ng-click="changeBusinessIndex($index)">
<h6 ng-cloak>{{business.name}}</h6>
<small ng-cloak>{{business.streetAddress + ',' + business.city + ',' + business.country}}</small>
</div>
</li>
</ul>
<div id="business-image" class="main-container" ng-controller="businessCtrl">
<img ng-src="http://localhost:8081{{businesses[currentBusinessIndex].logo.uri}}" alt="Business Logo">
</div>
Problem:
In changeBusinessIndex() method, when I modify currentBusinessIndex, it does not modify and as a result image in "business-image" div is not updating.

You are creating two different instances of businessCtrl, one for the li (let's call it businessCtrl1) and one for the business-image div (let's call that businessCtrl2). When you are calling changeBusinessIndex() in the li-instance it will update currentBusinessIndex for businessCtrl1, but not for the other instance.
Set ng-controller="businessCtrl" on an element that wraps both the li and the business-image divs to get a single instance of the controller, something like:
<div ng-controller="businessCtrl>
<li ng-repeat ... etc ></li>
<div id="business-image" ... ></div>
</div>

First of all it seems strange that you call twice ng-controller="businessCtrl" (with the same name). But if you interesting to use two different controllers I would store currentBusinessIndex into the Service and use it as storage. Since service is singleton, you can register the service to both controllers and by this way your logic should work.
HTML
<div ng-controller="MyCtrl_1">
<button ng-click="showMe();">press me</button>
<pre>{{myStorage.getIndex()}}</pre>
</div>
<div ng-controller="MyCtrl_2">
<pre>{{myStorage.getIndex()}}</pre>
</div>
JS
var myApp = angular.module('myApp', []);
function MyCtrl_1($scope, myStorage) {
$scope.myStorage = myStorage;
$scope.showMe = function(){
myStorage.setIndex(3);
}
}
function MyCtrl_2($scope, myStorage) {
$scope.myStorage = myStorage;
}
myApp.factory('myStorage', function () {
var currentBusinessIndex = 0;
return {
getIndex: function () {
return currentBusinessIndex;
},
setIndex: function (index) {
currentBusinessIndex = index;
}
}
});
Demo Fiddle

Related

How do I increment a counter when clicking an item in an ng-repeat?

I have generated a list with ng-repeat, where each item has a count variable. In each list item I have a link.
I want to increment the count when I click the link.
I tried the following way but it not work.
My Controller :-
myApp.controller('allpost', function ($scope, $http, $stateParams, Allposts) {
var id = $stateParams.id;
Allposts.GetAllposts(id).then(
function (response) {
$scope.allPosts = response.data.posts;
});
function ctrl($scope) {
$scope.increment = function(item){
item.count += 1;
}
}
})
and view like :-
<ion-content class="padding" lazy-scroll>
<div class="row no-padding HomeRowsList">
<div class="item itemfull" ng-repeat="post in allPosts">
<div class="item item-body">
<div>
<div class="title-news">
<div class="title" ng-bind-html="post.content"></div>
<div class="countbg">عدد المرات : {{post.custom_fields.azkarno}}</div>
<span>{{post.count}}</span><a onclick="ctrl(post);">Increment</a>
</div>
</div>
</div>
</div>
</div>
</ion-content>
In controller use
$scope.increment = function(item){
item.count += 1;
};
instead of
function ctrl($scope) {
$scope.increment = function(item){
item.count += 1;
}
}
and in html use
<span>{{post.count}}</span><a data-ng-click="increment(post)">Increment</a>
instead of
<span>{{post.count}}</span><a onclick="ctrl(post);">Increment</a>
It should be ng-click instead of onclick & the method name should be increment instead of ctrl.
Also remove unnecessary ctrl function wrapper from the increment method which is not needed at all, because whatever you wanted to call from the html should be included in the $scope of controller.
Markup
<span>{{post.count}}</span><a ng-click="increment(post);">Increment</a>

Changing $scope inside controller

I want to change $scope within controller from the wrapping div to the object I'm currently clicking on. My code is as follows:
var blogApp = angular.module('blogApp', ['ngSanitize', 'ngRoute']);
blogApp.controller('blogPostsCtrl', function($scope, $http) {
$http.get('http://jsonplaceholder.typicode.com/posts').success(function(data) {
$scope.posts = data;
$scope.postsLoaded = 'article--loaded';
});
$scope.getPost = function(postID) {
var currentPost = document.getElementById('post-'+postID);
$scope.postsLoaded = 'article--loaded';
$http.get('http://jsonplaceholder.typicode.com/posts/'+postID).success(function(data) {
$scope.body = data.body;
currentPost.insertAdjacentHTML('beforeend', '<div class="body body--hidden" id="body-'+postID+'">'+$scope.body+'</div>');
var currentBody = document.getElementById('body-'+postID);
setTimeout(function() { currentBody.className = currentBody.className + ' body--visible'; }, 1000);
currentPost.classname = 'article one-half desk-one-whole';
});
};
});
html:
<div class="site-wrapper">
<div class="grid-wrapper" ng-controller="blogPostsCtrl">
<article ng-repeat="post in posts" ng-class="postsLoaded" class="article one-half desk-one-whole" id="post-{{post.id}}" ng-click="getPost(post.id)">
<header><h2>{{post.title}}</h2></header>
</article>
</div>
</div>
As you can see, I use function getPost inside controller and there I'm using $scope, but it's (as it should be) set for, like I said, global wrapper. How can I solve this? Please note I'm new to Angular, so I don't know if it's the valid way ;-)
I agree with #Matthew Green. Your question a bit confused. But as far as I can see, you should use directives.
Create a directive and assign to it own $scope.
Hope that will help you.

How to Refresh Knockout ViewModel Using Mapping Plugin

I have the following Knockout ViewModel:
var EditorViewModel = function () {
var self = this;
self.addData = function (_data) {
ko.mapping.fromJS(_data, {}, self);
};
};
which is used as such:
var viewModel = new EditorViewModel();
viewModel.addData(model);
ko.applyBindings(viewModel);
For info, the data for viewModel.addData() comes from an AJAX call.
The View/ViewModel is populated with no problem the first time round when ko.applyBindings is called. However, if I later pull new data from the server via AJAX and then call either:
viewModel.addData(someNewAjaxData)
or
ko.mapping.fromJS(someNewAjaxData, {}, viewModel);
Then Knockout does not update the view with the new data. Hopefully this is a trivial problem...?!
KnockoutJS Mapping has the ability to specify a key for an item. This lets KnockoutJS know when an item needs to be created versus updated.
var ItemViewModel = function(d){
var self = this;
self.id = ko.observable(d.id);
self.text = ko.observable(d.text);
// We assign it when the object's created to demonstrate the difference between an
// update and a create.
self.lastUpdated = ko.observable(new Date().toUTCString());
}
var EditorViewModel = function(){
var self = this;
self.items = ko.observableArray();
self.addData = function(d){
ko.mapping.fromJS(d, {
'items': {
'create': function(o){
return new ItemViewModel(o.data);
},
'key': function(i){
return ko.utils.unwrapObservable(i.id);
}
}
}, self);
console.info(self);
}
self.addMoreData = function(){
self.addData({
'items': [
{id:1,text:'Foo'},
{id:3,text:'Bar'}
]
});
}
}
var viewModel = new EditorViewModel();
viewModel.addData({
items:[
{id:1,text:'Hello'},
{id:2,text:'World'}
]
});
ko.applyBindings(viewModel);
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"></script>
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Hello, world!</h3>
</div>
<ul class="list-group">
<!-- ko foreach:items -->
<li class="list-group-item">
<span class="text-muted" data-bind="text:id"></span>
<span data-bind="text:text"></span>
<span class="text-danger pull-right" data-bind="text:lastUpdated"></span>
</li>
<!-- /ko -->
<li class="list-group-item" data-bind="visible:!items().length">
<span class="text-muted">No items.</span>
</li>
</ul>
<div class="panel-footer">
Update
</div>
</div>
</div>
</div>
</div>
Observe, in the attached snippet, how the first item's lastUpdated value is assigned when it's created, but is left unchanged when it's updated. This is because the mapper sees the ID already exists in items and simply updated the properties. But, for item with the id 3, a new model is created.
All knockout bibdings were subscribed to the first created model. You recreate the bound model on every ajax response. Your problem seems similar that was discussed in this stackoverflow article. The same osbervable should be bound to the markup.
Try this (may be you should support an empty model binding):
// in the start of app
var observableModel = ko.observable();
ko.applyBindings(observableModel);
// on every ajax call
var viewModel = new EditorViewModel();
viewModel.addData(model);
observableModel(viewModel);

Logic problems with AngularJS and multiple $http queries

I have an app that will make 2 $http queries to an external API and get 2 different JSON responses. These responses will populate a ng-repeat, headers, etc.
My problem is that I want to include a 3rd query, dependent on the first two.
Like so:
I get artist JSON and release JSON, and I use artist.name and release.title to populate the URL of the third $http query.
So far I've managed to get the two first queries, and once the results they are displaying in the ng-repeat, with ng-click I launch the 3rd query and populate an img ng-src.
Buuut, my problem is that I want the img ng-src to be populated automatically without ng-click, so the function that triggers the 3rd query has to get launched right after the 2 first queries. And also, in my working version right now, the img that I fetch with ng-click, will populate all items in ng-repeat. Meaning that every item should get their own image, and right now they don't.
I've created a working Plunker, if you search for a music artist and click on a result and then on an album, you'll see what I mean.
Basically, I think I'm missing a piece of logic that will put everything together and in proper trigger order.
Any thoughts?
My JS:
angular.module('myApp', ['ngResource'])
function Ctrl($scope, $http) {
var search = function(name) {
if (name) {
$http.get('http://api.discogs.com/database/search?type=artist&q='+ name +'&page=1&per_page=5').
success(function(data3) {
$scope.clicked = false;
$scope.results = data3.results;
});
}
$scope.reset = function () {
$scope.sliding = false;
$scope.name = undefined;
}
}
$scope.$watch('name', search, true);
$scope.getDetails = function (id) {
$http.get('http://api.discogs.com/artists/' + id).
success(function(data) {
$scope.artist = data;
});
$http.get('http://api.discogs.com/artists/' + id + '/releases?page=1&per_page=100').
success(function(data2) {
$scope.releases = data2.releases;
});
$scope.clicked = true;
$scope.sliding = true;
$scope.getImages = function (title, name) {
$http.get('http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=e8aefa857fc74255570c1ee62b01cdba&artist=' + name + '&album='+ title +'&format=json').
success(function(data4) {
$scope.images = data4;
});
}
}
};
My directive:
angular.module('myApp', ['ngResource'])
.directive('artistData', function() {
return{
restrict: 'E',
template: '<div class="col-md-8 col-md-offset-2"> \
<h1 ng-show="artist.name" class="artist-name">{{artist.name}}</h1> \
<div class="header-border" ng-show="artist.name"></div> \
<input ng-show="artist.name" class="form-control" ng-model="album" /> \
<div class="col-md-3" ng-click="getImages(release.title, artist.name)" ng-repeat="release in releases | filter:album | filter:{ role: \'main\' }"><div class="release">{{release.title}}<img class="img-responsive" ng-src="{{images.album.image[2][\'#text\']}}" /></div></div> \
</div>',
replace: true
};
})
And my HTML:
<div class="container">
<div class="row" ng-controller="Ctrl">
<div class="col-md-8 col-md-offset-2">
<div class="intro">
<div class="intro-text" ng-class="{'slide':sliding}">
<h1>Howdy stranger!</h1>
<h3>Use the form below to search for an artist and start building your record collection!</h3>
</div>
<input type="text" ng-model="name" class="form-control input-lg" ng-class="{'slide':sliding}" ng-focus="reset()" placeholder="Artist name"/>
</div>
<ul ng-hide="clicked" class="search-results">
<li ng-repeat="result in results" ng-click="getDetails(result.id)">{{result.title}}</li>
</ul>
</div>
<artist-data></artist-data>
</div>
</div>
I would use "Chaining Promises" in this case.
In basic words you call new async task on response of previous.
You can read this POST that might help you

Triggering function on ngclick with AngularJS

I have a function that makes a $http call to an external API and then populates some results within an ng-repeat array.
Right now the function gets triggered on every element on the ng-repeat, which creates a whole lot of server calls. I'd like for the function to only make the call once an element from the ng-repeat is clicked upon.
I've tried with ng-click, but i'd say i'm missing something.
The $http query that i'm trying to call on click is the second one:
function ImageCtrl($scope, $http) {
$scope.image = 'img/record-default.png';
$http.get('http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=e8aefa857fc74255570c1ee62b01cdba&artist=' + $scope.artist.name + '&album=' + $scope.release.title + '&format=json').
success(function (data4) {
$scope.image = data4.album.image[2]['#text'];
}
)
function getVersions ($scope, $http){
$http.get('http://api.discogs.com/masters/' + $scope.release.id + '/versions').
success(function (data5) {
$scope.versions = data5.versions;
});
}
}
And the relevant html:
<div class="col-md-3" ng-controller="ImageCtrl" ng-repeat="release in releases | filter:album | filter:year | filter:{ role: \'main\' }" >
<div class="release" ng-click="getVersions()"> \
<img class="img-responsive" ng-src="{{image}}" /> {{release.title}}
<ul ng-controller="ImageCtrl">
<li ng-repeat="version in versions">{{version.format}}</li>
</ul>
</div>
</div>
And a working Plunker. Function in question is line 60 on script.js
So I ended up taking what you have shown and doing some refactoring.
I moved getVersions to the prototype, and use it to append versions to a release object instead of the $scope.
function ImageCtrl($scope, fakeService) {
var _this = this;
this.fakeService = fakeService;
this.$scope = $scope;
fakeService.getReleases()
.then(function (releases) {
$scope.releases = releases;
});
this.$scope.getVersions = function(release){
_this.getVersions(release);
};
}
ImageCtrl.$inject = ['$scope', 'fakeService'];
ImageCtrl.prototype.getVersions = function (release) {
this.fakeService.getVersions(release.id)
.then(function (versions) {
release.versions = versions;
});
};
The markup isn't terribly different, but you can see where I pass the actual release object into the getVersions function in the click event. This way it always acts directly on the object bound to that particular row.
<div class="row" ng-controller="ImageCtrl">
<div class="col-md-3" ng-repeat="release in releases">
<div class="release" ng-click="getVersions(release)">
<h1>{{release.title}}</h1>
<img class="img-responsive" height="100" width="100" ng-src="{{release.image}}" />
<ul>
<li ng-repeat="version in release.versions">{{version.format}}</li>
</ul>
</div>
</div>
</div>
And here is a working demo showing the whole thing in action: http://jsfiddle.net/jwcarroll/k6mkt/
I'm using a fake service here to mimic calling a web service in order to get the data. I highly recommend wrapping up your calls to $http in order to encapsulate data access in your controller.

Categories

Resources