In my angularJS app, I'm trying to pass a parameter to a modal popup so that when the Modal link is click, a name is displayed in the popup. The modal link is coming from a custom directive which is getting the list on names from an external service.
I've tried following this tutorial to Create an Angularjs Popup Using Bootstrap UI along with the documentation for $uibModal as that tutorial is a bit outdated.
I can get the modal PopUp and controller working but I can't pass a parameter to it.
I replicated the issue on Plunker.
This problem is I can't get the titlename param passed to the popupController from the listings directive (see script.js in Plunker). I don't think I have the resolve set up correctly. With the debugger set in Chrome I can see the titlename value up to this point.
app.directive('listings', [function(){
return {
restrict: 'E',
...
controller: ['$scope','$uibModal', function listingsDirectiveController($scope,$uibModal) {
$scope.open = function (titlename) {
var uibModalInstance = $uibModal.open({
templateUrl: 'popup.html',
controller: 'popupController',
titlename: titlename,
resolve: {
item: function(){
return titlename;
}
}
});
}
}]
};
}]);
But it doesn't get passed to the popupController. In the below code the titlename has value undefined
app.controller('popupController', ['$scope','$uibModalInstance', function ($scope,$uibModalInstance, titlename) {
$scope.title1 = titlename;
$scope.close = function () {
$uibModalInstance.dismiss('cancel');
};
}]);
Any idea why this is happening and how I can fix it? Is this the correct way to use resolve in AngularJS?
You don't need a double brace when using ng-click. See this post for more information on using the double curly braces. So your listings directive should be something like this. You were passing the actual string '{{item.name}}'
{{item.name}} -Popup
Then in your popupController, you were not passing the resolved item value. The controller should read:
app.controller('popupController', ['$scope','$uibModalInstance', 'item', function ($scope,$uibModalInstance, titlename) {
See plunker
First, you want to pass item.name, not the literal string '{{item.name}}' to your open method so change your template to
ng-click="open(item.name)"
Second, your resolved property is named item but you seem to be expecting titlename so change it to
resolve: {
titlename: function() {
return titlename;
}
}
And finally, you don't have an injection annotation for titlename in your controller so you need to add it
app.controller('popupController', ['$scope','$uibModalInstance', 'titlename',
function ($scope,$uibModalInstance, titlename) {
// ...
}])
Fixed Plunker ~ http://plnkr.co/edit/ee7Psz2jXbVSkD0mfhS9?p=preview
First, in your listingsDirective.html, don't use curly brackets to pass in variables. Also, by adding titlename1 to the directive $scope and sharing that parent scope with the child modal, you can access the variables in your modal.
app.directive('listings', [function(){
return {
restrict: 'E',
scope: {
data:'=',
},
templateUrl: 'listingsDirective.html',
replace: true,
controller: ['$scope','$uibModal', function listingsDirectiveController($scope,$uibModal) {
$scope.open = function (titlename) {
$scope.titlename = titlename;
var uibModalInstance = $uibModal.open({
templateUrl: 'popup.html',
controller: 'popupController',
scope: $scope,
resolve: {
item: function(){
return $scope.titlename;
}
}
});
}
}]
};
}]);
app.controller('popupController', ['$scope','$uibModalInstance', function ($scope,$uibModalInstance) {
$scope.title1 = $scope.titlename;
$scope.close = function () {
$uibModalInstance.dismiss('cancel');
};
}]);
New Plunkr: http://plnkr.co/edit/RrzhGCLuBYniGGWvRYI9?p=preview
Related
I am using Angular 1.5.7 version.
I have a directive that takes in the controller name and view name as strings to then invoke the controller respectively.
I am not able to bind username to the invoking controller from the previous controller, I see the value available in my previous controller.
Please can you advise what could be the issue?
myApp.directive("pendingRequests", function() {
return {
restrict: 'E',
controller: "#",
name: "controllerName",
controllerAs: 'pendingReqCtrl',
scope: {},
bindToController: {
username: '=username'
},
templateUrl: function(tElement, tAttrs) {
return tAttrs.templateUrl;
}
};
});
Thank you #westor you saved my day. I just updated my angular and started getting this issue, spent a lot of time fixing it, then I found this. So thought of giving some examples.
Using $onInit in your controller function for binding scope
controller: function () {
this.$onInit = function () {
// your business logic
};
};
In my app, I was binding through scope
myApp.directive('pendingRequests', function () {
return {
scope: {
item:'='
},
bindToController: true,
controller: controller,
controllerAs: 'vm',
controller: function ($scope) {
console.log(this.item); // doesn't logs
this.$onInit = function () {
console.log(this.item); // logs your other directives object
};
}
}
});
using require
myApp.directive('pendingRequests', function () {
return {
scope: {},
bindToController: {},
controller: controller,
controllerAs: 'pendingReqCtrl',
require: {
parent: '^otherDirective'
},
controller: function ($scope) {
console.log(this.parent); // doesn't logs
this.$onInit = function () {
console.log(this.parent); // logs your item object
};
}
}
});
Did you try $onInit to initialize your code?
angularjs doc says this:
Deprecation warning: although bindings for non-ES6 class controllers
are currently bound to this before the controller constructor is
called, this use is now deprecated. Please place initialization code
that relies upon bindings inside a $onInit method on the controller,
instead.
I updated to 1.6.2 and I had to put my code into $onInit method to access the bindings.
I have a controller used to add tasks. On that page a user needs to select a group to act upon. I have written a directive that is used to allow a user to pick groups (folders)
My page controller
function AddTaskController($scope) {
var vm = this;
vm.group = { whatsit: true };
$scope.$watch("vm.group", function () {
console.log("controller watch", vm.group);
},true);
}
The page html where the directive is used
<em-group-selection group="vm.group"></em-group-selection>
The directive configuration
function GroupSelectionDirective() {
return {
scope: {
group: '='
},
controller: GroupSelectionDirectiveController,
controllerAs: 'vm',
templateUrl: '/views/templates/common/folderselection.html'
};
}
The directive controller:
function GroupSelectionDirectiveController($scope) {
var vm = this;
$scope.$watch("group", function () { console.log("yo1", vm.group); }, true)
$scope.$watch("vm.group", function () { console.log("yo2", vm.group); }, true)
}
Now when this fires, both console.log() calls in the directive fire once, with undefined. They never fire again. If in the controller I set vm.group to something else the $watch in the AddTaskController never gets fired.
Why isnt the data binding working?
Update:
I notice that if, in the directive, I change the init() function in my directive to use $scope it works! Can I not, as Fedaykin suggests, use controllerAs with two way data binding?
function init() {
$timeout(function () {
$scope.group.shizzy = 'timeout hit';
}, 200);
}
Turns out that if you use isolate scopes and controlelrAs syntax you need to also use bindToController : true. Without this you will not be able to only use vm and will have to use $scope for the isolate scope variables
More information can be found in the John Papa style guide and this SO answer
The final directive setup is as so:
function GroupSelectionDirective() {
return {
scope: {
group: '='
},
controller: GroupSelectionDirectiveController,
controllerAs: 'vm',
bindToController: true,
templateUrl: '/views/templates/common/folderselection.html'
};
}
I'm using AngularUI router (0.2.13) and have a state defined as such
.state('foo', {
template:'<div some-directive></div>',
resolve: {
foo: function(SomeService) {
return SomeService.something().promise;
}
}
})
and a directive like this:
app.directive('someDirective', function(){
return {
controller: function(data) {
// I want `data` to be injected from the resolve...
// as it would if this was a "standalone" controller
}
}
})
but this doesn't work - the data parameter causes an UnknownProvider error. Defining the directive controller independently and setting it by name in the directive has the same result.
I more or less get why this is happening, but have two questions:
is there a way to do what I'm trying to do?
should I be trying to do it, or have I slipped into an antipattern here?
You can't use resolves in directives, but you can pass the result resolved in the state down to the directive which I think accomplishes what you're looking for.
You'd want to update your state definition to include a controller and set a parameter on the directive:
.state('foo', {
template:'Test<div some-directive something="foo"></div>',
url: 'foo',
resolve:{
foo:function(SomeService){
return SomeService.something();
}
},
controller: function($scope, foo){
$scope.foo = foo;
}
})
Then update the directive to use this parameter:
.directive('someDirective', function(){
return {
controller: function($scope) {
// I want `data` to be injected from the resolve...
// as it would if this was a "standalone" controller
console.log('$scope.something: '+ $scope.something);
},
scope: {
something: '='
}
};
})
Here's a sample plunker: http://plnkr.co/edit/TOPMLUXc7GhXTeYL0IFj?p=preview
They simplified the api. See this thread:
https://github.com/angular-ui/ui-router/issues/2664#issuecomment-204593098
in 0.2.19 we are adding $resolve to the $scope, allowing you to do "route to component template" style
template: <my-directive input="$resolve.simpleObj"></my-directive>,
In my html page I have a button and a directive snippet like so:
<button ng-click="showProfile();"></button>
<profile ng-if="isProfile==true"></profile>
In my controller I have initialized the $scope.isProfile variable = false and have the function called by the button:
$scope.showProfile = function(contact) {
$scope.contact = contact; // this object needs to get passed to the controller that the directive initiates, but how??
$scope.isProfile = true;
};
In my app I have a directive defined as such...
app.directive('profile', function () {
return {
templateUrl: '/contacts/profile',
restrict: 'ECMA',
controller: contactsProfileController,
link:function(scope, element, attrs) {
console.log('k');
}
};
});
Everything is working but I can't figure out how to pass the $scope.contact object to the controller that the directive references.
I've tried adding scope:scope to the return {} of the directive but with no luck. Do I need to do something in the link function? I've spent the entire day reading about directives and am exhausted so any tips would be greatly appreciated!!!
Thanks in advance for any help!
Here's what the controller that's being called from the directive looks like as well:
var contactsProfileController = function($scope,contact) {
$scope.init = function() {
console.log($scope.contact); //this should output the contact value from the showProfile function.
};
....
}
try this on your directive.
<profile ng-if="isProfile==true" contact="contact"></profile>
and add this to the scope
app.directive('profile', function () {
return {
templateUrl: '/contacts/profile',
restrict: 'ECMA',
scope: {
contact: '=contact'
}
controller: contactsProfileController,
link:function(scope, element, attrs) {
console.log('k');
}
};
});
But I see a couple of issues from your code:
- your showProfile function is expecting a "contact" argument that is not being passed from the button directive, so it will be undefined.
- you are injecting a "contact" dependency on your contactsProfileController controller. Do you have a service / factory declared with that name?
Instead of contact: '#contact', do contact: '=contact'
Since your custom directive is a "component" of sorts, it is a good idea to use an isolate scope and pass the necessary data (i.e. contact) via attributes.
E.g.:
<button ng-click="showProfile(...)"></button>
<profile contact="contact" ng-if="isProfile"></profile>
$scope.showProfile = function (contact) {
$scope.contact = contact;
$scope.isProfile = true;
};
.directive('profile', function () {
return {
restrict: 'ECMA',
scope: {contact: '='}
templateUrl: '/contacts/profile',
controller: contactsProfileController
};
});
Then, the property will be available on the scope (e.g. contactsProfileController's $scope):
var contactsProfileController = function ($scope) {
$scope.$watch('contact', function (newValue) {
// The `contact` has changed, do something...
console.log($scope.contact);
});
...
};
Both of your responses were incredibly helpful and I was able to get things working in a matter of minutes after reading your posts. Thank you so much!!!
Adding contact="contact" into the directive placeholder was key as was adding the scope object to the actual directive code.
So I ended up with:
<profile ng-if="isProfile===true" contact="contact"></profile>
and
.directive('profile', function () {
return {
templateUrl: '/contacts/profile',
restrict: 'ECMA',
controller: contactsProfileController,
scope: {contact: '='},
link:function(scope, element, attrs) {
}
};
});
Whatever I try to do I can't seem to pass data to a modal controller.
I tried using resolve:
var opts = {
backdrop: true,
keyboard: true,
backdropClick: true,
templateUrl: 'views/details/basic/view.html',
controller: 'BoxDetailsCtrl'
resolve: {
item: function () {
return angular.copy(item)
},
price: function (){ return 100; }
}
this.d = this.d || $dialog.dialog($scope.opts);
this.d.open();
I tried passing the scope:
var opts = {
scope: $scope
...
In my dispair I event tried hacking the dialog service:
if (self.options.controller) {
var ctrl = $controller(self.options.controller, locals);
ctrl.modal = self.options.modal; // nasty hack
self.modalEl.children().data('ngControllerController', ctrl);
}
Nothing worked. I can't seem to be able to pass data to BoxDetailsCtrl. Any ideas?
Cheers,
This method is working for me ... it's in coffeescript but you can convert it to js.
AngularUI separate files for modal controllers
resolve:
primer3: ->
$scope.primer3
Then
angular.module('assaypipelineApp').controller "ConfigureModalCtrl", ($scope, $modalInstance, primer3) ->
$scope.primer3 = primer3['data']
I found I was getting an object with the data array embedded and with the http return code. I don't really understand why, but this works.
I hope that helps.