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
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.
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.
Fairly new to AngularJS and first time posting here. On a data entry page, I display a select list where the options in the list come from an array. The array can have items added which need to be reflected in the select list. I can see the items are being added to the array but the options list is not updating without reloading the entire page.
How does one update or refresh the select options list after it has been initialized?
<select ng-model="VendorsController.selectedVendor.Items.selectedItem.locno"
style="width:300px;"
size="1"
ng-options="opt.value as opt.label for opt in VendorsController.selectedVendor.Locations"
required >
</select>
The new item is saved on the server with a callback and the promiss has the following statement to push the new (Location) item onto the array.
_this.selectedVendor.Locations.push(_this.selectedVendor.Locations.selectedLoc);
The array reflects the new item was pushed correctly but the options list does not show it. Any suggestions?
Here is some additional code about where I call on the push().
this.SaveLocation = function(){
$http.post('cfm/callback4.cfmx', _this.selectedVendor.Locations.selectedLoc ).
success(function (data, status, headers, config) {
// if new location pk now reflects valid data
_this.selectedVendor.Locations.selectedLoc = data;
//console.log(data);
//console.log(status);
//console.log(config);
// add location to list
if (_this.selectedVendor.Locations.addNew) {
_this.selectedVendor.Locations.selectedLoc.value = data.locno
// this push is not showing up on the locations options
_this.selectedVendor.Locations.push(_this.selectedVendor.Locations.selectedLoc);
}
// must reset after promiss is returned
_this.selectedVendor.Locations.originalLoc = undefined;
_this.selectedVendor.Locations.selectedLoc = undefined; // null;
_this.selectedVendor.Locations.addNew = false ;
}).
error(function (data, status, headers, config) {
// log error
console.log(data)
console.log(status)
console.log(config)
});
}; // end SaveLocation
And my form button that calls the above function.
<button ng-show="VendorsController.selectedVendor.Locations.selectedLoc" class="btn btn-default" data-dismiss="modal" ng-click="VendorsController.SaveLocation()">Save</button>
Here is how I create the controller... A friend that understands more about AngularJS suggested I use the _this when I'm calling on properties within functions of the controller... something to do with scope or at least that is the way I understood it.
app.controller('VendorsController', ['$scope', '$window', '$http', '$routeParams', '$location', 'state','$timeout','$upload', function ($scope, $window, $http, $routeParams, $location, state, $timeout,$upload) {
var _this = this; // the controller
this.name = "VendorsController";
this.params = $routeParams;
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'm new to angularjs and have been attempting to use the angularui modules to build an accordion. For each accordion header I have a nested tag to which calls my service factory. The service factory returns data according to the id and updates my inner accordion content however it updates it for all of the accordion headers. So in other words, clicking on an accordion header will load the same content for all accordion divs. I would like it to return only to the clicked header. I believe I need more help in understanding the scope of my factory service. So my question is if someone can help me understand how to get my service factory to only update it's caller.
my html:
<accordion close-others="false">
<accordion-group ng-repeat="dept in departments">
<accordion-heading>
<span ng-controller="DeptHeaderCtrl" ng-click="loadEmps(dept.DeptID)">
{{dept.DepartmentName}} ({{dept.DepartmentShortName}})
</span>
</accordion-heading>
<div ng-controller="departmentList">
<div ng-repeat="emp in deptemps">
{{emp.Name_First}}
</div>
</div>
</accordion-group>
angularjs factory service code:
app.factory('DeptFactory', function ($http, $rootScope) {
var sharedDeptEmpList = {};
sharedDeptEmpList.EmpList = '';
sharedDeptEmpList.fetchDeptEmps = function (deptid) {
var dept = { "userid": userid, "deptid": deptid };
$http({ method: 'POST', url: 'api/Fetch/DeptEmployees/', data: dept }).
success(function (data, status, headers, config) {
EmpList = data;
sharedDeptEmpList.broadCastEmpList();
}).
error(function (data, status, headers, config) {
alert('error');
});
};
sharedDeptEmpList.broadCastEmpList = function () {
alert('broadcasting');
$rootScope.$broadcast('handleBroadcast');
};
return sharedDeptEmpList;
});
Angularjs controller that receives broadcast:
app.controller('departmentList', function ($scope, $http, DeptFactory) {
$scope.init = function (p_userid) {
userid = p_userid;
};
$scope.$on('handleBroadcast', function () {
alert('broadcast received');
$scope.deptemps = EmpList;
});
});
Each directive is just some javascript that associates a DOM node with a given scope. The same way you think of the DOM tree, you can think of a "scope tree". On the other hand, services are just singleton objects that can be injected and used anywhere. They have no implicit relationship to the DOM/scope tree.
By injecting $rootScope into your DeptFactory, you are giving it access to the entire scope tree. When you call $broadcast(), you are sending an event throughout the entire tree, beginning at the root, and then propagating outwards to all of the leaf scopes.
I don't see enough of your code to completely understand what's happening, but I'm guessing that you're seeing all of your accordion divs change because they are all receiving your $broadcasted event, and reacting to it the same way. I would suggest you $broadcast some sort of ID value to identify which div you are trying to change. Then, when you handle this event in each accordion, check the ID against the accordion's ID to see it should change or not.
Does this help?