ng-options not updating when model changed - javascript

I have a JSON object that looks like:
[
{
"empName": "John",
"ID": 1
},
{
"empName": "Sam",
"ID": 2
},
{
"empName": "Ben",
"ID": 3
}
]
In the view I want a dropdown where the user selects one of the names. I'm using ng-options to achieve this:
<select ng-options="item as item.empName for item in employees track by item.ID" ng-model="emp.selected">
</select>
If I hard-code the JSON into the variable employees in my controller the select renders. However if I use:
$.getJSON("path to JSON file",function(json){
$scope.employees = json;
});
The select is not populated. I've tried adding in $scope.$apply() to no avail. Any suggestions?
Update 1
Taking on board the answer from Iso I'm still left with the select not binding. For example If my javascript is:
app.controller('Ctrl', ['$scope', '$http', function($scope, $http) {
$scope.employees = [
{
"empName": "John",
"ID": 1
},
];
$http.get(" --PATH TO JSON-- ").then(function (res) {
$scope.employees = res.data;
console.log($scope.employees);
});
}]);
The select is still only populated with the first name 'John' despite the fact that the console.log returns the full object with all three names.

You need to either call $scope.$evalAsync(), or use $http instead of jQuery here (which I would recommend):
$http.get("path to JSON file").then(function (res) {
$scope.employees = res.data;
});
The reason for this is that $http is aware of AngularJS' digest cycle, whereas jQuery is not.
Refrain from using $scope.$apply() (which can fail if a digest cycle is in progress), and $timeout (which is heavy since it provokes a digest cycle of all your scopes).

Wrap your code inside $timeout:
$.getJSON("path to JSON file", function (json) {
$timeout(function () {
$scope.employees = json;
})
});
The call to $apply may fail when the digest cycle is already in progress.
But consider using the $http instead of using jQuery to pull data.

You should use promises with the $q library in Angular.
var getEmployees = function () {
var deferred = $q.defer();
$.getJSON("path to JSON file", function (json) {
deferred.resolve(json);
});
return deferred.promise;
}
getEmployees().then(res){
$scope.employees = res.data;
}
EDIT
If you use $timeout is not really as correct a solution, as it doesn't give you control over the synchronicity. However, using $http to make your calls comes with promises built in, I believe.

Related

pull data from json file into an angularjs service

