Angular - How to show modal reject reason in table? - angularjs

I have small problem to solve.
I have modal controller rejectIssueModalCtrl.js
(function () {
'use strict';
function rejectIssueModalCtrl($modalInstance, issue, $rootScope) {
var self = this;
self.cancel = function () {
$modalInstance.dismiss('cancel');
};
self.reject = function ($rootScope) {
$modalInstance.close(self.reason);
console.log(self.reason);
};
$rootScope.reasono = self.reason;
}
rejectIssueModalCtrl.$inject = ['$modalInstance', 'issue', '$rootScope'];
angular
.module('app')
.controller('rejectIssueModalCtrl', rejectIssueModalCtrl);
})();
When I click the button I can open this modal and write a reason. I want to show this reject reason in table in other controller.
Here's my code from other controller issueDetailsCtrl.js
$scope.reasonoo = $rootScope.reasono;
function rejectIssue() {
var rejectModal = $modal.open({
templateUrl: '/App/Issue/rejectIssueModal',
controller: 'rejectIssueModalCtrl',
controllerAs: 'rejectModal',
size: 'lg',
resolve: {
issue: self.issueData
}
});
rejectModal.result.then(function (reason) {
issueSvc
.rejectIssue(self.issueData.id, reason)
.then(function (issueStatus) {
changeIssueStatus(issueStatus.code);
getIssue();
}, requestFailed);
});
};
and html code
<div>
<span class="right" ng-bind="$root.reasono"></span>
</div>
As you can see I tried to use $rootScope. I can console.log the reason but I cant make it to show in this html. Any help?

We're missing some context, but I believe this is your problem:
self.reject = function ($rootScope) {
$modalInstance.close(self.reason);
console.log(self.reason);
};
$rootScope.reasono = self.reason;
Assuming self.reason is bound to the input in your modal, it won't be defined outside of the reject callback - that's the nature of async. You're able to log to console because you're doing so within the callback.
Define $rootScope.reasono inside of the callback like so:
self.reject = function () {
$modalInstance.close(self.reason);
console.log(self.reason);
$rootScope.reasono = self.reason;
};
Edited to show that $rootScope should be removed as a named parameter in the reject function definition.

Using root scope is not recommended. For this reason it is recommended to create a service for intercommuncation with variable to store reject reason, then inject this service for each controller - that way you will be able to read/write reason from different controllers.

Related

How to unit test $uibModal in Jasmine? (unit testing injected library)

So I am using $uibModal from bootstrap and I have the following code in my AngularJS controller:
vm.openPopup = function() {
$uibModal.open({
templateUrl: 'popup.html',
controller: function() {
var modal = this;
modal.hi = function() {
// some code here
}
}
});
};
How would I go about calling the modal.hi function in Jasmine and unit testing it to make sure it works correctly?
So the main problem with testing this code is that you've basically "buried" an anonymous function (modal.hi) inside of another anonymous function ($uibModal.open). That makes it pretty tricky to test.
You've got a few options: a.) you can mock the $uibModal service, b.) you can restructure your code, or c.) you could just drop your hi function onto the vm itself, and then call it from your tests. I think the last option would be the most expedient, but here are some examples of all three approaches.
Option 1: Mock the $uibModal service
describe('Test vm.openPopup', function () {
var mockUibModal = {
open: function(options){
var ctrl = options.controller();
// call your `hi` function:
ctrl.hi();
}
};
beforeEach(function(){
module(function($provide){
$provide.value('$uibModal', mockUibModal);
});
});
});
And from there, you could call your vm.openPopup method, and go about testing the results. Note that the module function comes from angular-mocks, which you'll need to install/include with your tests. Related question: "How do you mock a service in AngularJS when unit testing with jasmine?"
Option 2: Restructure your code
Here's a pattern that I frequently use, which involves shifting the logic/functions you wish to test into a separate factory:
var app = angular.controller('YourController', function ($uibModal, MyHelperFactory) {
var vm = this;
var modal;
var helper = MyHelperFactory(vm, modal);
vm.openPopup = function () {
$uibModal.open({
templateUrl: 'popup.html',
controller: function () {
modal = this;
modal.hi = helper.hi;
}
});
};
});
app.factory('MyHelperFactory', function () {
return function (vm, modal) {
return {
hi: function () {
// some code here, maybe it needs to reference the `vm` object, whatever...
}
}
};
})
The benefit of this approach is that you can test the MyHelperFactory on its own, without needing to instantiate YourController, and without needing to involve the $uibModal service. This is typically my favorite approach: no inline/anonymous functions - get that logic into helper factories, and out of my controllers.
Option 3: Drop the hi function onto vm
var app = angular.controller('YourController', function ($uibModal, MyHelperFactory) {
var vm = this;
// this pattern allows your function to be scoped with the not-yet-existing `modal` object
vm.hi = function (modal) {
return function () {
// some code here
}
};
vm.openPopup = function () {
$uibModal.open({
templateUrl: 'popup.html',
controller: function () {
var modal = this;
modal.hi = vm.hi(modal);
}
});
};
});
And from there, you can just test it by calling vm.hi from within your tests. I call this approach "dirty" because it adds the hi method to the vm object, and I generally avoid adding any properties to the vm object that aren't actually needed on the controller scope. In this case though, we're breaking that rule because it's the quickest/easiest way to "expose" this function that you wish to test.

