Calling a function from Angular.js Controller - javascript

Following is a simple Angular.js code snippet :
XApp.controller('ProductsController', function ($scope, GetProductsForIndex, $http) {
console.log('Step 1');
var Obj = new Object();
Obj.PAGEINDEX = 1;
Obj.PAGESIZE = 25;
Obj.SPNAME = "index_get_products";
Obj.PAGECOUNT = null;
Obj.COUNTRYCODE = 'in'
$scope.data = GetProductsForIndex.query({ parameters : Obj }, function () {
console.log($scope.data);
$scope.products = $scope.data;
});
})
XApp.factory('GetProductsForIndex', function ($resource) {
console.log('Step 2');
return $resource('api/index/:object?type=json', {}, { 'query': { method: 'GET', isArray: true } });
});
I am trying to implement infinite scroll using http://binarymuse.github.io/ngInfiniteScroll/
In their demo here http://binarymuse.github.io/ngInfiniteScroll/demo_basic.html they are calling the loadMore() function.
In my case i want to execute the following on scroll :
$scope.data = GetProductsForIndex.query({ parameters : Obj }, function () {
console.log($scope.data);
$scope.products = $scope.data;
});
and increment the pageIndex Obj.PAGEINDEX = 1 by 1. How am i supposed to do that? Today is my 3rd day with Angular.js.

You need to implement a loadMore function like this inside your controller ,
XApp.controller('ProductsController', function ($scope, GetProductsForIndex, $http) {
function loadData($scope, obj){
$scope.products.push( GetProductsForIndex.query({ parameters : Obj }, function () {
}));
}
console.log('Step 1');
var Obj = new Object();
$scope.products=[];
Obj.PAGEINDEX = 1;
Obj.PAGESIZE = 25;
Obj.SPNAME = "index_get_products";
Obj.PAGECOUNT = null;
Obj.COUNTRYCODE = 'in'
loadData($scope, Obj);
})

Related

bind controllers to service with ajax promise

I'm new to angularjs. In my webapp I'm trying to work with user contacts as follows.
SERVICE
app.service('Contacts', function ($http,$timeout,$q) {
return {
getData: function() {
var defer = $q.defer();
$http.get('../ListContacts')
.success(function(data) {
defer.resolve(data);
});
return defer.promise;
}
}
});
ContactsController, OtherControllers
$scope.contactsBook = {};
...
Contacts.getData().then(function(data) {
$scope.contactsBook = data;
});
I found the above method somewhere in SO itself. I used it because I don't want to use separate module for Contacts.
I can get data at page load. I can update my contacts at server through ajax posts (from ContactsController). Now I only need a way to update(/refresh) the list automatically in all controllers. How can I achieve that.
I found these three links related but being a newbie I'm unable to figure my way out.
While it is understandable that you may not want to update your current architecture, it may be necessary to adjust your calls slightly if you want to be able to easily share data between controllers via a service.
One flexible approach is to store the data in your service and register watchers in each controller. This allows you to call the service update from one controller (the Contacts controller) and have the change be reflected in all consuming controllers. Note the service is mocked.
You can find the working plunker example here.
Contacts Service:
var app = angular.module('app', []);
app.service('contactsService', function ($http) {
var contacts = [];
return {
loadData: function() {
var mockGet = $q.defer();
var data = [
{ id: 1, name: 'Jack' },
{ id: 2, name: 'Jill' }
];
contacts = data;
mockGet.resolve(contacts);
return mockGet.promise;
},
retrieveNewData: function() {
var mockGet = $q.defer();
var data = [
{ id: 1, name: 'Jack' },
{ id: 2, name: 'Jill' },
{ id: 3, name: 'Bob' },
{ id: 4, name: 'Susan' }
];
contacts = data;
mockGet.resolve(contacts);
return mockGet.promise;
},
getContacts: function () {
return contacts;
}
}
});
Contacts Controller:
app.controller('ContactsCtrl', ['$scope', 'contactsService',
function ($scope, contactsService) {
var vm = this;
vm.contacts = [];
vm.loadData = loadData;
vm.retrieveNewData = retrieveNewData;
$scope.$watch(angular.bind(contactsService, function () {
return contactsService.getContacts();
}), function (newVal) {
vm.contacts = newVal;
});
function loadData() {
contactsService.loadData();
}
function retrieveNewData() {
contactsService.retrieveNewData();
}
}
]);
Other Controller:
app.controller('OtherCtrl', ['$scope', 'contactsService',
function($scope, contactsService) {
var vm = this;
vm.contacts = [];
$scope.$watch(angular.bind(contactsService, function () {
return contactsService.getContacts();
}), function (newVal) {
vm.contacts = newVal;
});
}
]);
You can do
Contacts.getData().then(function(data) {
$scope.contactsBook = data;
$scope.$emit('contacts:updated', data);
});
And then, where you need to notify the controller about the update:
$rootScope.$on('contacts:updated', function(e, contacts) {
$scope.contacts = contacts;
});
Another approach
The service is holding the current contacts list
app.service('Contacts', function ($http,$timeout,$q) {
this.currentList = [];
this.getData = function() {
var defer = $q.defer();
$http.get('../ListContacts')
.success(function(data) {
defer.resolve(data);
});
return defer.promise;
}
});
In your controller:
Contacts.getData().then(function(data) {
$scope.contactsBook = data;
Contacts.currentList = data;
});
In other controller:
controller('AnotherController', function($scope, Contacts) {
$scope.contacts = Contacts.currentList;
});
If you are going to return an object literal you will need to turn your .service() into a .factory() module . In this case I'll be using a service module .
Example
Your service .
app.service('Contacts', function ($http,$timeout,$q) {
var Contacts = this;
contacts.getData = function() {
var defer = $q.defer();
$http.get('../ListContacts')
.success(function(data) {
defer.resolve(data);
});
return defer.promise;
}
}
return Contacts;
});
You will then need to inject this server into your ContactsController .
app.controller('ContactsController', function(Contacts){
$scope.data = null;
$scope.init = function(){
Contacts.getData().then(function(response){
$scope.data = response;
})
}
})
now data can be used in dom
Example
<li ng-repeat="x in data">{{x.name}}</li>

