this is my first question so I apologize if I break any protocol. Anyway. I'm trying to have my ng-repeat update in the then clause of my service call. USE CASE: User logs in via an ajax call and then a call is made for that users 'items'. For example, here is what is in my controller:
appControllers.controller('UserCtrl', function($rootScope, $scope, sessionService, alertService, $modal, itemService){
$scope.login = function() {
$scope.loggedInUsersItems = [];
sessionService.login($scope.account).then(
function(data) {
alertService.add('success', 'You are logged in.');
itemService.getItemsByUserName($rootScope.loggedinUserName).then(
function(data){
$scope.loggedInUsersItems = data.data;
}
)
},
function() {
alertService.add('danger', 'There was a problem logging in. Check your credentials and try again.');
}
)
};
...
I think I looked at every question on stack and elsewhere and I keep on spinning my wheels. I tried doing a $scope.$apply(), but that resulted in the expected digest already happening error. I also tried .push() and iterating and pushing, as well as placing the itemService.getItemsByUserName in a chained then with no luck. I'm new to Angular, but I believe I understand $scope is not to be in the service layer, so I'd like to avoid that.
I should mention that the login process is working great, and I do set some $rootScope items in the login service and I see my view updating as a result of successful login (such as the authenticated div showing and a success message). The only thing that doesn't update is my ng-repeat. EDIT to provide more details: The snippet below is already on the view when the login is fired. Login is done using a modal - the user fills in credentials and hits login and the controller is fired. Not sure if this information changes anything but I thought I'd try giving more details.
<div ng-show="authenticated">
...
<tr class="text-left" ng-repeat="item in loggedInUsersItems">
<td>{{ item.itemName }}</td>
<td>{{ item.itemType }}</td>
</tr>
...
</div>
Here is my the snippet in my service:
...
service.getItemsByUserName = function(username){
return $http.get('useritems', {params: { username: username }})
};
...
use reference to the current scope to keep your controller in current state.
Try AngularJS’s Controller As and the vm Variable
appControllers.controller('UserCtrl', function($rootScope, $scope, sessionService, alertService, $modal, itemService){
var vm=this;
vm.login = function() {
vm.loggedInUsersItems = [];
sessionService.login(vm.account).then(
function(data) {
alertService.add('success', 'You are logged in.');
itemService.getItemsByUserName($rootScope.loggedinUserName).then(
function(data){
vm.loggedInUsersItems = data.data;
}
)
},
function() {
alertService.add('danger', 'There was a problem logging in. Check your credentials and try again.');
}
)
};
HTML
<div ng-controller="UserCtrl as vm">
<tr class="text-left" ng-repeat="item in vm.loggedInUsersItems">
<td>{{ item.itemName }}</td>
<td>{{ item.itemType }}</td>
</tr>
The way I see it you're doing the following:
sessionService.login($scope.account)
returns a promise that resolves and your .then().. code executes. Presumably you see this happen and it's okay. Your alertService fires the .add method correctly. You call the following line which also returns a promise:
itemService.getItemsByUserName($rootScope.loggedinUserName)
This too resolves at your .then() and you try to set the bound variable to the new value retrieved from the promise:
$scope.loggedInUsersItems = data.data;
Could you log console.log($scope.loggedInUserItems) to see if it correctly gets the value which is a list of objects each of which contain a property called itemType and itemName? This last part is crucial because if the formatting of your returned object doesn't match your html it won't end up showing anything.
Related
I am showing employee record using AngularJS. I am using two views to show data, I am using two views emp-box.htm and its respective controller(empController) and in this controller employeeBoxController I am fetching data from service, I want the result which is obtained in employeeBoxController to be used in empController and show in view (emp-list.htm), I created a service eService
app.service('dataService',function() {
var s = {};
this.setData = function(data,key) {
s[key]=data;
},
this.getData = function(key) {
return s[key];
}
this.hello = function() {
return 'hello';
}
})
for fetching result and setting data in employeeBoxController and getting in empController but when I fetch the data using console.log(dataService.getData('result')); in empController i get undefined
The employeeBoxController is
app.controller("employeeBoxController", ['$scope', 'employeeService',
'dataService', function($scope, employeeService, dataService) {
$scope.getEmployeeDetails = function(eid) {
$scope.isLoading = false;
employeeService.getDetails($scope.eid).then(function(result) {
dataService.setData(result, 'result');
$scope.isLoading = true;
console.log(dataService.getData('result'));
})
}
}])
The empController is :-
app.controller("empController", ['$scope', 'employeeService', 'dataService',
function($scope, employeeService, dataService) {
$scope.result = dataService.getData('result');
//console.log(dataService.hello());
console.log(dataService.getData('result'));
console.log(dataService.hello());
}
])
The service class employeeService is :-
app.config(["employeeServiceProvider",function(employeeServiceProvider){
employeeServiceProvider.config('http://localhost:8080/pos');
}]);
app.provider("employeeService",function(){
var myurl='';
this.config=function(eurl){
myurl=eurl;
}
this.$get=['$http','$log',function($http,$log){
var employeeobj={};
employeeobj.getDetails=function(eid){
return $http.get(myurl+'/getEmployees/'+eid);
}
return employeeobj;
}];
});
emp-box.htm is:-
<div>
Enter the id: <input type="text" ng-model="eid"/>
<button ng-click="getEmployeeDetails()">show</button>
</div>
emp-list.htm is:-
<div class="panel panel-primary">
<div class="panel-body" style="text-align:center; margin:0 auto">
<h3>Employee Data</h3>
</div>
</div>
<div class="panel panel-primary">
<div class="panel-body">
<!-- <div ng-show="!isLoading" style="color:red">
<span class="glyphicon glyphicon-time"></span>Loading...
</div>-->
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>empno</th>
<th>salary</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="oemp in result.data">
<td>{{oemp.eid}}</td>
<td>{{oemp.name}}</td>
<td>{{oemp.empno}}</td>
<td>{{oemp.sal}}</td>
</tr>
</tbody>
</table>
</div>
</div>
From what I understand you're trying to cache your API results in another service, so you don't have to call the API again in another controller. Also, empController is executed first, and when you're doing dataService.getData('result') the API response of setting it isn't received yet which in turn is called from another service. I would suggest you combine these 2 services, so that instead of caching the exact values in service, you can cache the API call itself, and in case where cache doesn't contain your data, make an API call and cache it.
Here's something I'd make, say CacheAPIService.
app.factory('CacheAPIService', ['$http', function($http) {
var cache = {};
return {
get: function(api) {
if(angular.isUndefined(cache[api])) {
cache[api] = $http.get(api); //storing in cache while making API call
}
return cache[api]; //Return from cache
},
clear: function(api) {
delete cache[api];
}
}
}]);
So, whenever you need to make an cache an API call, use this service in addition to making API call it'll also cache it. And, if it is already cached, no new API call is made. Benefits is you'll never run into cases where it return's undefined, as you are returning promise.
In your 1st controller, updated code becomes:
app.controller("employeeBoxController", ['$scope', 'CacheAPIService', function($scope, CacheAPIService) {
$scope.getEmployeeDetails = function(eid) {
$scope.isLoading = true;
var endpoint = 'api/endpoint/'+$scope.eid; //Replace with your API endpoint
CacheAPIService.get(endpoint).then(function(result) {
$scope.isLoading = false;
console.log(dataService.getData('result'));
})
}
}]);
Here, 1st API call is made and is cached. Take a look at your other controller:
app.controller("empController", ['$scope', 'CacheAPIService', function($scope, CacheAPIService) {
CacheAPIService.get(endpoint).then(function(data) {
var endpoint = 'api/endpoint/'+$scope.eid; //your API endpoint
console.log('data =', data);
});
}]);
Here, you still use the same service, but it'll be cached, and if not cached, it'll make API call and cache that promise. Here I've directly used API endpoint as key for storing in cache. This way you don't have to provide unique keys every time, as endpoint itself is unique.
Note that in cases where you want to delete cached data, when making POST or PUT call, you can call CacheAPIService.clear(apiEndpoint) to clear details from cache.
$http.get will return a promise.
To correctly set data to dataService:
employeeService.getDetails($scope.eid).then(function(result) {
dataService.setData(result.data, 'result'); // note .data
$scope.isLoading = true;
console.log(dataService.getData('result'));
})
Storing the promise in your "cache" service is not the best option.
I expect getEmployeeDetails() function is going to route to empController, so if you still store $promise in your service as you stated, you can do.
dataService.getData('result').then(function(result){
$scope.result = result.data;
});
When $scope.result is set to $scope, ng-repeat will start iterating over.
With the first change suggested, you just don't need to touch empController but just the ng-repeat directive emp-list.html with:
<tr ng-repeat="oemp in result">
Refactor the http call with factory recipe and $resource.
https://docs.angularjs.org/api/ngResource/service/$resource
That is easier and quicker than writing providers.
I want to know how to dynamically change an $http call so that the $http request URL differs based on the element that is clicked in an ng-repeat list. But I'm stuck.
Currently, I have an ng-repeat set up on my index page:
<div ng-repeat="team in nt.getStandings">
<h2>{{team.team_name}}</h2>
<p>Team ID = {{team.team_id}}</p>
</div>
The variable getStandings is taken from an API via an $http call. Like so:
In StandingsService
return $http.get(
'http://api.com/standings/1005?Authorization=xxxx'
)
.success(function(data) {
return data;
})
.error(function(err) {
return err;
});
And then StandingsService is attached to the getStandings variable in my controller.
"1005" is a property which calls a specific array, in this case a particular sporting competition, from an array of competitions.
So, on my index page I'm using ng-repeat to list all teams within that competition.
As you can see on the html above, I have linked each team so that it dynamically generates a URL which appends the team_id to the end, which using $routeParams I define as the variable whichTeam.
Team Details Page
<h1>Dynamic Team ID = {{whichTeam}}</h1>
This works fine, the team ID is generated dynamically according the team that is clicked.
Just like 'StandingsService' above, I have another service called 'TeamService' which makes an $http request to pull team data. Currently though it is set up statically to make a call to one individual team - I want to make the service take in the whichTeam variable so that the call changes depending on which team was clicked.
This is the static team $http request (I've broken the URL apart and concatenated to make it clearer):
return $http.get(
'http://api.com/team/' + '16110' + '?Authorization=xxxx'
)
I want the 16110 part, which refers to ONE team, to be a the whichTeam variable, allowing it to pull in the correct individual team data, but I don't know how to write this (or indeed if it's possible).
I hope I've been clear - happy to clarify further if needed. Thanks in advance.
Make a factory:
app.factory("DataService", ["$http", function($http) {
return {
getTeamDetailsById: function(teamId) {
return $http.get('path/to/api' + teamId + '?Auth=xxxx')
}
};
}]);
Use it in a controller:
app.controller("MainCtrl", ["$scope", "DataService", function($scope, DataService) {
$scope.teamDetails = {};
$scope.getTeamDetailsById = function(event, teamId) {
//prevent click navigation
event.preventDefault();
//call factory service
DataService.getTeamDetailsById(teamId).then(function(response) {
//success callback
$scope.teamDetails = response.data;
}, function(response) {
//an error has occurred
});
}
}]);
In the ng-repeat element:
<div ng-repeat="team in teams">
<a href ng-click="getTeamDetailsById($event, team.team_id)">{{team.team_name}}</a>
</div>
The above assumes you have only one state and are storing in only one controller. If you want to use different states usving $stateProvider, then you'd have to use parameters, by making use of ui-sref and passing in team Id.
If indeed you are using $states and parameters, do this:
<a href ng-click="goToState($event, team.team_id)">{{ team.team_name }}</a>
$scope.goToState = function(e, teamId) {
$state.go("teamDetailsState", { "teamId": teamId });
}
The following is a mock up of what I have using data from the angular site. The goal is to remove any items in scope2 (newdevices) that already exist in scope1 (devices). I have a working model but do not feel it is the best method.
I have a controller that draws data from two different sources. For simplicity i have made the first scope static, whereas the second will take data via httpget from the angular site and this is initiated from a button click. (My prod code needs to use a button so i can inject variables into the call)
app.controller('customersCtrl', function($scope, $http) {
//Example static data for scope 1
$scope.devices = [
{"Name":"Around the Horn","City":"London","Country":"UK"},
{"Name":"B's Beverages","City":"London","Country":"UK"},
{"Name":"Chop-suey Chinese","City":"Bern","Country":"Switzerland"}
];
//scope 2 data from angular example site that is initiated from a button
$scope.loaddata = function() {
$http.get("http://www.w3schools.com/angular/customers_mysql.php")
.then(function (response) {
$scope.newdevices = response.data.records;
});
}
});
I then have a filter that compares the scopes:
app.filter('matcher', function() {
return function(newdevices, devices) {
var array2Ids = []
angular.forEach(devices, function(value, index) {
array2Ids.push(value.Name);
})
return newdevices.filter(function(val) {
return array2Ids.indexOf(val.Name) === -1;
})
}
});
Lastly, I apply the filter to my ng-repeat call:
<div ng-app="myApp" ng-controller="customersCtrl">
<button ng-click="loaddata()">load me</button>
<table>
<tr ng-repeat="x in newdevices | matcher: devices">
<td width="300px">{{ x.Name }}</td>
<td width="150px">{{ x.City }}</td>
<td width="100px">{{ x.Country }}</td>
</tr>
</table>
</div>
As mentioned, this currently works, but as I am already calling the second scope httpget from a function, is there a way I can integrate the filter into the loaddata function, so it happens all at once and can eliminate the need to filter on the ng-repeat stage?
I am still relatively new to this and have not yet been able to accomplish it.
you don't need an angular "filter". Just filter the response data before it gets assigned to $scope.newdevices. below code has been tested, but you get the idea.
$scope.loaddata = function() {
$http.get("http://www.w3schools.com/angular/customers_mysql.php")
.then(function (response) {
//do things here, i.e.
var array2Ids = [];
angular.forEach(devices, function(value, index) {
array2Ids.push(value.Name);
});
$scope.newdevices = response.data.records.filter(function(val) {
return array2Ids.indexOf(val.Name) === -1;
});
});
}
Controllers and services can retrieve filters using the $filter service.
var matcherFn = $filter('matcher');
var result = marcherFn(newdevices, devices);
AngularJS filters can be used both in templates and JavaScript.
The example in the Docs:
angular.module('filterExample', [])
.controller('MainCtrl', function($scope, $filter) {
$scope.originalText = 'hello';
$scope.filteredText = $filter('uppercase')($scope.originalText);
});
For more information, see AngularJS $filter Service API Reference.
I have recently started to learn angularjs using restangular to talk to my restfull API (sails). The problem I have stumbled upon is that the ng-repeat does not update after I change the list in the scope.
Controller:
app.controller('UsersCtrl', ['UsersSvc', '$scope', function(UsersSvc, s) {
UsersSvc.getList().then(function (new_users) {
s.users = new_users;
})
s.destroy = function (user) {
user.remove().then(function () {
s.users = _.without(s.users, user);
});
}
}]);
Service:
app.factory('UsersSvc', function(Restangular) {
return Restangular.all('users');
});
Template:
<div ng-controller="UsersCtrl">
...
<tr ng-repeat"user in users">
<td>{{user.firstName}}</td>
<td>{{user.lastName}} </td>
<td>{{user.emailAddress}}</td>
<td>{{user.age}}</td>
</tr>
...
</div>
When I inspect the scope the array of restangular objects is correctly assigned to the scope of the users controller but the template refuses to update.
Thanks in advance
AngularJS (and javascript) care about references vs. overwrites. So to be safe I always set my scope variables initially, and then update using angular.copy() or Restangular.copy() (if it's a Restangular object being set).
Below is how I'd refactor your controller to ensure bindings + digest cycles stay connected.
(Please note I renamed s to the "traditional" $scope for easier reading for everyone else)
app.controller('UsersCtrl', ['$scope', 'UsersSvc', 'Restangular', function($scope, UsersSvc, Restangular) {
// we're expecting a list, so default as array
$scope.users = [];
UsersSvc.getList().then(function (new_users) {
// In normal $resource/ng projects use: angular.copy(src, dst) but
// Restangular has an issue when using angular.copy():
// https://github.com/mgonto/restangular/issues/55
// so use their version of copy():
Restangular.copy(new_users, $scope.users);
});
$scope.destroy = function (user) {
user.remove().then(function () {
$scope.users = _.without($scope.users, user);
});
}
}]);
I have multidimensional json:
{"cat":
{"total":12,"per_page":3,"current_page":1,"last_page":4,"from":1,"to":3,
"data":[{"id":1,"emlakKategori":"a"},{"id":2,"emlakKategori":"b"},{"id":3,"emlakKategori":"c"}]}}
And i am reaching to data via ng-repeat like this:
<tr ng-repeat="kat in kategoriList.cat.data">
<td>{{ kat.id }}</td>
<td>{{ kat.emlakKategori }}</td>
My service is:
app.factory('KategoriData',['$resource', 'api.config', function($resource, config) {
return $resource(config.apiBasePath + 'kategori',{}, {
query: {
isArray: false,
method: 'GET'
}
});
And my controller:
kategori.controller('KategoriListCtrl',['$scope', 'KategoriData', function($scope, KategoriData) {
$scope.kategoriList = KategoriData.query();
}
I wonder how can i assign data to variable inside controller and reach it in view with directly kategoriList instead of kategoriList.cat.data
Thanks.
You can make the assignment in your controller in the success callback of your query.
kategori.controller('KategoriListCtrl',['$scope', 'KategoriData', function($scope, KategoriData) {
KategoriData.query({},
function(data){
$scope.kategoriList = data.cat.data;
});
}
Actually your example is not valid anymore due to this (since v1.2.0-rc.3 )
deprecate promise unwrapping means that you can not bind a scope value to a promise and when the promise will be resolved a $digest will automatically unwrap it.
You should use a callback:
kategori.controller('KategoriListCtrl',['$scope', 'KategoriData', function($scope, KategoriData) {
KategoriData.query({}, function(data){
$scope.kategoriList.cat.data = data;
})
}