I'm trying to communicate two controllers.
var main = angular.module('starter', ["ionic", "ngCordova", "starter.services"]);
cart-ctrl.js
main.controller('CartCtrl',
["$scope", "global",
function($scope, global) {
$scope.$on("globalvar", function() {
//alert("from service cart: " + global.cart.items);
console.log("from service cart: " + global.cart.items);
$scope.carts = global.cart.items;
});
}]);
menu-ctrl.js
main.controller('AppCtrl',
["$scope", "$state", "global",
function($scope, $state, global) {
$scope.cart_click = function() {
global.updateCart();
$state.go('app.cart');
}
}]);
services.js
var service = angular.module("starter.services", []);
service.factory("global", ["$rootScope", "database",
function($rootScope, database) {
var service = {
cart: {
items: [],
count: 0
},
broadcastItem: function() {
$rootScope.$broadcast("globalvar");
},
updateCart: function() {
database.select_cart(function(p_cart) {
this.cart.items = p_cart;
alert("service cart: " + JSON.stringify(this.cart.items));
});
this.broadcastItem();
}
};
return service;
}]);
What I wanted to happen is when I click a the tab (which triggeres the cart_click()), the cart list will re-update. However no value is passed into CartCtrl. I wonder what's wrong in this code. service.cart.items has a value when I passed the value from the database.
I think we have 2 options.
You can $scope.cart_click => $rootScope.cart_click.
You can $emit, $broadcast and $on
And see more in https://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
I think you should call this.broadcastItem(); inside the callback of your database call. Also context of this inside the callback is not actually of the same service. Update your code as
updateCart: function() {
var self = this;
database.select_cart(function(p_cart) {
self.cart.items = p_cart;
self.broadcastItem();
alert("service cart: " + JSON.stringify(self.cart.items));
});
}
Related
I have below code:
vm.data = [{name: 'test-1'},{name: 'test-2'}];
function addRecords(data) {
vm.data.push(data);
}
function openPopup() {
$uibModal.open({
templateUrl: 'modal-popup/modal-popup.html',
controller: 'ModalPopupController',
controllerAs: 'vm',
resolve: {
id: _.constant('123')
}
}).result.then(addRecords);
}
Trying to mock this, Below are the declarations:
let allData = [{name: 'test-1'},{name: 'test-2'}];
let data = {name: 'test-3'};
beforeEach(inject(function (_$q_, _$rootScope_, _$componentController_, _$uibModal_) {
$q = _$q_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
controller = _$componentController_;
$uibModal = _$uibModal_;
spyOn($uibModal, 'open').and.returnValue({
result: function() {
return $q.when(data);
}
});
vm = controller('bvcListings', {
$q,
data: allData,
$uibModal
});
$scope.$apply();
}));
describe('openPopup', function () {
it('should add records on modal results', function () {
vm.openPopup();
expect($uibModal.open).toHaveBeenCalled();
});
});
Expectation is, it should add: {name: 'test-3'} as result to existing array.
Spy on modal open is working fine, but after results fetched, its not entering addRecords function. What am i doing wrong?
What changes need to be done here to get inside callback function after results retrieved.
.result.then callback method will get call only when you call modalInstance.close method, also don't forgot to pass data from close method something like modalInstance.close(data).
Before proceeding to test you need to do one change inside openPopup function. It should return $uibModal.open which basically returns newly created modal's instance. Thereafter you can easily have a control over modal to call dismiss/close method whenever needed.
function openPopup() {
vm.modalInstance = $uibModal.open({
templateUrl: 'modal-popup/modal-popup.html',
controller: 'ModalPopupController',
controllerAs: 'vm',
resolve: {
id: _.constant('123')
}
});
vm.modalInstance.result.then(addRecords);
}
Spec
$uibModal = _$uibModal_;
var data = {name: 'test-3'};
//creating fake modal which will help you to mock
var fakeModal = {
result: {
then: function(confirmCallback) {
//Store the callbacks
this.confirmCallBack = confirmCallback;
}
},
close: function( item ) {
//The user clicked OK on the modal dialog
this.result.confirmCallBack( item );
}
};
spyOn($uibModal, 'open').and.returnValue(fakeModal);
describe('It should data to vm.data when popup closed', function () {
it('should add records on modal results', function () {
vm.data = [{name: 'test-1'},{name: 'test-2'}];
let data = {name: 'test-3'};
vm.openPopup();
expect($uibModal.open).toHaveBeenCalled();
vm.modalInstance.close(data);
expect(vm.data.length).toBe(4);
expect(vm.data[3]).toBe(data);
});
});
Note: fakeModal has been referred from this post
Continuing with #Pankajs answer.
Here is a tweak which i made and got that worked.
function openPopup() {
vm.modalInstance = $uibModal.open({
templateUrl: 'modal-popup/modal-popup.html',
controller: 'ModalPopupController',
controllerAs: 'vm',
resolve: {
id: _.constant('123')
}
}).result.then(addRecords);
}
Spec
describe('modalpopup', function () {
it('should add records on modal results', function () {
vm.data = [{name: 'test-1'},{name: 'test-2'}];
let data = {name: 'test-3'};
vm.openPopup();
expect($uibModal.open).toHaveBeenCalled();
vm.modalInstance.close(data);
expect(vm.data.length).toBe(4);
expect(vm.data[3]).toBe(data);
});
});
Worked like charm for me. And i consier Pankajs answer as well which was almost 90% gave solution to my problem.
add $rootScope.$digest(); to resolve promises (like $q.when())
vm.openPopup();
expect($uibModal.open).toHaveBeenCalled();
$rootScope.$digest(); >> triggers your callback
I'm dividing my functions/objects into service and factory methods, and injecting them into my controller patentTab. I had a code for a tab panel which I originally placed within the controller patentTab that worked.
Now I have placed this code in a factory method and for some reason the content isn't loading. Console log shows no errors, and when I click the relative tab the correct URL is loaded, but the content doesn't change. Is there an issue with my array in the factory? If not, what is the reason?
Orginal code
app.controller('patentTab', function($scope, $http){
$scope.tabs = [{
title: 'Patent Information',
url: 'patent-info.htm'
}, {
title: 'Cost Analysis',
url: 'cost-analysis.htm'
}, {
title: 'Renewal History',
url: 'renewal-history.htm'
}];
$http.get('../json/patent-info.json').then(function(response){
$scope.patentData = response.data.patentInfo;
})
$scope.currentTab = 'patent-info.htm';
$scope.onClickTab = function (tab) {
$scope.currentTab = tab.url; //the tabs array is passed as a parameter from the view. The function returns the url property value from the array of objects.
}
$scope.isActiveTab = function(tabUrl) {
return tabUrl == $scope.currentTab;
}
});
New code (with issue)
app.controller('patentCtrl', ['$scope', '$http', 'patentTabFactory', function($scope, $http, patentTabFactory) {
$http.get('http://localhost:8080/Sprint002b/restpatent/').then(function(response) {
$scope.patents = response.data;
});
$scope.loadPatentItem = function(url) {
$scope.patentItem = url;
}
$scope.tabs = patentTabFactory.tabs;
$scope.currentTab = patentTabFactory.currentTab;
$scope.onClickTab = patentTabFactory.onClickTab;
$scope.isActiveTab = patentTabFactory.isActiveTab;
}]);
app.factory('patentTabFactory', function() {
var factory = {};
factory.tabs = [{
title: 'Patent Information',
url: 'patent-info.htm'
}, {
title: 'Cost Analysis',
url: 'cost-analysis.htm'
}, {
title: 'Renewal History',
url: 'renewal-history.htm'
}];
factory.currentTab = 'patent-info.htm';
factory.onClickTab = function (tab) {
factory.currentTab = tab.url; //the tabs array is passed as a parameter from the view. The function returns the url property value from the array of objects.
console.log(tab.url);
}
factory.isActiveTab = function(tabUrl) {
return tabUrl == factory.currentTab; //for styling purposes
}
return factory;
});
You not calling factory.onClickTab() method from your controller.
It should be like :
$scope.onClickTab = function(currentTab) {
patentTabFactory.onClickTab(currentTab);
$scope.currentTab = patentTabFactory.currentTab;
};
and, for isActiveTab, Like :
$scope.isActiveTab = patentTabFactory.isActiveTab(currentTab);
Here is a plunker where I am using a factory. The only changes I have done are:
1. Place the factory file before the app script file.
2. Use a separate declaration for factories and then inject it in the app.
var factories = angular.module('plunker.factory', []);
factories.factory('patentTabFactory', function() {
// Factory bits
};
I have injected the factories in the app.
var app = angular.module('plunker', ['plunker.factory']);
Here is a working plunker for that. PlunkR
Here is my plnkr with my progress so far: http://plnkr.co/edit/iEHMUMlASZaqdMQUeF7J?p=preview
I'm having problems implementing the following functionality however.
When an item on the list is clicked, I need to disable the remaining items on the list. ie, another request should not take place, and these remaining items' colour should change to indicate the disabled state.
Once the request has taken place, then the entire list should go back to the original state.
Edit: I've made some progress. Although a bit messy it's getting me a bit closer. My problem is the following line:
$(this).parent().addClass('item-selected').children().unbind('click').removeClass('pending');
This prevents the click event running more than once at a time. However it's stopping the click event from running all together once its run for the first time. I would like to be able to re-run the process once it is complete an unlimited amount of times.
Directive:
app.directive('listItem', function (ListService, $timeout, $location) {
return {
restrict: 'ACE',
controller : 'ItemController',
template: '<p>{{item}} {{foo}}</p>',
link: function (scope, element, attrs) {
$(element).bind('click', function (e) {
$(this).parent().addClass('item-selected').children().unbind('click').removeClass('pending');
$(this).addClass('pending');
var elem = $(this);
$timeout(function () {
ListService
.selectItem(scope.item)
.then( function () {
console.log('success');
elem.removeClass('pending').addClass('success');
//$location.path('foo.html')
scope.foo = 'not bar';
}, function () {
console.log('error');
elem.removeClass('pending').addClass('error');
elem.parent().removeClass('item-selected');
});
;
}, 2000);
});
}
};
});
The entire app code including directive:
var app = angular.module('listtestApp', []);
app.service('ListService', function ($http) {
var data = [
'alpha',
'bravo',
'charlie',
'delta',
'foxtrot'
];
return {
getData : function () {
return data;
},
selectItem : function () {
return $http({ method: 'GET', url : '/data/list.json'});
}
}
});
app.controller('ListController', function ($scope, ListService) {
$scope.list = ListService.getData();
$scope.foo = 'Bar';
});
app.controller('ItemController', function ($scope, ListService) {
});
app.directive('listItem', function (ListService, $timeout, $location) {
return {
restrict: 'ACE',
controller : 'ItemController',
template: '<p>{{item}} {{foo}}</p>',
link: function (scope, element, attrs) {
$(element).bind('click', function (e) {
$(this).parent().addClass('item-selected').children().unbind('click').removeClass('pending');
$(this).addClass('pending');
var elem = $(this);
$timeout(function () {
ListService
.selectItem(scope.item)
.then( function () {
console.log('success');
elem.removeClass('pending').addClass('success');
//$location.path('foo.html')
scope.foo = 'not bar';
}, function () {
console.log('error');
elem.removeClass('pending').addClass('error');
});
;
}, 2000);
});
}
};
});
html markup below:
<body ng-app="listtestApp">
<div ng-controller="ListController">
<div ng-repeat="item in list" list-item>
</div>
</div>
</body>
You have several solutions at your disposal :
Check that any element has the pending or success or error class
use your function scope to store it in a variable
EDIT : if you want to re-enable selection after the request has been posted, you could use something like this (variant of version #1)
I'm having a recurrent problem with my angular app whereby it doesn't refresh the page after data has been added, edited or removed. So if I add a new item to a list of subjects, the new item doesn't appear on the list unless I navigate away from the page and then come back to it. I've tried using route.reload and then resetting the scope of the subjects list below. I put in an alert to see if it get fired- but the alert appears before the page redirects back to the list of subjects, which is strange as $location.path('/subjects') is two lines before it. Here's my controller:
angular.module('myApp.controllers')
.controller('SubjectEditCtrl', ['$scope', '$routeParams', 'SubjectFactory', 'SubjectsFactory', '$location', '$route',
function ($scope, $routeParams, SubjectFactory, SubjectsFactory, $location, $route) {
// callback for ng-click 'updateSubject':
$scope.updateSubject = function () {
//Performs an update to the server
SubjectFactory.update($scope.subject);
//Redirects to list of all subjects
$location.path('/subjects/');
//Should reset the scope of the subject list
$scope.subjects = SubjectsFactory.query();
//Should reload the page
$route.reload();
//For debugging- the alert appears BEFORE the redirect to list of all subjects happens
alert('route reload happening');
};
SubjectFactory.show({id: $routeParams.subjectId}).$promise.then(function(subject) {
$scope.subject = subject;
}, function(err) {
console.error(err);
});
}]);
Can anyone suggest a solution?
EDIT: Subjects Service
var app = angular.module('myApp.services');
app.factory('SubjectsFactory', function ($resource) {
return $resource('https://myapiurl.com/subjects', {}, {
query: { method: 'GET', isArray: true },
create: { method: 'POST' }
})
});
app.factory('SubjectFactory', function ($resource) {
return $resource('https://myapiurl.com/subjects/:id', {}, {
show: { method: 'GET', isArray: false },
update: { method: 'PATCH', params: {id: '#id'} },
delete: { method: 'DELETE', params: {id: '#id'} }
})
});
Some times you need to apply changes to scope this is done by the following code:
$scope.$apply();
But this can be done only if it's not in "$digest" phase, otherwise it will throw exception. So you need to check first it's not in "$digest" phase then you can apply it. Here is the example of the code I use for safe applying changes:
safeApply: function (scope, callback) {
if (scope.$$phase != '$apply' && scope.$$phase != '$digest' &&
(!scope.$root || (scope.$root.$$phase != '$apply' && scope.$root.$$phase != '$digest'))) {
scope.$apply();
}
if (angular.isFunction(callback)) {
callback();
}
}
I can suggest next way:
You can't get data from database, after adding, you can easly push new added object to $scope.items.
Example:
$scope.add = function (newItem) {
DataService.addItem(newItem).then(function(){
$scope.items.push(newItem);
//or for removing
//$scope.items.splice($scope.items.indexOf(newItem), 1);
});
};
And adjust your factory:
addItem: function (newProject) {
$http.post('Api/Controller/Post').then(function(successResult){
...
}, function (errorResult) {
...
});
}
Item will be added in $scope.items only after success calling of server-side method.
Changing the structure of the requests slightly fixed the problem- so instead of
$scope.updateSubject = function () {
SubjectFactory.update($scope.subject);
$location.path('/subjects/');
$scope.subjects = SubjectsFactory.query();
$route.reload();
};
It is now
$scope.updateSubject = function () {
SubjectFactory.update($scope.subject).$promise.then(function (subject) {
$scope.subject = subject;
$location.path('/subjects/');
$route.reload();
}, function (err) {
console.error(err);
});
};
I am totally newbie to AngularJs.
I have pretty good example of ngClick which is calling a Facebook function.
I would like to change it and programmatically to call registerWithFacebook function after my controller will be ready. What is the way for it?
Here is the sample: http://jsfiddle.net/IgorMinar/Hxbqd/5/
angular.module('HomePageModule', []).service('facebookConnect', function($rootScope) {
this.askFacebookForAuthentication = function(fail, success) {
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me', function() { $rootScope.$apply(success) });
} else {
$rootScope.$apply(function() {
fail('User cancelled login or did not fully authorize.')
});
}
});
}
});
function ConnectCtrl(facebookConnect, $scope, $resource) {
$scope.user = {}
$scope.error = null;
$scope.registerWithFacebook = function() {
facebookConnect.askFacebookForAuthentication(
function(reason) { // fail
$scope.error = reason;
}, function(user) { // success
$scope.user = user
});
}
// Tried this but no success
// $scope.registerWithFacebook();
}
ConnectCtrl.$inject = ['facebookConnect', '$scope', '$resource'];
Thanks
If you want to load the FB library asynchronously, you need to let angular know after it initializes. Here is one way to do it:
http://plnkr.co/edit/YO4duxL1Mh3dwYEEpprV?p=preview