I am currently working on a small aplication using Angular.JS
In my view i have following button
<md-button class="md-primary" ng-click="editUser(user, $event)">Edit</md-button>
the editUser method looks something like this:
$scope.editUser = function (user, $event) {
$scope.userToEdit = user;
$mdDialog.show({
controller: DialogController,
targetEvent: $event,
templateUrl: '/js/modules/user/views/edit.tmpl.html',
parent: angular.element(document.body),
clickOutsideToClose: true,
scope: $scope
})
.
then(function (answer) {
if (answer == "save") {
for (right in $scope.allSystemRightsStatements) {
if ($scope.allSystemRightsStatements[right].selected) {
if( $scope.userToEdit.rights==null){
$scope.userToEdit.rights = [];
}
$scope.userToEdit.rights.push($scope.allSystemRightsStatements[right]);
}
}
$scope.updateUser($scope.userToEdit);
}
$scope.userToEdit = {};
}, function () {
$scope.userToEdit = {};
});
};
$scope.updateUser = function (user) {
//userService.updateUser makes a $http PUT request
var promise = userService.updateUser(user);
promise.then(function (result) {
$mdToast.show(
$mdToast.simple(result.message)
.position($scope.getToastPosition())
.hideDelay(3000)
);
}, function (reason) {
$mdToast.show(
$mdToast.simple(reason)
.position($scope.getToastPosition())
.hideDelay(3000)
);
}, function (update) {
});
};
Now the dialog is nicely shown and the answer function is also called, everything as expected.
However, when I click the button a second time the editUser funciton is not executed. As if the onClick event from the button had been removed at dialog close.
Any help on solving this problem is greatly appreciated,
Thanks
As said here
it is probably a good idea to explicitly mention that the scope will be destroyed upon hiding the dialog (so people shouldn't pass a controller's $scope directly).
(regarding the scope you are passing to mdDialog)
So, as the scope is destroyed, angular is not binding your button with any action
Related
I have a $scope.$on function which works fine when I'm leaving an unfinished operation on a tab to another:
$scope.$on("$stateChangeStart", function(event, toState) {
if (some conditions) {
event.preventDefault();
$modal.open({
templateUrl: 'prompt modal template',
controller: 'prompt modal controller',
backdrop: 'static',
size: 'sm'
})
.result.then(function (result) {
if (result === 'confirm') {
myService.doSomething(arg).then(function(result) {
$scope.doAnotherThing();
});
$state.go(toState.name);
}
else {
//stay where you were
}
});
}
});
Now I'd like to add ui-select2 change event to this function, so when user is trying to change the ui-select2 option while they have not finished the operation, the prompt modal would pop up and prevent any changes unless user confirms.
My ui-select2 ng-change="selectCity()" which it looks like:
$scope.selectCity = function() {
//some stuff
}
I tried adding $scope.selectCity/selectCity as an event after $stateChangeStart, but didn't work.
I would appreciate if someone could help me with this! Thanks
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've want to display a popup, but with a bit of delay. The problem is that when I'm not delaying the popup the buttons aren't working and when I'm adding the delay the buttons don't show up! I've read online that the $timeout is an async function, but that isn't the issue, or is it?
The popup:
var myPopup = $ionicPopup.show({
templateUrl: 'templates/components/welcomePopup.html',
scope: $scope,
buttons: [{
text: 'Hejdå!',
type: 'custom-save-button',
onTap: function (e) {
$scope.showSpinner = true
}
}]
})
How I delay it:
$timeout(function (){
// Popup here
}, 3000)
But when adding a delay my buttons aren’t showing and without they delay nothing is working.
buttons: [{
text: 'Hejdå!',
type: 'custom-save-button',
Does anyone know a quick fix?
This is the code at the moment:
$timeout(function() {
var myPopup = $ionicPopup.show({
templateUrl: 'templates/components/welcomePopup.html',
scope: $scope,
buttons: [{
text: 'Hejdå!',
type: 'custom-save-button',
onTap: function (e) {
$scope.showSpinner = true
}
}]
})
}, 3000)
Thanks for reading! :)
Well I managed to solve half of the problem. I suspected that the problem had something to do with the $scope and apparently I was correct.
So I added another $scope to:
var myPopup = $ionicPopup.show({
templateUrl: 'templates/components/welcomePopup.html',
scope: $scope.$scope, // added $scope
buttons: [{
text: 'Hejdå!',
type: 'custom-save-button',
onTap: function (e) {
$scope.showSpinner = true
}
}]
})
However, the buttons aren't working yet!
Your button is not working because when you do something on your $scope in the timeout context, you're not anymore in your controller context.
The variable showSpinner should be actually set to true when you tap on the button, the problem is that Angular isn't aware that the value has changed, and therefore does not refresh the view.
To solve this, you can force a refresh by adding that piece of code after you change the value in your scope:
if ($scope.$root.$$phase !== '$apply' && $scope.$root.$$phase !== '$digest') {
$scope.$apply();
}
The condition is to prevent a refresh request while there is already one in progress.
Therefore, I don't think you need to pass a $scope inside your main $scope.
Hope this helps!
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);
});
};