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.
Related
This question already has an answer here:
AngularJS Variable in service is not updating in view?
(1 answer)
Closed 5 years ago.
I'm new to AngularJs, and I've got a little problem without using $scope in my factory module.
I've got a GET function that's used by a controller via the factory, and it's supposed to return a list from my db as json
and show it on the html using ng-repeat. For a reason I couldn't find a solution for, it doesn't. My only guess was that there's a problem regarding databinding from the factory to the html.
How do I bind the returned json to a variable and bind that variable to my html?
This function worked before I created the factory, but without using the $scope
I'm pretty lost.
I hope I was able to explain myself correctly.
Factory:
(function() {
angular.module("myApp").factory('appServicesProvider',function( $http ) {
var restURL = "http://localhost:8080/Project/rest/api/";
function getAllRows(allRows){
$http.get(restURL).then(
function(response){
allRows = response.data.coupon;
}
);
}
return{getAllRows:getAllRows}
Controller:
(function() {
angular.module("myApp")
.controller("AdminController",function($scope, $http,
appServicesProvider) {
// I know that this method of scoping a service is not best and/or recommended,
//it just works better for me with the admin controller.
$scope.appServicesProvider = appServicesProvider;
HTML:
<div id="getAllRowsDiv">
<table class="table table-hover">
<thead>
<tr>
// list details
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="coupon in allRows">
// list
</tr>
</tbody>
</table>
<button class="btn btn-success" ng-
click="appServicesProvider.getAllRows()" >Get All Rows</button>
</div>
Use $q
(function() {
angular.module("myApp").factory('appServicesProvider',function( $q, $http ) {
var restURL = "http://localhost:8080/Project/rest/api/";
function getAllRows(allRows){
var deffer = $q.defer();
$http.get(restURL).then(
function(response){
deffer.resolve(response.data.coupon;)
},function(error){
deffer.reject(error)
}
);
return deffer.promise
}
return{getAllRows:getAllRows}
and in controller
$scope.getRows = function(){
appServicesProvider.getAllRows().then(function(res){
$scope.allRows = res;
},function(err){
alert(err)
})
}
$scope.getRows();
In the service, return the http promise:
app.factory('appServices',function( $http ) {
return { getAllRows:getAllRows }
var restURL = "http://localhost:8080/Project/rest/api/";
function getAllRows(){
͟r͟e͟t͟u͟r͟n͟ $http.get(restURL)
.then(function(response){
var allRows = response.data.coupon;
͟r͟e͟t͟u͟r͟n͟ allRows;
});
}
});
In the controller, use the .then method to extract the data:
app.controller("AdminController",function($scope, $http, appServices) {
var promise = appServices.getAllRows();
promise.then(function(allRows) {
$scope.allRows = appRows;
});
});
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.
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.
I'd like to init a filtered list with angularjs, I don't know how to access to ng-init variables...
Edit #1 :
app.controller("AgreementsController", function($scope, $http, $filter) {
$scope.agreements = [];
$http.get('/api/agreement').success(function(data, status, headers, config) {
$scope.agreements = data.agreements;
$scope.filteredAgreements = $filter('filter')($scope.agreements,
{number: $scope.search});
});
<tbody ng-init="filteredAgreements = (agreements | filter:{number:search})">
<tr ng-repeat="agreement in agreements | filter:{number:search} | limitTo:5">
<td>{{agreement.number}}</td>
</tr>
</tbody>
{{filteredAgreements.length}} <!-- 291 even if I put "65" into the search -->
You get agreements via AJAX, that means at the time the ngInit directive runs they are empty and therefore filteredAgreements will be empty, too. You have to wait until the AJAX call returns.
To use an angular filter in JavaScript you need the $filter service. You call it with the name of the filter to get the filter, and then call that function with the data to be filtered (in your case agreements) and the desired arguments.
app.controller("AgreementsController", function($scope, $http, $filter) {
$scope.agreements = [];
$http.get('/api/agreement').success(function(data, status, headers, config) {
$scope.agreements = data.agreements;
$scope.filteredAgreements = $filter('filter')($scope.agreements,
{number: $scope.search});
});
References:
$filter
The "filter" filter
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);
});
}
}]);