angular.module('testJsonLoadApp')
.factory('LoadJsonFactory', function($http) {
var jsonLoad = {
fetch: function() {
return $http.get('../../test.json').then(function(data) {
return data;
});
},
};
return jsonLoad;
})
.controller('MainCtrl', function ($scope, LoadJsonFactory) {
var a = [];
LoadJsonFactory.fetch().then(function(items) {
a = items;
});
$scope.data = a;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div>
<ul>
<li ng-repeat="datum in data">{{datum.id}}</li>
</ul>
</div>
Here is the Json file content
[{"id": "0","Name": "Items 0", "Description": "Description 0"},
{"id": "1","Name": "Items 1", "Description": "Description 1"},
{"id": "2","Name": "Items 2", "Description": "Description 2"}]
The variable 'a' in the controller is not getting updated with the array generated from the json file. However if I use the $scope variable to bind the array directly, then it works.
LoadJsonFactory.fetch().then(function(items) {
$scope.data = items;
});
The above code works.
Is there a way I can assign it to a controller variable and then bind it to the scope variable?
This call is Async
LoadJsonFactory.fetch().then(function(items) {
a = items;
});
This executes before the call has finished
$scope.data = a;
So No you cannot do it this way. a will always be empty upon assigning to $scope.data
You can do this however if it suits you, which the same with what you have working, just a little clearer.
LoadJsonFactory.fetch().then(onFetch);
function onFetch(data) {
$scope.data = data;
}
The problem with your code is that LoadJsonFactory's $http call is asynchronous. Your $scope.data = a is run immediately after LoadJsonFactory is called.
I've made a quick plunkr that illustrates this—open console to see what I mean. (I've also cleaned up the LoadJsonFactory a bit so it more clearly returns a promise.)
https://plnkr.co/edit/Ns1iQdvhKT6DXrfjrOjm
Assigning to $scope.data within success function of the promise works because at that point the promise is resolved and the assignment triggers a digest that updates the two-way binding.
You can assign your service response LoadJsonFactory to a variable within your service, instead of in your controller, and then just call it from any controller.
For example:
.factory('LoadJsonFactory', function($http) {
var jsonResponse = [];
return {
fetch: function() {
return $http.get('../../test.json').then(function(data) {
jsonResponse = data;
});
},
getResponseResult: function() {
return jsonResponse;
}
}
})

Dynamic routing with Angular - how to work with this JSON?

I'm currently trying to pull in some JSON from an API with Angular's http method. I can retrieve the data fine, however I can't target specific ID's - to be clear what I'd like to do is have say /#/work/1 and /#/work/2, and be able to use the data relevant to that ID. I have looked into using $routeParams to do this but I have only gotten so far.
My JSON looks exactly like this:
[
{
"id": 1,
"title": "New post",
"body": "oh yeah"
},
{
"id": 2,
"title": "another one",
"body": "test test test",
}
]
and I'm trying to route dynamically to each ID, set up in my config app like so:
myApp.config(function($routeProvider){
$routeProvider.
when('/',{
templateUrl: 'partials/home',
controller: 'mainController'
}).
when('/work/:id', {
templateUrl: 'partials/project',
controller: 'postController'
})
});
This is linked in my view like this, as I'm using ng-repeat all of the data for this view is succesfully being printed - it runs through my JSON fine
<div class = "images" ng-repeat = 'data in projects">
<a href = "#/work/{{ data.id }}">
<p> {{ data.title }} </p>
</a>
</div>
and my controller is set up like this, as you can see I'm calling a function defined in my factory to make the http request here:
var postController = angular.module('postControllers', []);
postControllers.controller('projectController', ['$scope', '$routeParams', 'workPost',
function($scope, $routeParams, workPost) {
$scope.projectID = $routeParams.id;
workPost.getData().success(function(data){
$scope.data = data;
});
}]);
so my factory is set up like so:
var workServices = angular.module('workServices', ['ngResource']);
projectServices.factory('workPost', function($http) {
return {
getData: function() {
return $http({
url: 'somelink/todata.json',
method: 'GET'
})
}
}
});
and finally I'm trying to access the data associated with the ID in my view like so:
<p> {{ data.body }} </p>
This does not work, however repeating it or specifying which item in the array I want (like data[1].title) does
Clicking the href routes me correctly to #/work/id, so my config is fine. I'm just not sure how to get the relevant data (i.e. title, body) associated with only that ID? I was thinking it may be the way my JSON is structured, perhaps I need to nest the parameters under an object for each?
Thanks for looking.
When you do $scope.data = data;, you are setting the entire array of objects into the scope's data variable.
What you want to do is find the object with id matching the route param's id from the array and set that object as scope's data. You can use Array.prototype.find() to do just that.
workPost.getData().success(function(data){
$scope.data = data.find(function (element) {
return element.id === $scope.projectID;
});
});
Your issue is that you are working with only one endpoint which is a static resource file that has all data in it.
You need to loop over that data and filter out the one you need by matching properties in the array of objects with the $routeParams value.
When you find the match assign it to the scope

Using factory to expose a simple API

I'm trying to write a factory which exposes a simple users API. I'm new to AngularJS and I'm a bit confused about factories and how to use them. I've seen other topics but none that are a good match to my use case.
For the sake of simplicity, the only functionality I'd like to achieve is getting all users in an array and then pass them to a controller through the injected factory.
I stored the users in a json file (for now I only want to read that file, without modifying the data)
users.json:
[
{
"id": 1,
"name": "user1",
"email": "a#b.c"
},
{
"id": 2,
"name": "user2",
"email": "b#b.c"
}
]
The factory I'm trying to write should be something like this:
UsersFactory:
app.factory('usersFactory', ['$http', function ($http) {
return {
getAllUsers: function() {
return $http.get('users.json').then(
function(result) {
return result.data;
},
function(error) {
console.log(error);
}
);
}
};
}]);
And finally, the controller call would be like this:
UsersController
app.controller('UsersCtrl', ['$scope', 'usersFactory', function($scope, usersFactory){
usersFactory.getAllUsers().then(function (result) {
$scope.users = result;
});
}]);
I've searched the web and it seems like it is not really a good practice to use factories this way, and if I'd like to achieve some more functionality like adding/removing a new user to/from the data source, or somehow store the array within the factory, that wouldn't be the way to do it. I've seen some places where the use of the factory is something like new UsersFactory().
What would be the correct way to use factories when trying to consume APIs?
Is it possible to initialize the factory with an object containing the $http.get() result and then use it from the controller this way?
var usersFactory = new UsersFactory(); // at this point the factory should already contain the data consumed by the API
usersFactory.someMoreFunctionality();
I don't see anything wrong with your factory. If I understand correctly you want to add functionality. A few small changes would make this possible. Here's what I'd do (note that calling getAllUsers wipes out any changes):
app.factory('usersFactory', ['$http', function ($http) {
var users = [];
return {
getAllUsers: function() {
return $http.get('users.json').then(
function(result) {
users = result.data;
return users;
},
function(error) {
users = [];
console.log(error);
}
);
},
add: function(user) {
users.push(user);
},
remove: function(user) {
for(var i = 0; i < users.length; i++) {
if(users[i].id === user.id) { // use whatever you want to determine equality
users.splice(i, 1);
return;
}
}
}
};
}]);
Typically the add and remove calls would be http requests (but that's not what you're asking for in the question). If the request succeeds you know that your UI can add/remove the user from the view.
I like my API factories to return objects instead of only one endpoint:
app.factory('usersFactory', ['$http', function ($http) {
return {
getAllUsers: getAllUsers,
getUser: getUser,
updateUser: updateUser
};
function getAllUsers() {
return $http.get('users.json');
}
function getUser() {
...
}
function updateUser() {
...
}
}]);
That way if you have any other user-related endpoints you can consume them all in one factory. Also, my preference is to just return the $http promise directory and consume the then() in the controller or where ever you're injecting the factory.
I'm really a fan of route resolve promises. Here is John Papa's example. I will explain afterwards how to apply this to what you're doing:
// route-config.js
angular
.module('app')
.config(config);
function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'Avengers',
controllerAs: 'vm',
resolve: {
moviesPrepService: moviesPrepService
}
});
}
function moviesPrepService(movieService) {
return movieService.getMovies();
}
// avengers.js
angular
.module('app')
.controller('Avengers', Avengers);
Avengers.$inject = ['moviesPrepService'];
function Avengers(moviesPrepService) {
var vm = this;
vm.movies = moviesPrepService.movies;
}
Basically, before your route loads, you get the request data you need (in your case, your "users" JSON.) You have several options from here... You can store all that data in a Users factory (by the way, your factory looks fine), and then in your controller, just call Users.getAll, which can just return the array of users. Or, you can just pass in users from the route resolve promise, much like John Papa does in his example. I can't do it as much justice as the article he wrote, so I would seriously recommend reading it. It is a very elegant approach, IMHO.
I typically use a factory something like this:
.factory('usersFactory', ['$resource',
function($resource){
return $resource('http://someresource.com/users.json', {}, {
query: {
method:'GET',
isArray:true
}
})
}])
Which you could call with:
usersFactory.query();
As this is a promise you can still use the .then method with it too
$http is a promise that means you have to check whether your get call worked or not.
so try to implement this type of architecture in your controller
$http.get('users.json')
.success(function(response) {
// if the call succeed
$scope.users = result;
})
.error(function(){console.log("error");})
.then(function(){
//anything you want to do after the call
});