Update $scope variable that is used in ng-repeat, from other controller

I have a controller HomeworkPageController where I get all the topics from MongoDB using method getAllMainTopics from TopicService. $scope.topics is then used to show all topics. I have a button that open a modal where a new topic is add in MongoDB. The modal is using another controller AddTopicController. How can I update $scope.topics from HomeworkPageController in AddTopicController ? I want to do this because after I close the modal, the list of all topics should be refreshed, it must contain the topic that has been added. I tried to use HomeworkPageController in AddTopicController and then call the method getAllMainTopics but the $scope.topics from html is not updated. Thanks.
Here is HomeworkPageController:
app.controller('HomeworkPageController', ['$scope','TopicService',
function ($scope, TopicService) {
$scope.topics = [];
$scope.getAllMainTopics = function () {
TopicService.getAllMainTopics('homework')
.success(function(data) {
$scope.topics = data;
}
$scope.addTopic = function () {
ModalService.openModal({
template: "templates/addTopic.html",
controller: 'AddTopicController'
});
}
]);
Here is AddTopicController:
app.controller('AddTopicController', ['$scope','$controller', '$timeout','TopicService', '$modalInstance',
function ($scope, $controller, $timeout,TopicService, $modalInstance) {
var homeworkPageController = $scope.$new();
$controller('HomeworkPageController',{$scope : homeworkPageController });
$scope.save = function() {
TopicService.saveTopic(data)
.success(function(result){
homeworkPageController.getAllMainTopics();
$modalInstance.close();
})
}
}]);
Here is the view where I use $scope.topics:
<div class="homework-content-topic-list" ng-repeat="topic in topics">
<label> {{ topic.subject }} </label>
</div
You should probably keep your list of topics in a service and then inject that service into both controllers. This way you would be able to access and update the topics in both of your controllers. It could look something like
app.controller('HomeworkPageController', ['$scope','TopicService',
function ($scope, TopicService) {
$scope.topics = TopicService.topics;
// Do stuff here
]);
Then you just need to modify your TopicService to have it's methods work on the stored object.
you can solve this by two methods
1)look at the example given in ui-bootstrap's website. They have given an example that will suit your requirement - plunker. There are three items in the modal - item1, item2, item3. If you select one of those items and click 'ok', the selected item is sent to the main controller through "resolve" attribute in the $scope.open function.
2)You can write a custom service that acts as a bridge to the two controllers and you can write getter and setter methods in the service.
angular.module('app').service('popupPageService', function() {
var topics;
var setDetails = function(param) {
topics = param;
};
var getDetails = function() {
return topics;
};
return {
setDetails: setDetails,
getDetails: getDetails,
};
});
call the setDetails function in the AddTopicController and once when you come out of the modal, update your $scope.topics in HomeworkPageController by pushing the new value added (getDetails)

AngularJS push of undefined

Cannot call method 'push' of undefined
I receive that error when my AngularJS runs the following:
$scope.ok = function () {
$modalInstance.close();
$scope.key.push({ title: '', gps:'', desc:''});
};
I declared my $scope.key = []; right after my .controller as I need to be able to use the $scope.key in other parts of the application. Could someone please point out where I should be declaring this?
$scope.ok is my Save Button which pulls the data from my input fields and $scope.plotmarkers is what I am using to pull the data from the inputs that have been pushed.
app.controller('MenuSideController', ['$scope','$modal','$log', function($scope, $modal, $log) {
$scope.key = [];
$scope.createmarker = function () {
var modalInstance = $modal.open({
templateUrl: 'template/modal-add-marker.html',
controller: ModalInstanceCtrl,
resolve: {}
});
modalInstance.result.then(function (selectedItem) {
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
var ModalInstanceCtrl = function ($scope, $modalInstance) {
$scope.ok = function () {
$modalInstance.close();
$scope.key.push({ title: '', gps:'', desc:''});
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
$scope.plotmarkers = function() {
console.log($scope.key);
};
}]);
Don't forget to pass a scope to $modal.open! If you don't, it will default to the root scope, which is not a child of the controller's scope, so key is not defined on it or its parents. You can use { scope: $scope.$new(), ... } as a parameter of $model.open to pass it a child of the controller's scope. See the docs for details. Good luck!
I think you are using $modal a bit improperly.
So you have two controller here - one for the application logic and one for the modal window itself. According to best practice you shouldn't interact between different controllers directly (the case with parent-child directive is exclusion but honestly speaking it not direct interaction - rather your linker function used the controller from parent directive). Instead of interaction between controller in general we should use services. It is just additional note.
What is related to your question - you have two things here to keep in mind:
if you want to pass the information from the controller to the modal window you should use resolve property which actually specifies multiple functions which are called to get the data and then injected to the modal windows controller as a function parameter. This way you can pass some data from the main controller.
if you need to pass the result back you should use result property of the modal instance which is the promise (by using your $modalInstance.result.then(function(result){ ... }); ) To pass this object from the modal you can close it with the result as a parameter like this: $modalInstance.close(result);
Hope this helps. For further details you can look at the documentation for the $modal: Angular Bootstrap

AngularJS controller constructor and instantiation with arguments

Is it possible to instantiate a controller in AngularJS and pass arguments to its constructor like in OOP ? I can't figure out how to refactor 3 identical controller with just variables name and content which change...
Thanx.
If you have 3 separate sections on the page that have very similar controller code, it sounds like you should consider using a directive. Even if you don't need to control the DOM directly (which is the classic reason to use directive), and only need the standard Angular data-bindings, then this is a nice way to reuse controllers in different contexts by the attributes set on the directive.
You can see a working plunkr at
http://plnkr.co/edit/qclp6MOxGWP7Ughod4T8?p=preview
But the key point is directives can bind-to variables in their parent scope's controller. Say, in the parent scope you have 3 variables, so:
$scope.myVariable1 = 'Value 1';
$scope.myVariable2 = 'Value 2';
$scope.myVariable3 = 'Value 3';
Then you can setup 3 instances of the directive in the template:
<my-directive my-param="myVariable1"></my-directive>
<my-directive my-param="myVariable2"></my-directive>
<my-directive my-param="myVariable3"></my-directive>
Then each directive can use the variable in the 'my-param' attribute
scope: {
'myParam':'='
}
The '=' means that in the scope of the directive you have a variable, called 'myParam', that is equal (+ bound to) the variable specified by the 'my-param' attribute on the directive. So on the template of the directive, you can use:
<div>Value of parameter: {{myParam}}</div>
And in the controller of the directive, you have access to is as:
$scope.myParam
And should then be able to customise its behaviour based on that instance's myParam.
You can create services with a common interface and then pass the corresponding services to each controller through dependency injection. In my code this is the case of table controllers where the only thing that changes is the data source:
Imagine you have some code that looks like this
angular.module('myapp').controller('TableACtrl', ['$scope', function($scope) {
$scope.data = // get the data from some source
$scope.someFunction = function () { ... };
$scope.someOtherFunction = function () { ... };
}]);
angular.module('myapp').controller('TableBCtrl', ['$scope', function($scope) {
$scope.data = // get the data from some other source
$scope.someFunction = function () { ... };
$scope.someOtherFunction = function () { ... };
}]);
It looks something like:
var tableCtrl = function($scope, dataSource) {
$scope.data = dataSource.getData();
$scope.someFunction = function () { ... };
$scope.someOtherFunction = function () { ... };
};
angular.module('myapp')
.controller('TableACtrl', ['$scope', 'dataSourceA', tableCtrl])
.controller('TableBCtrl', ['$scope', 'dataSourceB', tableCtrl])
angular.module('myapp')
.service('dataSourceA', function() {
return {
getData: function() { ... }
};
});
angular.module('myapp')
.service('dataSourceB', function() {
return {
getData: function() { ... }
};
});
I would still put things within a module, so you don't pollute the global window. But that's a different issue.

AngularJS: Resolving not in RouteProvider but in Controller?

I saw some sample code here Delaying AngularJS route change until model loaded to prevent flicker
And straight away I though this was the right way to go, I need to have my controller LOAD only when a resolve is finished loading, normally most of the examples around tell you to put the code under resolve in the routeprovder as an inline function, but this sounds wrong. The controller needs it so why not have the controller implement the function to resolve. This sounded just what I was looking for ie. This seems to use the prototype pattern ??
function PhoneListCtrl($scope, phones) {
$scope.phones = phones;
$scope.orderProp = 'age';
}
PhoneListCtrl.resolve = {
phones: function(Phone, $q) {
// see: https://groups.google.com/forum/?fromgroups=#!topic/angular/DGf7yyD4Oc4
var deferred = $q.defer();
Phone.query(function(successData) {
deferred.resolve(successData);
}, function(errorData) {
deferred.reject(); // you could optionally pass error data here
});
return deferred.promise;
}
}
Problem is I have my controller like so
'use strict';
angular.module('TestApp')
.controller('ItemsCtrl', function ($scope) {
});
So how do I apply a new function on the controller when my controller is declared inside a module ?
What I really need is something like
TestCtrl.resolve = {
items: function( $q) {
..........
return deferred.promise;
}
}
This then would allow me to have in my routeprovider..
when('/items', {
templateUrl: 'views/items.html',
controller: 'TestCtrl',
resolve: 'TestCtrl.resolve'}). // Need to use ' around resolve?
But I am confused how I would get this to work ?
I would really love any feedback, I am at a loss.
Not possible to define like 'TestCtrl.resolve' if you want to use resolve with .controller syntax then you have to define inline within route provider . The advantage of inline resolve in routeprovider is that you can reuse controller easily but using same controller and changing the logic in resolve function
You can also use a service:
resolve: {data : function(service) {
return service.getData();
}}

Resources