Bear with me - I'm an AngularJS newb.
I have a form that I want to be available from anywhere in my app, and I'm trying to figure out how to code that. My current attempt is to put the modal into a service, like this:
.service('NewObjectService', function() {
var svc = this;
svc.showModal = function() {
$ionicModal.fromTemplateUrl('template.html', {
scope: null, // what should I do here?
animation: 'slide-in-up'
}).then(function(modal) {
svc.modal = modal;
modal.show();
});
}
})
.controller('NewObjectController', function() {
$scope.$on('$ionicView.enter', function() {
console.log('NewObjectController');
// setup new object
})
})
Then from anywhere in my app, I can call NewObjectService.showModal() and the modal pops up. That part is working.
The trouble I'm running into is that I can't get my controller to fire, so the initialization never gets called and my new object is null.
It seems like I should actually be calling the modal from within NewObjectController to setup scope, but I tried that and I couldn't figure out how to call that controller from within other controllers - hence the service.
I know I'm just doing something fundamentally wrong, but I'm not sure what it is. Any help?
Update: I also just tried calling one controller from another using a root scope broadcast:
.controller('MainCtrl', function() {
this.showModal = function() {
$rootScope.$broadcast('new_object:show_modal');
}
})
.controller('NewObjectCtrl', function() {
$rootScope.$on('new_object:show_modal', function() {
// show modal
})
})
The problem I'm running into there is that NewObjectCtrl hasn't been invoked at the time MainCtrl runs, so it doesn't catch the broadcast event.
When you declare a service you need to return itself in the Angular declaration ie var svc = {}; return svc; Call svc.showModal from any controller after you've injected the service and pass in the scope. Call the controller from another controller by using $rootScope.$on (receiver) and $rootScope.$emit (from)
.service('NewObjectService', function($ionicModal) {
var svc = {};
svc.showModal = function(_scope) {
$ionicModal.fromTemplateUrl('template.html', {
scope: _scope, // passing in scope from controller
animation: 'slide-in-up'
}).then(function(modal) {
svc.modal = modal;
modal.show();
});
}
return svc;
})
.controller('NewObjectController', function($scope, $rootScope, NewObjectService) {
// fires off when $rootScope.$emit('ShowModal') is called anywhere
$rootScope.$on('ShowModal', function(data) {
NewObjectService.showModal(data._scope);
});
})
.controller('OtherController', function($scope, $rootScope) {
$scope.contactOtherController = function() {
// contact the other controller from this controller
$rootScope.$emit("ShowModal", {scope: $scope});
}
})
Related
I am using angular-modal-service library. My logic is : when the modal is open it runs a function from SomeService, and $rootScope.$broadcast from SomeService to modal controller that way I can send resource from service to my modal controller. However, it doesn't fire. Please help me to figure out what I have missed. Thank you.
**Service: **
angular.module('ng-laravel').service('SomeService', function($rootScope, Restangular, CacheFactory, $http) {
this.testFunction = function() {
console.log("from service");
$rootScope.$broadcast('event', {success:'success'});
};
}
**Controller: **
$scope.show = function(customer_id) {
ModalService.showModal({
templateUrl: 'modal.html',
inputs: {
customer_id: customer_id
},
scope: $scope,
controller: function($scope, close) {
$scope.customer_id = customer_id;
$scope.close = function(result) {
close(result, 500); // close, but give 500ms for bootstrap to animate
};
$scope.$on('event', function(event, data){
alert('yes');
console.log('from modal controller');
});
}
}).then(function(modal) {
SomeService.testFunction(customer_id, tour_id);
modal.element.modal();
modal.close.then(function(result) {
$scope.message = "You said " + result;
});
});
};
After switching the function it works, but...
how could i pass data in to modal? like ui-bs-modal, they have resolve.
You're being broadcasting event before events from modal controller are binding. So before broadcasting event make sure that event listeners are registered(meaning modal controller has been loaded). So call SomeService.testFunction(); after showModal method.
$scope.show = function(customer_id) {
ModalService.showModal({
templateUrl: 'modal.html',
inputs: {
customer_id: customer_id
},
scope: $scope,
controller: function($scope, close) {
//code as is
//listeners will get register from here.
}
})
.then(function(modal) {
SomeService.testFunction(); //broadcasting event
}).catch(function(error) {
// error contains a detailed error message.
console.log(error);
});
};
You are broadcasting the event, before the modal controller is instantiated or created, as service function is called before ModalService.showModal. Try changing the order. That should work fine.
Inside $scope.show try this order
$scope.show = function(){
ModalService.showModal({
....
// Listen for broadcast event
});
SomeService.testFunction();
}
I am using angular material.
I created a factory to display a loading modal.
I can display it, but I can't dismiss it.
Could you tell me why, or tell me how to do it ?
Here is my factory :
(function() {
'use strict';
angular
.module('loveProjectApp')
.factory('ProgressFactory', ProgressFactory);
ProgressFactory.$inject = ['$mdDialog'];
/* #ngInject */
function ProgressFactory($mdDialog) {
var service = {
show: show,
cancel: cancel
};
return service;
////////////////
function show() {
$mdDialog.show({
templateUrl: 'miscellaneous/progress/progress.dialog.html',
parent: angular.element(document.body),
clickOutsideToClose: false,
fullscreen: false
});
}
function cancel() {
$mdDialog.hide();
}
}
})();
And I call it like this :
function logout() {
ProgressFactory.show();
authFactory.logout().then(function() {
ToastFactory.successToast('SUCCESSES.DISCONNECTED');
$state.reload();
ProgressFactory.cancel();
});
So the loader is shown, but isn't dismissed.
I have created a CodePen example of your code and the dialog does close. I have used a $timeout to replicate your authFactory.logout() call.
This makes me think that
The .then callback function is not being called, or
Something is happening before ProgressFactory.cancel(); is called that prevents it from being executed
JS
.controller('AppCtrl', function($scope, $timeout, ProgressFactory) {
$scope.logout = function () {
ProgressFactory.show();
$timeout(function () {
ProgressFactory.cancel();
}, 2000);
}
})
FOUND IT !
My server is running on the same machine that my application uses.
I was calling $mdDialog.cancel(); too fast visibly, so I added a 1s timeout in the factory, and now the dialog dismisses correctly.
It now works like a charm, thank you !
I'm trying to pass information about a listing that appears on the show page to the modal on that page.
I successfully created the factory service which returns me an object.
angular.module('articles').factory('ProductService', [ '$resource', 'Articles','$stateParams', function($resource, Articles, $stateParams) {
var listingInfo =
Articles.get({
articleId: $stateParams.articleId
});
return listingInfo;
}
]);
(logged by using angular.element(document.body).injector().get('ProductService'))
If I place this in my main ArticlesController I'm able to see the scope via browser console with angular.element($0).scope() and able to access the object by injecting into my controller and giving it a scope of $scope.product = ProductService;, allowing me to access the data in the expected way (product.furtherinfo).
However when trying the same technique for my modal controllers, I'm unable to find the scope when I log through the browser or access the data through binding or brackets.
I've tried passing the value through the resolve, injecting the dependency in all my controllers having to do with my modal, but nothing works.
// Modals
angular.module('articles').controller('ModalDemoCtrl',['$scope', '$modal', '$log', 'ProductService' , function ($scope, $modal, $log, ProductService) {
$scope.items = ['item1', 'item2', 'item3'];
$scope.product = ProductService;
$scope.animationsEnabled = true;
$scope.open = function (size) {
var modalInstance = $modal.open({
animation: $scope.animationsEnabled,
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
size: size,
resolve: {
items: function () {
return $scope.items;
},
product: function () {
return $scope.product;
}
}
});
modalInstance.result.then(function (selectedItem) {
$scope.selected = selectedItem;
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
$scope.toggleAnimation = function () {
$scope.animationsEnabled = !$scope.animationsEnabled;
};
}]);
The idea is to pass the returned factory object to my modal so I can link it to an input(maybe hidden) that I could designate as a model to send through to an email.
angular.module('articles').controller('ModalInstanceCtrl',['$scope', '$modalInstance', 'items', '$http', 'product','ProductService','$stateParams', function ($scope, $modalInstance, items, $http, product,ProductService,$stateParams) {
$scope.items = items;
$scope.product = product;
$scope.sendMail = function(){
var data = ({
input : this.contactAgentInput,
inputBody : this.contactAgentInputBody,
})
$http.post('/contact-agent', data).
then(function(response) {
// this callback will be called asynchronously
$modalInstance.close($scope.selected.item);
console.log("all is well")
// when the response is available
}, function(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
})
}
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}]);
Use a debugger or console.log. If you add console.log(ProductService) in your modal controller, it should show you the service is being injected. – Anid Monsur
Thanks for the suggestion #AnidMonsur i noticed my Rentals controller firing off while In the Sales show page (I split the modules into Sales and Rentals). Thinking that I might be launching the modal from the wrong Module. Investigating now. – Meir Snyder
#AnidMonsur that did it! I was using the same names for some of the modal controllers ( stupid obviously) it must have launched the wrong modal instance and that's why I wasn't able to access the object. After giving them distinct names, it now works. Thanks so much, would have spent another day overlooking the error! – Meir Snyder
Let me guess. You're trying to display the items in the ModalDemoCtrl through the ModalInstanceCtrl, but you can't.
It seems you think DI is going to work as long as you put the name of the dependency but it's not until you register the dependency itself. So 'items' won't ever be a dependency at all and all you get is the value in the inner $scope on the controller (which probrablly is undefined).
In your case I'd say you register a third party factory (which is closer to a singleton) which can be injected just alike ProductService, and eventually would be called ItemsFactory.
Hope it helped.
I have been trying to find a way of testing this controller part for a few days but keep getting stuck. Now I get a ReferenceError: Can't find variable: $modal but I have it injected so im not sure why its not working. I also know that this test I am writing doesn't really test anything important so if you have any suggestions about moving forward please let me know. And thank you to anyone who has helped me on code throughout this controller
Code:
$scope.confirmDelete = function (account) {
var modalInstance = $modal.open({
templateUrl: '/app/accounts/views/_delete.html',
controller: function (global, $scope, $modalInstance, account) {
$scope.account = account;
$scope.delete = function (account) {
global.setFormSubmitInProgress(true);
accountService.deleteAccount(global.activeOrganizationId, account.entityId).then(function () {
global.setFormSubmitInProgress(false);
$modalInstance.close();
},
function (errorData) {
global.setFormSubmitInProgress(false);
});
};
$scope.cancel = function () {
global.setFormSubmitInProgress(false);
$modalInstance.dismiss('cancel');
};
},
resolve: {
account: function () {
return account;
}
}
});
Test:
describe("confirmDelete() function", function () {
var controller, scope;
// sets scope of controller before each test
beforeEach(inject(function ($rootScope, _$modal_) {
scope = $rootScope.$new();
controller = $controller('AccountsController',
{
$scope: scope,
$stateParams: mockStateParams,
$state: mockState,
// below: in order to call the $modal have it be defined and send on the mock modal?
$modal: _$modal_,
//modalInstance: mockModalInstance,
global: mockGlobal,
accountService: mockAccountSrv
});
}));
beforeEach(inject(function ($modal, $q) {
spyOn($modal, 'open').and.returnValue({
result: $q.defer().promise
});
}));
it("make sure modal promise resolves", function () {
scope.confirmDelete(mockAccountSrv.account);
expect($modal.open).toHaveBeenCalled();
});
});
You need to set modal to a variable in order to be able to use it.
i.e
describe("confirmDelete() function", function () {
var controller, scope, $modal; //Initialize it here
//....
beforeEach(inject(function ($rootScope, _$modal_, $controller) {
$modal = _$modal_; //Set it here
And you need to inject $controller as well in order to be able to use it.
Plnkr
within a controller i have a function which uses $state.transitionTo to "redirect" to another state.
now i am stuck in testing this function, i get always the error Error: No such state 'state-two'. how can i test this? it its totally clear to me that the controller does not know anything about the other states, but how can i mock this state?
some code:
angular.module( 'mymodule.state-one', [
'ui.state'
])
.config(function config($stateProvider) {
$stateProvider.state('state-one', {
url: '/state-one',
views: {
'main': {
controller: 'MyCtrl',
templateUrl: 'mytemplate.tpl.html'
}
}
});
})
.controller('MyCtrl',
function ($scope, $state) {
$scope.testVar = false;
$scope.myFunc = function () {
$scope.testVar = true;
$state.transitionTo('state-two');
};
}
);
describe('- mymodule.state-one', function () {
var MyCtrl, scope
beforeEach(module('mymodule.state-one'));
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
MyCtrl = $controller('MyCtrl', {
$scope: scope
});
}));
describe('- myFunc function', function () {
it('- should be a function', function () {
expect(typeof scope.myFunc).toBe('function');
});
it('- should test scope.testVar to true', function () {
scope.myFunc();
expect(scope.testVar).toBe(true);
expect(scope.testVar).not.toBe(false);
});
});
});
Disclaimer: I haven't done this myself, so I totally don't know if it will work and is what your are after.
From the top of my head, two solutions come to my mind.
1.) In your tests pre configure the $stateProvider to return a mocked state for the state-two That's also what the ui-router project itself does to test state transitions.
See: https://github.com/angular-ui/ui-router/blob/04d02d087b31091868c7fd64a33e3dfc1422d485/test/stateSpec.js#L29-L42
2.) catch and parse the exception and interpret it as fulfilled test if tries to get to state-two
The second approach seems very hackish, so I would vote for the first.
However, chances are that I totally got you wrong and should probably get some rest.
Solution code:
beforeEach(module(function ($stateProvider) {
$stateProvider.state('state-two', { url: '/' });
}));
I recently asked this question as a github issue and it was answered very helpfully.
https://github.com/angular-ui/ui-router/issues/537
You should do a $rootScope.$apply() and then be able to test. Note that by default if you use templateUrl you will get an "unexpected GET request" for the view, but you can resolve this by including your templates into your test.
'use strict';
describe('Controller: CourseCtrl', function () {
// load the controller's module
beforeEach(module('myApp'));
// load controller widgets/views/partials
var views = [
'views/course.html',
'views/main.html'
];
views.forEach(function(view) {
beforeEach(module(view));
});
var CourseCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
CourseCtrl = $controller('CourseCtrl', {
$scope: scope
});
}));
it('should should transition to main.course', inject(function ($state, $rootScope) {
$state.transitionTo('main.course');
$rootScope.$apply();
expect($state.current.name).toBe('main.course');
}));
});
Also if you want to expect on that the transition was made like so
expect(state.current.name).toEqual('state-two')
then you need to scope.$apply before the expect() for it to work