In my angular project I'm using Angular.js material. And I want to show $mdialog with custom controller, where user changes some data and this data should be applied to my $scope variable. Example what I do now:
function myControllerFn($scope, MyService){
// I do copy of my service variable because I don't want to change it until user will click save button
$scope.name = angular.copy(MyService.name);
$scope.editCurrentProfile = function() {
$scope.showEditProfileDialog($scope.name).then(function(name){
$scope.name = name;
}
}
$scope.showEditProfileDialog = function(name) {
var deferred = $q.defer();
$mdDialog.show({
controller: 'editProfileViewCtrl',
templateUrl: 'controllers/editProfileDialog.tmpl.html',
locals: {
name: name,
deferred: deferred
}
});
return deferred.promise;
};
}
Then in dialog controller I do:
function editProfileViewCtrl($scope, name, deffered) {
deferred.resolve('newName');
}
But I think it is the wrong way. So what is the best way to communicate between two view controllers in angular without new service ? Or better create another service like: EditDialogService, where I will save results ?
When you open a modal, the show() function returns a promise.
$scope.showEditProfileDialog = function(name) {
var modalInstance = $mdDialog.show({
controller: 'editProfileViewCtrl',
templateUrl: 'controllers/editProfileDialog.tmpl.html',
locals: {
name: name
}
});
modalInstance.then(function(result){
// acces what is returned
// In your case, you would do
$scope.name = result;
}, function(error){
// Usually when you cancel your modal
});
}
Your modal controller can be injected with $mdDialog.
function editProfileViewCtrl($scope, name, $mdDialog) {
$scope.close = function() {
$mdDialog.hide('newName');
}
}
You should create a directive with your user as scope variable. Angular in itself is handling the data binding.
It is possible to create a minimal controller function that has access to $scope.
$mdDialog.show({
controller: function () { this.parent = $scope; },
templateUrl: 'controllers/editProfileDialog.tmpl.html',
locals: {
name: name,
deferred: deferred
}
});
Related
Im using md-dialog from Material Design and I came across a small issue which causes me a lot of trouble.
I'm using this dialog as a form for creating a new record in db and I need its controller to be loaded from external file. The reason is that I'm using the same dialog in many places of the app (in many other controllers) and I dont want to copy and paste it to each one of them.
I've tried to write it as a service, but the problem is, as I'm binding data from form to the controller I'm using $scope and that way i got "$scope is not defined". When I add $scope as dependencies in that service, I'got injection error.
Do you have any ideas how to load modal controller externally so it will work even with using of $scope?
$scope.showNewContactDialog = function($event) {
var parentEl = angular.element(document.body);
$mdDialog.show({
parent: parentEl,
targetEvent: $event,
templateUrl: 'app/Pages/directory/contacts/newContact.dialog.html',
controller: NewCompanyContactDialogCtrl,
clickOutsideToClose: true,
hasBackdrop: true
});
};
// New User dialog controller
function NewCompanyContactDialogCtrl($scope, $mdDialog) {
var self = this;
$scope.modalIcon = "add";
$scope.modalTitle = 'Nová položka';
$scope.modalAdvanced = true;
// Country Selector
apiCalls.getData(countryUrl, function(response){
$scope.countries = response;
})
// Add New Object
$scope.newItem = function() {
var url = baseUrl + 'new/';
var data = JSON.stringify({
code: $scope.newItem.contactCode,
first_name: $scope.newItem.contactFirstName,
last_name: $scope.newItem.contactLastName,
street: $scope.newItem.contactStreet,
city: $scope.newItem.contactCity,
country: $scope.newItem.contactCountry,
postal: $scope.newItem.contactPostal,
pobox: $scope.newItem.contactPobox,
price_lvl: $scope.newItem.contactPriceLvl,
orgid: $cookies.get('orgid')
});
apiCalls.postData(url, data, function(response){
console.log(response);
// Toast
if(response.status == 201){
$mdToast.show(
$mdToast.simple()
.textContent('Záznam bol vytvorený.')
.position('bottom right')
.action('Skryť')
.highlightAction(true)
.highlightClass('md-warn')
);
$mdDialog.cancel();
}
});
}
To use as service you can do something like:
angular.module('myApp').factory('newCompModal', function($mdDialog){
var parentEl = angular.element(document.body);
function show($event){
return $mdDialog.show({
parent: parentEl,
targetEvent: $event,
templateUrl: 'app/Pages/directory/contacts/newContact.dialog.html',
controller: 'NewCompanyContactDialogCtrl',
clickOutsideToClose: true,
hasBackdrop: true
});
}
return {
show: show
}
});
Then in any controller:
angular.module('myApp').controller('someController',function($scope,newCompModal){
$scope.newCompanyModalShow = newCompModal.show;
})
And pass event in from view
<button ng-click="newCompanyModalShow($event)">New Company</button>
If you need to pass data also from controller to modal you can add another argument and pass it to locals property of $mdDialog or share through another service property
Example of a dialog with external controller:
$mdDialog.show({
scope : scope,
preserveScope : true,
templateUrl : 'template/search.html',
targetEvent : event,
clickOutsideToClose : true,
fullscreen : true,
controller : 'DialogController'
});
And the controller search.js:
(function() {
angular.module('myApp')
.controller('DialogController', DialogController);
DialogController.$inject = ['$scope', '$mdDialog'];
function DialogController($scope, $mdDialog) {
$scope.closeOpenedDialog = closeOpenedDialog;
function closeOpenedDialog() {
$mdDialog.hide();
}
}
})();
If your mdDialog config doesn't recognize your controller name because it belongs to an external file, then instead of doing this:
controller : 'DialogController'
You should load your controller as a directive in your dialog's view:
<md-dialog ng-controller="DialogController">
...
</md-dialog>
I have an application that allows the user to create and edit records in a modal Angular Material Design Dialog ($mdDialog)
My problem is to put the object returned by the dialog into a collecion that is in the main controller. Is there a way to do that?
angular.module("module").controller("mainController", function ($scope, $mdDialog) {
$scope.Users = [];
function OpenEditWindow(userToEdit) {
$mdDialog.show({
templateUrl: 'Views/user.html',
controller: 'UserDialogController',
clickOutsideToClose: true,
locals: { // Envia valores para o controller do dialog
User: userToEdit
}
}).then(function (data) {
// Put the object edited into the collection on main controller, to show on the screen
$scope.Users.push(data); // ******** NOT WORKS
});
}
});
angular.module('module')
.controller('UserDialogController', function ($scope, $mdDialog, User) {
$scope.User = User;
$scope.Save = function () {
$mdDialog.hide($scope.User);
}
});
Maybe you can centralize your data and create a service model that persists the state of your user across your application. Such a service can be passed into your controller like every other dependency.
angular.module('module')
.controller('UserDialogController', function ($scope, $mdDialog, User, UserModel) {
$scope.User = User;
$scope.Save = function () {
$mdDialog.hide($scope.User);
}
});
angular.module('module').factory('UserModel', function () {
var userModel = this;
userModel.set = function(){
...
}
return userModel;
});
Given that services are singletons you are guaranteed to have access to the latest and greatest information every time.
Try using a style guide which will greatly improve your code, logic and overall quality. Such a guide could be John Papa's Angular Style Guide
What is returned from $mdDialog.show is a promise. You are putting your then in the wrong place. This is shown in the angular material docs here
angular.module("module").controller("mainController", function ($scope, $mdDialog) {
$scope.Users = [];
function OpenEditWindow(userToEdit) {
var promise = $mdDialog.show({
templateUrl: 'Views/user.html',
controller: 'UserDialogController',
clickOutsideToClose: true,
locals: { // Envia valores para o controller do dialog
User: userToEdit
}
});
promise.then(function (data) {
// Put the object edited into the collection on main controller, to show on the screen
$scope.Users.push(data); // ******** NOT WORKS
});
}
});
I have a list of items. When you click on an item, it brings up a modal to display data for that item. In the controller for the list, there's a function, openRecentsModal, that takes the data object from the ng-repeat list, and creates it on a new scope when the function is run. The new modal then has that object available as $scope.recentsFoldersData. I need to write a unit test to ensure recentsFolderData is defined on the scope, but everything I've tried results in "expected undefined to be defined." I'm hoping somebody can help.
Here's the method in the list controller to open a modal:
function openRecentsModal(obj) {
var scope = $rootScope.$new();
scope.recentsFoldersData = obj;
var controller = 'recentsFoldersDetailController';
$modal.open({
scope: scope,
controller: controller,
templateUrl: 'js/modal/recents/folder/recentsFoldersDetail.tpl.html'
});
}
Here's the modal's controller:
angular.module('modal.recents.folder', [])
.controller('recentsFoldersDetailController', recentsFoldersDetailController);
recentsFoldersDetailController.$inject = ['$scope', '$modalInstance'];
function recentsFoldersDetailController($scope, $modalInstance) {
$scope.close = function close() {
$modalInstance.dismiss('close');
};
}
Finally, here's the unit tests I'm working on (I've excluded the ones that are passing, as well as the helper functions that aren't needed for this test):
describe('recents folders modal controller tests', function() {
var scope, q, modal, mockDetailController, mockListController, mockRecentService, mockFolderService, mockModalInstance, $httpBackend;
beforeEach(module('mainApp'));
beforeEach(inject(function($rootScope, $q, $controller, $modal, _recentService_, _folderService_, $injector) {
q = $q;
scope = $rootScope.$new();
modal = $modal;
$httpBackend = $injector.get('$httpBackend');
$httpBackend.whenGET('js/modal/recents/folder/recentsFoldersDetail.tpl.html').respond(200, '');
mockRecentService = _recentService_;
mockFolderService = _folderService_;
mockModalInstance = {
dismiss: jasmine.createSpy('modalInstance.dismiss')
};
mockDetailController = function() {
return $controller('recentsFoldersDetailController', {
'$scope': scope,
'$modalInstance': mockModalInstance
});
};
mockListController = function() {
return $controller('recentsListFoldersController', {
'$scope': scope,
'$modal': modal,
'recentService': mockRecentService,
'folderService': mockFolderService
});
};
}));
describe('scope tests', function() {
it('should place the data on the scope when openRecentsModal is called', function() {
var obj = defaultSuccessfulRecentsDataResponse();
mockListController();
spyOn(scope, 'openRecentsModal');
scope.openRecentsModal(obj);
expect(scope.openRecentsModal).toHaveBeenCalledWith(obj);
mockDetailController();
expect(scope.recentsFoldersData).toBeDefined();
});
});
/* helper functions */
function defaultSuccessfulRecentsDataResponse() {
return {
id: 'id 1',
name: 'first name',
description: 'first description'
};
}
});
I was able to fix this with a simple change to the function calling the modal
function openRecentsModal(obj) {
$rootScope.recentsFoldersData = obj;
var controller = 'recentsFoldersDetailController';
$modal.open({
//scope: scope,
controller: controller,
templateUrl: 'js/modal/recents/folder/recentsFoldersDetail.tpl.html'
});
}
By allowing the object to be placed on $rootScope (UI-Bootstrap modal's default setting), rather than a new $scope, the test came back with a defined value
it('should put recents object on the scope', function() {
mockListController();
scope.openRecentsModal(defaultSuccessfulRecentsDataResponse());
mockDetailController();
expect(scope.recentsFoldersData).toBeDefined();
});
I'm facing the following situation in my Angular application and I would like to have some advices here.
I have a page where I show some products, this page is managed by a controller called 'ProductsController'. This controller has a method called 'showProductDetails' which is called once the user clicks on a specific product, and the goal of this method is just to retrieve the details of the product and to display these details in a modal panel.
Nothing really special until here. The problem is that because of modularity I would like to attach a different controller to the modal panel, and to manage all the logic of this modal panel in the new controller, in this case 'ProductDetailController'. The problem is that I retrieve the data of the product before opening the modal panel, but as I retrieve this data in the scope of the first controller, from the second controller I cannot access to the product that I have previously retrieved. I've been told that to share data between controllers in angularJs is done through services, but I don't see how a stateless service can help me here.
Here is my code to understand better the situation:
The first controller:
app.controller('ProductsController', ['$scope','productsFactory','commonFactory','productsFactoryHelper','$filter','$modal',function ($scope,productsFactory,commonFactory,productsFactoryHelper,$filter,$modal)
{
$scope.showProductDetails = function (size,product) {
$scope.showLoader('Loading the details of the product. Please wait...');
productsFactoryHelper.Product.query({id:product.id},function(response)
{
$scope.selectedProduct=response;
$scope.hideLoader();
var modalInstance = $modal.open({
templateUrl: 'productDetail.html',
controller: 'ProductDetailController',
size: size
});
},function(error)
{
commonFactory.Pop('error','This product is not available at this moment. Please try again later. If the problem persists contact a system administrator');
$scope.hideLoader();
});
};
_init();
}]);
And the second controller:
app.controller('ProductDetailController',['$scope','$modalInstance', function ($scope, $modalInstance) {
$scope.ok = function () {
$modalInstance.close();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}]);
So basically the question is how can access from the 'ProductDetailController' to the object 'selectedProduct' which is in the scope of the 'ProductsController'.
Thank you for your help.
Use resolve of the $modal to send your data to the new controller like below.
app.controller('ProductsController', ['$scope','productsFactory','commonFactory','productsFactoryHelper','$filter','$modal',function ($scope,productsFactory,commonFactory,productsFactoryHelper,$filter,$modal)
{
$scope.showProductDetails = function (size,product) {
$scope.showLoader('Loading the details of the product. Please wait...');
productsFactoryHelper.Product.query({id:product.id},function(response)
{
$scope.selectedProduct=response;
$scope.hideLoader();
var modalInstance = $modal.open({
templateUrl: 'productDetail.html',
controller: 'ProductDetailController',
size: size,
resolve:{
"selectedProduct":response
}
});
},function(error)
{
commonFactory.Pop('error','This product is not available at this moment. Please try again later. If the problem persists contact a system administrator');
$scope.hideLoader();
});
};
_init();
}]);
I dont know about the producfactory helper product query has a promise if it has a promise you can use like this..
$scope.showProductDetails = function (size,product) {
$scope.showLoader('Loading the details of the product. Please wait...');
var modalInstance = $modal.open({
templateUrl: 'productDetail.html',
controller: 'ProductDetailController',
size: size,
resolve:{
"selectedProduct":productsFactoryHelper.Product.query({id:product.id})
}
});
};
And in the ProductDetailController you can inject this selectedProduct like below
app.controller('ProductDetailController',['$scope','$modalInstance','selectedProduct ' function ($scope, $modalInstance,selectedProduct ) {
$scope.ok = function () {
$modalInstance.close();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}]);
This can indeed be done through services, since they are stateless and keep their data once instantiated.
function productService($http) {
this.products = [];
this.loadProducts() {
$http.get('/url/to/your/product/api').then(function(err, data) {
this.products = data.products;
});
};
this.getProducts = function() {
return this.products;
}
}
angular
.module('yourModule')
.service('productService', productService);
You can then just inject productService in both controllers, load the products using productService.loadProducts(), and get them using productService.getProducts().
This is just an example. Services can be used to share any kind of data.
Services are indeed the answer for you, or you can use pure eventing if you do not need to access the data more then once.
Pure Eventing
app.controller('parentCtrl', function($scope) {
// Do something
// Action completed
#scope.$emit('someactionComplete', data);
});
app.controller('childCtrl', function($scope) {
$scope.$on('someactionComplete', function(data) {
// Process data
});
});
Using a service. The advantage of using a service is that the data is persisted.
app.controller('parentCtrl', function($scope, MyService) {
// Do something
// Action completed
MyService.setData(data);
#scope.$emit('someactionComplete');
});
app.controller('childCtrl', function($scope) {
$scope.$on('someactionComplete', function() {
MyService.getData(data);
});
});
You could further enhance this were the service loaded the data and returns a promise in the getter.
I have tried everything to get ui-router's resolve to pass it's value to the given controller–AppCtrl. I am using dependency injection with $inject, and that seems to cause the issues. What am I missing?
Routing
$stateProvider.state('app.index', {
url: '/me',
templateUrl: '/includes/app/me.jade',
controller: 'AppCtrl',
controllerAs: 'vm',
resolve: {
auser: ['User', function(User) {
return User.getUser().then(function(user) {
return user;
});
}],
}
});
Controller
appControllers.controller('AppCtrl', AppCtrl);
AppCtrl.$inject = ['$scope', '$rootScope'];
function AppCtrl($scope, $rootScope, auser) {
var vm = this;
console.log(auser); // undefined
...
}
Edit
Here's a plunk http://plnkr.co/edit/PoCiEnh64hR4XM24aH33?p=preview
When you use route resolve argument as dependency injection in the controller bound to the route, you cannot use that controller with ng-controller directive because the service provider with the name aname does not exist. It is a dynamic dependency that is injected by the router when it instantiates the controller to be bound in its respective partial view.
Also remember to return $timeout in your example, because it returns a promise otherwise your argument will get resolved with no value, same is the case if you are using $http or another service that returns a promise.
i.e
resolve: {
auser: ['$timeout', function($timeout) {
return $timeout(function() {
return {name:'me'}
}, 1000);
}],
In the controller inject the resolve dependency.
appControllers.controller('AppCtrl', AppCtrl);
AppCtrl.$inject = ['$scope', '$rootScope','auser']; //Inject auser here
function AppCtrl($scope, $rootScope, auser) {
var vm = this;
vm.user = auser;
}
in the view instead of ng-controller, use ui-view directive:
<div ui-view></div>
Demo
Here is how I work with resolve. It should receive promise. So I create service accordingly.
app.factory('User', function($http){
var user = {};
return {
resolve: function() {
return $http.get('api/user/1').success(function(data){
user = data;
});
},
get: function() {
return user;
}
}
});
This is main idea. You can also do something like this with $q
app.factory('User', function($q, $http){
var user = {};
var defer = $q.defer();
$http.get('api/user/1').success(function(data){
user = data;
defer.resolve();
}).error(function(){
defer.reject();
});
return {
resolve: function() {
return defer.promise;
},
get: function() {
return user;
}
}
});
These are almost identical in action. The difference is that in first case, service will start fetching date when you call resolve() method of service and in second example it will start fetch when factory object is created.
Now in your state.
$stateProvider.state('app.index', {
url: '/me',
templateUrl: '/includes/app/me.jade',
controller: function ($scope, $rootScope, User) {
$scope.user = User.get();
console.log($scope.user);
},
controllerAs: 'vm',
resolve: {
auser: function(User) {
return User.resolve()
}
}
});