How do I load JSON objects into separate global variables using AngularJS?

I'm learning AngularJS and I have one JSON file with data that I want to load into separate variables.
My JSON file has two objects/arrays: "views" and "addressbook". I can bind the data to a $scope.variabale in the html but but that's not what i'm looking for. I would like to load the views and addressbook into a "var views" and a "var adressbook" so i can access them with jquery.
My JSON:
{
"myData": {
"views": [
{
"view": "Nieuwe activiteit",
"link": "index.html",
"active": true
},
{
"view": "Activiteiten",
"link": "activiteiten.html",
"active": false
}
],"adresboek": [
{
"Voornaam": "Ruben",
"e-mail": "ruben#e-mail.com",
"mobiel": "0612345678",
"hasFacebook": true,
"hasTwitter": true
},
{
"Voornaam": "Roos",
"e-mail": "roos#e-mail.com",
"mobiel": "0612345677",
"hasFacebook": true,
"hasTwitter": true
}
]
}
}
What i'm trying to get:
var alldata = $http.get('data/data.json').then(function(res) {
var views = res.data.mydata.views;
var adresbook = res.data.mydata.adresbook;
});
I would like to bind the data like so:
$scope.views = alldata.views;
$scope.contacts = alldata.addressbook;
Any help is appreciated :)
Why exactly do you want to put these variables on the global scope/window? This is typically advised against. You may consider creating an Angular Service. A Service allows you to access variables across different modules.
In the context of your code, where is this $http.get request occurring?
EDIT: In response to your comment:
Ok, try something like this?
myApp.controller( 'getData', function($http){
this.onLoad: function(){
$http.get('data/data.json').then(function(res) {
$scope.views = allData.views;
$scope.contacts = allData.addressBook;
}
};
this.onLoad();
};
When you're first instantiating your angular controller it will call the loadData function and set your $scope variables.
(I didn't check your $http code, I just copied what you had down).
You can access the JavaScript global scope at any point by referencing the window object. Make sure to use $apply in angular if you modify data outside off the angular digest. In your example:
var alldata = $http.get('data/data.json').then(function(res) {
window.alldata = {
"views": res.data.mydata.views,
"addressbook": res.data.mydata.adresbook
};
});
You may now access the data in jQuery, e.g.:
console.log( window.alldata.views );

Angular.js multiple rest calls scope issue

i'm a biginner when it comes to Angular.js, and i have a problem with $scope not getting additional value from one of two $resource rest calls. Here's my code:
controller: function ($scope, $modalInstance, $route) {
$scope.server = {}
$scope.submit = function () {
//AddNewServer is a $resource service
AddNewServer.post($.param({
'name': $scope.server.name,
'ip': $scope.server.ip,
'port': $scope.server.port
}));
//ServerStats is a $resource service
ServerStats.postServerStats(function success(data) {
$scope.server.bytesIn = data.returnValue.bytesIn
}, function err(err) {
console.log("Error: " + err)
})
$modalInstance.dismiss('cancel');
$route.reload()
//BELLOW LOG RETURNS Object {name: "asd", ip: "asd", port: 2} NO bytesIn
console.log($scope.server)
}
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
$route.reload()
};
}
Question is how do i add bytesIn from my other service call into my server object? I'm sure it a pretty obvious thing but i'm still in learning phase. Thanks in advance.
Your postServerStats() call is asynchronous, so it's likely that your success function isn't being called before the console.log($scope.server) statement.
Put console.log($scope.server) in your success function, after you assign $scope.server.bytesIn.
Perhaps you mean to do more work in your postServerStats() callback?
Or better yet, look into angular promises

Categories