I have a little issue by using a customize directive within the template field of UI-Bootstrap modal directive.
My aim is send data to modal via resolve attribute and re-use these resolved parameters inside the controller of my own directive.
var app = angular.module('app', ['ui.bootstrap']);
app.controller('MyCtrl', ['$scope', '$modal', function($scope, $modal) {
$scope.openModal = function () {
var popup = $modal.open({
template: '<my-modal></my-modal>',
resolve : {
mydata : function() {
return 42;
}
}
});
};
}]);
app.controller('ModalController', ['$scope', 'mydata', function($scope, mydata) {
//The error is in this directive controller
$scope.mydata = mydata;
}]);
app.directive('myModal', function() {
return {
restrict: 'E',
templateUrl : 'mymodal.html',
controller : 'ModalController',
replace: true
};
});
Maybe I proceed in the wrong way.
Any suggest to make this code functionnal ?
http://plnkr.co/edit/RND2Jju79aOFlfQGnGN8?p=preview
The resolve parameters are only injected to the controller defined in the $modal.open config parameters, but you want to inject it to the directive controller. That will not work. Imagine you would use the myModal directive somewhere else, there wouldn't be a myData object that could be used.
But i don't realy see, what you need the directive for. You could go much easier this way:
app.controller('MyCtrl', ['$scope', '$modal',
function($scope, $modal) {
$scope.openModal = function() {
var popup = $modal.open({
templateUrl: 'mymodal.html',
controller: 'ModalController',
resolve: {
mydata: function() {
return 42;
}
}
});
};
}
]);
// Here the mydata of your resolves will be injected!
app.controller('ModalController', ['$scope', 'mydata',
function($scope, mydata) {
$scope.mydata = mydata
}
]);
Plunker: http://plnkr.co/edit/bIhiwRjkUFb4oUy9Wn8w?p=preview
you need to provide an Object "mydata". Ensure, that you have a correct implemented factory which provides your myData Object. If you had done that, you can "inject" your myData Object where ever you want to.
yourApp.MyDataFactory = function () {
var myData = {i: "am", an: "object"};
return myData;
}
this would provide an "myData" Object
I'm not sure what you are trying to accomplish with the directive, but if you are trying to provide a generic way to invoke the $model, that you can then use from many places in your app, you may be better off to wrap $model with a service. Than you can then call from other places in your app.
I forked and modified your plunkr to work this way: http://plnkr.co/edit/0CShbYNNWNC9SiuLDVw3?p=preview
app.controller('MyCtrl', ['$scope', 'modalSvc', function($scope, modalSvc) {
var mydata = {
value1: 42
};
$scope.openModal = function () {
modalSvc.open(mydata);
};
}]);
app.factory('modalSvc', ['$modal', function ($modal) {
var open = function (mydata) {
var modalInstance,
modalConfig = {
controller: 'ModalController',
resolve: {
mydata: function () {
return mydata;
}
},
templateUrl: 'mymodal.html'
};
modalInstance = $modal.open(modalConfig);
return modalInstance;
};
return {
open: open
};
}]);
Also, I changed mydata to be an object, rather than '42', as I am sure you will have other data to pass in. the markup was updated accouringly:
<div class="modal-body">
BODY {{mydata.value1}}
</div>
Doing it this way, the resolve property works, and you can get your data.
As for the other answers mentioning you must define mydata, the resolve property passed into $model does this for you, so it can be injected into the modal's controller (ModalController), like you have done.
Related
I pass the link of this example, I have a variable called "greeting" that changes its value in a modal window but does not bind. Do not share scope?
http://jsfiddle.net/Bibidesign/Lodkh4jy/7/
var myApp = angular.module('myApp',['ui.bootstrap']);
myApp.controller('GreetingController', ['$scope', '$modal', function($scope,$modal) {
$scope.greeting = 'Hello!';
$scope.changeValues = function() {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
backdrop: 'static',
scope: $scope,
controller: function($scope, $modalInstance) {
$scope.greeting = "Welcome";
$scope.cancel = function(){
modalInstance.close();
}
$scope.ok = function(){
modalInstance.close();
}
}
});
};
}]);
You are partially there. We just need to set up passing the variables between the GreetingController and the Modal controller. We will use the resolve property on the object passed into $modal.open() to pass a value to the modal controller, and than when we close the modal, we will pass back the new value through those the results object. We are also removing scope: $scope, because the controller declaration is overriding that scope copy, and we want to keep these scopes separate. Example to follow.
This answer has a more thorough explanation of what is happening with the resolve, but it is essentially just a simplified way to resolve promises and guarantee data is in the controller before initializing the modal.
myApp.controller('GreetingController', ['$scope', '$modal', function($scope,$modal) {
$scope.greeting = 'Hello!';
// this will handle everything modal
$scope.changeValues = function() {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
backdrop: 'static',
// resolve is an object with all the objects we will pass to the controller
// I'm adding more than just greeting for examples sake
resolve: {
greeting: function () {
return $scope.greeting;
},
testData: function () {
return "This is some random test data";
}
}
// on the controller parameters, we add the properties we are resolving
controller: function($scope, $modalInstance, greeting, testData) {
// the greeting variable passed in through resolve
console.log('greeting', greeting); // expected output: greeting Hello!
// the testData passed in through resolve
console.log('testData', testData); // expected output: testData This is some random test data
// this will be the greeting you are able to access on the modal
// we can set this to the value from the resolve or a new value like below
$scope.greeting = "Welcome";
//$scope.greeting = greeting // this will set the modal to the value passed in from the resolve
// NOTE*** I changed this call to dismiss()
$scope.cancel = function(){
modalInstance.dismiss();
}
$scope.ok = function(){
// what ever we pass in to close will be accessible in the result below
// we are passing in the new greeting - 'Welcome'
modalInstance.close($scope.greeting);
}
}
});
// this is the majority of the new code
modalInstance.result.then(function(okReturnObject){
// this okReturnObject will be whatever you pass in to modalInstance.close()
console.log('return', okReturnObject); // expectedOutput: return Welcome
// this is where we are setting the original value to the new value from the modal
$scope.greeting = okReturnObject;
},function(){
// will be run after modalInstance.dismiss()
console.log("Modal Closed");
});
};
}]);
You can't url refer the text/ng-template as the templateUrl. Instead, add the html form in a separate html file and refer to that in the modal declaration.
Here's a working plunker https://plnkr.co/edit/lMZK0uGNsEmRjAN7teVZ?p=preview
I think I'm missing something but cannot figure what.
Basically I'm trying to pass an object to the modal like below, but instead of getting the passed object I gets null...so I think is a problem with the scope but I'm new in Angular and need some help.
Controller
app.controller("musicViewModel", function ($scope, $http, $location, $uibModal, $log) {
$scope.selected = null;
$scope.open = function (item) {
$scope.selected = item;
$log.info('Open' + $scope.selected); // get right passes object
var modalInstance = $uibModal.open({
templateUrl: 'myModalContent.html',
controller: 'musicViewModel',
size: 'lg',
resolve: {
items: function () {
return $scope.selected;
}
}
});
};
$scope.toggleAnimation = function () {
$scope.animationsEnabled = !$scope.animationsEnabled;
};
});
View
<div class="row" ng-controller="musicViewModel">
<script type="text/ng-template" id="myModalContent.html">
<div class="modal-header">
<h3 class="modal-title">I'm a modal!</h3>
</div>
<div class="modal-body">
<ul>
<li>
{{ selected }} // always gets null
</li>
</ul>
</div>
</script>
</div>
I'd suggest you to pass the scope of your own controller instead of passing same controller again, by doing that you can remove the resolve also.
var modalInstance = $uibModal.open({
templateUrl: 'myModalContent.html',
scope: $scope, //passed current scope to the modal
size: 'lg'
});
Otherwise you need to create a new controller and assign that controller for modal while opening it.
When you use resolve, the map is injected into the given controller.
I recommend that u use a different controller to handle the modal functionality (separation of concerns).
I also recommend to use dependency injection to support minification of the code. Step 5 on the Angular tutorial wil explain this.
A simplified example of the modal controller would be.
(function () {
'use strict';
var app = angular.module('App');
app.controller('musicDetailController',
['$scope', '$uibModalInstance', 'items',
function ($scope, $uibModalInstance, items) {
$scope.items = items;
}]);
}());
You cannot pass an object directly.
I've tried all the solutions above, but wasn't really satisfied. I've solved the issue by writing a simple parser that enables you to pass both strings and objects directly to the modal, through the provided resolve function.
app.controller('ModalController', ['$uibModal', '$scope', function ($uibModal, $scope) {
// Initialize $modal
var $modal = this;
// Open component modal
$modal.open = function (component, size, data) {
// Init modal
var modalInstance = $uibModal.open({
ariaLabelledBy: 'modal-title',
ariaDescribedBy: 'modal-body',
component: component,
size: size || 'md',
resolve: parseResolve(data)
});
};
// Parse the resolve object
function parseResolve(data) {
if (typeof data === 'string') {
return {
data: function() {
return data;
}
}
}
else if (typeof data === 'object') {
var resolve = {};
angular.forEach(data, function(value, key) {
resolve[key] = function() {
return value;
}
})
return resolve;
}
}
}]);
When usings strings
Template:
<button ng-click="$modal.open('modalSomething', 'md', 'value'">
Click
</button>
Component:
bindings: {
resolve: '#'
}
When using objects
Template:
<button ng-click="$modal.open('modalSomething', 'md', {key1: value1, key2: value2})">
Click
</button>
Component:
bindings: {
resolve: '<'
}
I got the below code working:
this.OpenModal = function() {
var modalInstance = $uibModal.open({
url: "/name?parameter=" + $scope.Object.ParamValue,
templateUrl: 'views/module/page.html',
controller: myController
});
}
Consider we have some directive with templateUrl and controller.
angular.module('someApp').directive('myDirective', [function() {
return {
templateUrl:'someTemplate.html',
controller: ['$scope', function($scope) {
this.someFunction = function() {}
}]
}
}])
If we try to get controller now
var directive = angular.element('<my-directive />');
$compile(directive)( scope );
var controller = directive.controller('myDirective');
the controller is undefined.
The answer is to set a template instead of templateUrl in the directive.
angular.module('someApp').directive('myDirective', [function() {
return {
template:'<span>Solution!</span>',
controller: ['$scope', function($scope) {
this.someFunction = function() {}
}]
}
}])
I can't find anything about it in documentation. I think it's a bug.
I have a controller that starts like this (simplified for this question):
angular.module('myApp.controllers')
.controller('MyController', ['$scope', '$routeParams', 'MyService',
function ($scope, $routeParams, MyService) {
MyService.fetchWithId($routeParams.id).then(function(model) {
$scope.model = model;
});
Which is fine, but then in many places throughout the controller, I have functions that are referred to in the view that refer to the model ...
$scope.someFunctionMyViewNeeds = function() {
return $scope.model.someModelAttribute;
};
Since these often run before the fetch completes, I end up with errors like "cannot read property of undefined" when the view tries to see someModelAttribute.
So far, I've tried three things:
// before the fetch
$scope.model = new Model();
...but I really don't want a new model, and in some cases, cannot complete initialization out of the blue without other dependences.
Another idea is to litter the code with defense against the unready model, like:
return ($scope.model)? $scope.model.someModelAttribute : undefined;
... but that's a lot of defense all over the code for a condition that only exists while the fetch completes.
My third idea has been to "resolve" the model in the route provider, but I don't know how to do that and get at the $routeParams where parameter to fetch the model is kept.
Have I missed a better idea?
Try this if you want to use resolve.
var app = angular.module('app', ['ngRoute']);
app.config(function ($routeProvider) {
$routeProvider.when('/things/:id', {
controller: 'ThingsShowController',
resolve: {
model: function ($routeParams, MyService) {
return MyService.fetchWithId(+$routeParams.id);
}
},
template: '<a ng-href="#/things/{{model.id}}/edit">Edit</a>'
});
$routeProvider.when('/things/:id/edit', {
controller: 'ThingsEditController',
resolve: {
model: function ($routeParams, MyService) {
return MyService.fetchWithId(+$routeParams.id);
}
},
template: '<a ng-href="#/things/{{model.id}}">Cancel</a>'
});
});
// Just inject the resolved model into your controllers
app.controller('ThingsShowController', function ($scope, model) {
$scope.model = model;
});
app.controller('ThingsEditController', function ($scope, model) {
$scope.model = model;
});
// The rest is probably irrelevant
app.factory('Model', function () {
function Model(attributes) {
angular.extend(this, attributes);
}
return Model;
});
app.service('MyService', function ($q, Model) {
this.fetchWithId = function (id) {
var deferred = $q.defer();
deferred.resolve(new Model({ id: id }));
return deferred.promise;
};
});
// Just to default where we are
app.run(function ($location) {
$location.path('/things/123');
});
app.run(function ($rootScope, $location) {
$rootScope.$location = $location;
});
// Because $routeParams does not work inside the SO iframe
app.service('$routeParams', function () {this.id = 123;});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.9/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.9/angular-route.min.js"></script>
<div ng-app="app">
<div>Route: {{$location.path()}}</div>
<div ng-view=""></div>
</div>
I have a completely working version of the ui.bootstrap.modal as per the example here http://angular-ui.github.io/bootstrap/#/modal but I want to take a stage further and add it to my controller configuration.
Perhaps I'm taking it too far (or doing it wrong) but I'm not a fan of just
var ModalInstanceCtrl = function ($scope...
My modal open controller:
var controllers = angular.module('myapp.controllers', []);
controllers.controller('ModalDemoCtrl', ['$scope', '$modal', '$log',
function($scope, $modal, $log) {
$scope.client = {};
$scope.open = function(size) {
var modalInstance = $modal.open({
templateUrl: 'templates/modals/create.html',
controller: ModalInstanceCtrl,
backdrop: 'static',
size: size,
resolve: {
client: function () {
return $scope.client;
}
}
});
modalInstance.result.then(function (selectedItem) {
$log.info('Save changes at: ' + new Date());
}, function () {
$log.info('Closed at: ' + new Date());
});
};
}
]);
Modal instance controller:
var ModalInstanceCtrl = function ($scope, $modalInstance, client) {
$scope.client = client;
$scope.save = function () {
$modalInstance.close(client);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
However I would like to change this last controller to:
controllers.controller('ModalInstanceCtrl', ['$scope', '$modalInstance', 'client',
function ($scope, $modalInstance, client) {
$scope.client = client;
$scope.save = function () {
$modalInstance.close(client);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}
]);
If I also update the controller reference within $scope.open for ModalDemoCtrl controller to
controller: controllers.ModalInstanceCtrl
then there are no errors but the save and cancel buttons within the modal window no longer work.
Could someone point out where I am going wrong - possibly a fundamental lack of understanding of the way controllers work within the AngularJS?!
Controller specified in $scope.open needed single quotes around it.
controller: 'ModalInstanceCtrl',
You are referencing your module to variable controllers.
All controllers in angular systems has unique names.
The controller is still "ModalInstanceCtrl" not "controllers.ModalInstanceCtrl".