Why am I getting "Incorrect injection token!" in this angular application code?

I'm trying to setup a restful API interface via AngularJS with the following code:
'use strict';
(function(angular) {
function ApiAction($resource, ResourceParameters) {
return $resource(ResourceParameters.route,
{ },
{ api_index: {
method: ResourceParameters.method,
isArray: true
}
});
return $resource(ResourceParameters.route,
{ },
{ create: {
method: ResourceParameters.method,
isArray: true
}
}
);
}
function ResourceParameters($scope) {
var factory = {};
factory.method = '';
factory.route = '';
factory.SetMethod = function(method) {
factory.method = method;
}
factory.SetRoute = function(route) {
factory.route = route;
}
return factory;
}
function superheroCtr($scope, ApiAction, ResourceParameters) {
$scope.superheroSubmit = function() {
// ApiAction.create({}, { superhero_name: $scope.superheroName, age: $scope.superheroAge });
angular.forEach($scope.superheroes, function(hero) {
// ApiAction.create({}, { superhero_name: hero.superhero_name, age: hero.age });
});
};
var heroesResources = ResourceParameters($scope).SetRoute('/api/');
var heroes = ApiAction.api_index({}, heroesResources);
$scope.superheroes = [];
heroes.$promise.then(function(data) {
angular.forEach(data, function(item) {
$scope.superheroes.push(item);
});
}, function(data) {
//if error then...
});
$scope.appendSuperheroFields = function() {
var i = $scope.superheroes.length + 1;
$scope.superheroes.push({"id": i, age: "", superhero_name: "" })
}
}
var superheroApp = angular.module('superheroApp', ['ngResource']);
superheroApp.controller('superheroCtr', ['$scope', 'ApiAction', 'ResourceParameters', superheroCtr]);
superheroApp.factory('ResourceParameters', ['$scope', ResourceParameters]);
superheroApp.factory('ApiAction', ['$resource', ResourceParameters, ApiAction]);
})(angular);
Yet, when I run it I get the following error:
Error: [$injector:itkn] Incorrect injection token! Expected service name as string, got function ResourceParameters($scope)
Why is this?
Simply you can not inject $scope OR you can not have access to $scope
inside a factory
Your problem is at this line
superheroApp.factory('ResourceParameters', ['$scope', ResourceParameters]);
You need to replace that line with
superheroApp.factory('ResourceParameters', [ResourceParameters]);
Factory
function ResourceParameters() { //<--removed $scope from here
var factory = {};
factory.method = '';
factory.route = '';
factory.SetMethod = function(method) {
factory.method = method;
}
factory.SetRoute = function(route) {
factory.route = route;
}
return factory;
}
Update
Additionally you should correct the declaration of ApiAction where ResourceParameters should be placed inside ' single qoutes
superheroApp.factory('ApiAction', ['$resource', 'ResourceParameters', ApiAction]);

AngularJS unit tests: Initialize scope of a directive's controller

I have the following code for a directive using a separated controller with the "controller as" syntax:
'use strict';
angular.module('directives.featuredTable', [])
.controller('FeaturedTableCtrl',
['$scope',
function ($scope){
var controller = this;
controller.activePage = 1;
controller.changePaginationCallback =
controller.changePaginationCallback || function(){};
controller.density = 10;
controller.itemsArray = controller.itemsArray || [];
controller.metadataArray = controller.metadataArray || [];
controller.numberOfItems = controller.numberOfItems || 0;
controller.numberOfPages = 1;
controller.options = controller.options || {
'pagination': false
};
controller.changePaginationDensity = function(){
controller.activePage = 1;
controller.numberOfPages =
computeNumberOfPages(controller.numberOfItems, controller.density);
controller.changePaginationCallback({
'page': controller.activePage,
'perPage': controller.density
});
};
controller.getProperty = function(object, propertyName) {
var parts = propertyName.split('.');
for (var i = 0 ; i < parts.length; i++){
object = object[parts[i]];
}
return object;
};
controller.setActivePage = function(newActivePage){
if(newActivePage !== controller.activePage &&
newActivePage >= 1 && newActivePage <= controller.numberOfPages){
controller.activePage = newActivePage;
controller.changePaginationCallback({
'page': controller.activePage,
'perPage': controller.density
});
}
};
initialize();
$scope.$watch(function () {
return controller.numberOfItems;
}, function () {
controller.numberOfPages =
computeNumberOfPages(controller.numberOfItems, controller.density);
});
function computeNumberOfPages(numberOfItems, density){
var ceilPage = Math.ceil(numberOfItems / density);
return ceilPage !== 0 ? ceilPage : 1;
}
function initialize(){
if(controller.options.pagination){
console.log('paginate');
controller.changePaginationCallback({
'page': controller.activePage,
'perPage': controller.density
});
}
}
}]
)
.directive('featuredTable', [function() {
return {
'restrict': 'E',
'scope': {
'metadataArray': '=',
'itemsArray': '=',
'options': '=',
'numberOfItems': '=',
'changePaginationCallback': '&'
},
'controller': 'FeaturedTableCtrl',
'bindToController': true,
'controllerAs': 'featuredTable',
'templateUrl': 'directives/featuredTable/featuredTable.tpl.html'
};
}]);
You can see at the beginning of the controller that I'm initializing its properties with the attributes passed by the directive or providing default values:
controller.activePage = 1;
controller.changePaginationCallback =
controller.changePaginationCallback || function(){};
controller.density = 10;
controller.itemsArray = controller.itemsArray || [];
controller.metadataArray = controller.metadataArray || [];
controller.numberOfItems = controller.numberOfItems || 0;
controller.numberOfPages = 1;
controller.options = controller.options || {
'pagination': false
};
At the end I'm executing the initialize(); function that will execute the callback according to the options:
function initialize(){
if(controller.options.pagination){
controller.changePaginationCallback({
'page': controller.activePage,
'perPage': controller.density
});
}
}
I'm now trying to unit test this controller (with karma and jasmine) and I need to "simulate" the parameters passed by the directive, I tried the following:
'use strict';
describe('Controller: featured table', function () {
beforeEach(module('directives.featuredTable'));
var scope;
var featuredTable;
var createCtrlFn;
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
createCtrlFn = function(){
featuredTable = $controller('FeaturedTableCtrl', {
'$scope': scope
});
scope.$digest();
};
}));
it('should initialize controller', function () {
createCtrlFn();
expect(featuredTable.activePage).toEqual(1);
expect(featuredTable.changePaginationCallback)
.toEqual(jasmine.any(Function));
expect(featuredTable.density).toEqual(10);
expect(featuredTable.itemsArray).toEqual([]);
expect(featuredTable.metadataArray).toEqual([]);
expect(featuredTable.numberOfPages).toEqual(1);
expect(featuredTable.numberOfItems).toEqual(0);
expect(featuredTable.options).toEqual({
'pagination': false
});
});
it('should initialize controller with pagination', function () {
scope.changePaginationCallback = function(){};
spyOn(scope, 'changePaginationCallback').and.callThrough();
scope.options = {
'pagination': true
};
createCtrlFn();
expect(featuredTable.activePage).toEqual(1);
expect(featuredTable.changePaginationCallback)
.toEqual(jasmine.any(Function));
expect(featuredTable.density).toEqual(10);
expect(featuredTable.itemsArray).toEqual([]);
expect(featuredTable.metadataArray).toEqual([]);
expect(featuredTable.numberOfPages).toEqual(1);
expect(featuredTable.numberOfItems).toEqual(0);
expect(featuredTable.options).toEqual({
'pagination': true
});
expect(featuredTable.changePaginationCallback).toHaveBeenCalledWith({
'page': 1,
'perPage': 10
});
});
});
And got the following error, meaning that scope is not well initialized:
Expected Object({ pagination: false }) to equal Object({ pagination: true })
at test/spec/app/rightPanel/readView/historyTab/historyTab.controller.spec.js:56
Simulating the bindings would be non-trivial - after all, it's hard to really know what compiling and linking a directive does with the data passed to it...unless you just do it yourself!
The angular.js documentation offers a guide on how to compile and link a directive for unit testing - https://docs.angularjs.org/guide/unit-testing#testing-directives. After doing that, you'd just need to get the controller from the resulting element(see the documentation for the controller() method here - https://docs.angularjs.org/api/ng/function/angular.element) and perform your tests. ControllerAs would be irrelevant here - you would be testing the controller directly, instead of manipulating the scope.
Here's an example module:
var app = angular.module('plunker', []);
app.controller('FooCtrl', function($scope) {
var ctrl = this;
ctrl.concatFoo = function () {
return ctrl.foo + ' world'
}
})
app.directive('foo', function () {
return {
scope: {
foo: '#'
},
controller: 'FooCtrl',
controllerAs: 'blah',
bindToController: true,
}
})
And test setup:
describe('Testing a Hello World controller', function() {
ctrl = null;
//you need to indicate your module in a test
beforeEach(module('plunker'));
beforeEach(inject(function($rootScope, $compile) {
var $scope = $rootScope.$new();
var template = '<div foo="hello"></div>'
var element = $compile(template)($scope)
ctrl = element.controller('foo')
}));
it('should produce hello world', function() {
expect(ctrl.concatFoo()).toEqual('hello world')
});
});
(Live demo: http://plnkr.co/edit/xoGv9q2vkmilHKAKCwFJ?p=preview)

angularjs restricting count to current scope

I am tring to setup a counter where for each country in my list I can keep count of how many clicks there has been plus an overall tally.
I have the below so far which can be viewd in this fiddle. The issue I am having is that I am not able to keep the count unique for each country. How can this be achieved?
<div ng-app="myApp">
<div data-ng-view></div>
</div>
'use strict';
var myApp = angular.module('myApp', ['ngRoute', 'templates/view1.html', 'templates/view2.html']);
myApp.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'templates/view1.html',
controller: 'CountryListCtrl'
})
.when('/:id', {
templateUrl: 'templates/view2.html',
controller: 'CountryCtrl'
})
}]);
myApp.factory('Countries', ['$q', function ($q) {
var countriesList = [];
// perform the ajax call (this is a mock)
var getCountriesList = function () {
// Mock return json
var contriesListMock = [
{
"id": "0",
"name": "portugal",
"abbrev": "pt"
}, {
"id": "1",
"name": "spain",
"abbrev": "esp"
}, {
"id": "2",
"name": "angola",
"abbrev": "an"
}
];
var deferred = $q.defer();
if (countriesList.length == 0) {
setTimeout(function () {
deferred.resolve(contriesListMock, 200, '');
countriesList = contriesListMock;
}, 1000);
} else {
deferred.resolve(countriesList, 200, '');
}
return deferred.promise;
}
var getCountry = function(id) {
var deferred = $q.defer();
if (countriesList.length == 0) {
getCountriesList().then(
function() {
deferred.resolve(countriesList[id], 200, '');
},
function() {
deferred.reject('failed to load countries', 400, '');
}
);
} else {
deferred.resolve(countriesList[id], 200, '');
}
return deferred.promise;
}
var cnt = 0;
var cntryCnt = 0;
var incCount = function() {
cnt++;
return cnt;
}
var incCntryCount = function(id) {
cntryCnt++;
return cntryCnt;
}
return {
getList: getCountriesList,
getCountry: getCountry,
getCount : function () {
return cnt;
},
getCntryCount : function () {
return cntryCnt;
},
incCount: incCount,
incCntryCount: incCntryCount
};
}]);
myApp.controller('CountryListCtrl', ['$scope', 'Countries', function ($scope, Countries) {
$scope.title = '';
$scope.countries = [];
$scope.status = '';
Countries.getList().then(
function (data, status, headers) { //success
$scope.countries = data;
},
function (data, status, headers) { //error
$scope.status = 'Unable to load data:';
}
);
}]);
myApp.controller('CountryCtrl', ['$scope', '$routeParams', 'Countries', function ($scope, $routeParams, Countries) {
$scope.country = {
id: '',
name: '',
abbrev: ''
};
var id = $routeParams.id;
Countries.getCountry(id).then(
function(data, status, hd) {
console.log(data);
$scope.country = data;
$scope.countOverall = Countries.getCount;
$scope.countCntry = Countries.getCntryCount;
$scope.clickCnt = function () {
$scope.countTotal = Countries.incCount();
$scope.country.clicks = Countries.incCntryCount(id);
console.log($scope);
};
},
function(data, status, hd) {
console.log(data);
}
);
}]);
angular.module('templates/view1.html', []).run(["$templateCache", function ($templateCache) {
var tpl = '<h1>{{ title }}</h1><ul><li ng-repeat="country in countries"><a href="#{{country.id}}">{{country.name}}</div></li></ul>';
$templateCache.put('templates/view1.html', tpl);
}]);
angular.module('templates/view2.html', []).run(["$templateCache", function ($templateCache) {
var tpl = '<div>{{country.name}} clicks {{countCntry()}} <br> overall clicks {{countOverall()}}</div><button>BACK</button><button ng-click="clickCnt()" >count clicks ++ </button>';
$templateCache.put('templates/view2.html', tpl);
}]);
The problem is that you are not incrementing a count based on the country. Working on the fiddle right now.
EDIT:
I've updated the fiddle: http://jsfiddle.net/1xtc0zhu/2/
What I basically did was making the cntryCnt an object literal which takes the country id as a property and keeps the right counting per each id, like so:'
var cnt = 0;
var cntryCnt = {};
...
// The function now receives the country id and increments the specific country clicks only.
var incCntryCount = function(id) {
cntryCnt[id] = cntryCnt[id] || 0;
cntryCnt[id]++;
return cntryCnt[id];
}
The rest of the changes are in the templates, and are basically only sending the country id as a param when getting or incrementing the counts.
Also, this is not an Angular Specific question, but more a programming in general question.

Angularjs - How to combine two objects obtained with ngresource

How to combine two objects obtained with ngressource.
Each 5 seconds, i call my service to obtain a message and i want to add a new message with the olders.
My Json message :
[
{"age": 0,"id": "my first tweet","name": "Hello Sarah","snippet": "It's fabulous"},
{"age": 1,"id": "my second tweet","name": "Hello dude !","snippet": "It's fabulous"}
]
My Service :
'use strict';
/* Services */
var listlogServices = angular.module('listlogServices', ['ngResource']);
listlogServices.factory('Log', ['$resource',
function($resource){
return $resource('log/log1.json', {}, {
query: {method:'GET', params:{}, isArray:true}
});
}]);
My controller and functions
'use strict';
var app = angular.module('appRecupTweetApp');
app.controller('TimerCtrl1', function TimerCtrl1($scope, Timer){
$scope.$watch(function () { return Timer.data.myTab; },
function (value) {
$scope.data = value ;
//$scope.data.push(value);
//$scope.data = $scope.data.concat(value);
}
);
});
app.service('Timer', function ($timeout, Log) {
var data = { myTab: new Array()};
var updateTimer = function () {
data.myTab = Log.query();
$timeout(updateTimer, 5000);
};
updateTimer();
return {
data: data
};
});
I try to combine my object with 'push' and 'concat' but is not right.
Corrected error (Angular says : $scope.data is undefined )
Could i make this operation in my 'Timer' service or in my controller and what is the good solution.
Online demo : plnkr.co/edit/Vzdy9f7zUObd71Lm86Si
Thank's
Guillaume
You just have to initialize $scope.data in you TimerCtrl1 controller.
app.controller('TimerCtrl1', function TimerCtrl1($scope, Timer){
$scope.data=[];
$scope.$watch(function () { return Timer.data.myTab; },
function (value) {
$scope.data = value ;
//$scope.data.push(value);
//$scope.data = $scope.data.concat(value);
}
);
});

Categories

Resources