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>,
Related
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
What is the better approach to get access to scope created by ng-if in my directive (without $parent.params.text):
<span ng-if="params" uib-tooltip="{{params.text}}"></span>
.directive('myDirective', functions(){
return {
templateUrl: 'template.html',
replace: true,
$scope: {
data: '='
},
controller: function(){
if (data) { //some logic
$scope.params.text = 'text'
}
}
}
})
I've noticed that I don't have to use $parent if my variable is nested inside an object.
For example:
controller
$scope.params = { ... }
view ng-if="params"
Won't work, but:
controller
$scope.something_here = {};
$scope.something_here.params = { ... }
view ng-if="something_here.params"
would work. I believe Angular preserves the scope if the key you're trying to access is part of an object. Give it a try!
You could use the controllerAs syntax.
add
controllerAs: "vm",
bindToController: true
as properties in your directive definition and replace $scope with vm.
Then refer to vm.params.text inside the ng-if
I have a directive which I want to tightly couple with a controller as a component. I assumed I was following best practice by explicitly passing ion my functions even though I was declaring the controller to use. Here is an example:
app.js
var app = angular.module('plunker', [])
app
.controller('myCtrl', function($scope) {
$scope.output = '';
$scope.foo = function () {
$scope.output = 'foo';
}
$scope.bar = function () {
$scope.output = 'bar';
}
})
.directive('myDirective', function() {
return {
scope: {
output: '=',
foo: '&',
},
templateUrl: 'template.html',
replace: true,
controller: 'myCtrl',
};
})
template.html
<div>
<button ng-click="foo()">Click Foo</button>
<p>You clicked: <span style="color:red">{{output}}</span></p>
</div>
index.html
<body>
<my-directive
output="output"
foo="bar()"> <!-- pass in the *bar* function instead of the *foo* function -->
</my-directive>
</body>
Plunkr: http://plnkr.co/edit/Y4lhxuXbK9YbjAklR7v1?p=preview
Here, even though I'm passing in the bar() function, the output is 'foo' when the button is clicked. If I uncouple the controller by commenting out controller: 'myCtrl' in the directive, the output becomes 'bar'.
I thought I could declare the controller but still be free to pass in which functions I desire to the directive. It also seems that explicitly passing these functions in is a little redundant if the directive just looks up to the controller to find it (I can pass nothing into the directive and it still works).
This is especially problematic when testing as I would like to pass in my own stub functions to the directive, which at the moment I cannot do.
Is there some way to achieve what I want or am I doing something fundamentally wrong?
EDIT I meant to not have the controller declared in the HTML.
Remove the controller property on the directive:
.directive('myDirective', function() {
return {
scope: {
output: '=',
foo: '&',
},
templateUrl: 'template.html',
replace: true,
// controller: 'myCtrl',
};
})
You're wiring up the same controller to the directive as the parent, which is overwriting all the properties you're trying to pass in via isolate scope. The controller is wired up twice, once on the parent scope and then again on the directive. Removing this will allow you to pass in the function bar() and it will not be overwritten.
Here's the Plunker Demonstration
When running inside a directive, the $scope is initialized with output and foo variables before the controller constructor is called. Your controller is essentially overwriting these properties.
A simple check in your controller
if(!$scope.foo)
{
$scope.foo = function () {
$scope.output = 'foo';
}
}
Would work.
PS. I'm assuming your example is a simplification of your problem. If it's not, then the other answer's advice to simply remove the controller from the directive is the best approach.
i am using ui-router and there i am resolving some data i want to inject the resolve to my custom directive ,below is code how i am doing
module portal {
$stateProvider.state('portal', {
url: '/demo',
template: tpl.html,
abstract: true,
resolve: {
demoResolve:function(){
return 'foo';//here i am returing a promise
}
});
}
module portal.directives{
export class demoDirevtive{
static $inject =['demoResolve'];
constructor(demoResolve){
console.log(demoResolve)
var directive: ng.IDirective = {};
directive.link = (scope, element, attrs, ctrl) => {
};
directive.restrict = "EAC";
return directive;
}
}
}
but i am getting error of unknown provider
From reading their code it doesn't seem like it's possible, they have a local variable that they inject into the controller you define on the view, it's not accessible via $inject service as well.
Easiest solution would be to put it on the controller's scope, and then use it in the directive.
You can also create a real service, that will hold all the resolved objects in your application, i.e.:
resolve: {
demoResolve: ['myResolvingService', function(resolver) {
resolver.myValue = 'Foo';
return 'Foo';
}]
I know it's not what you were looking for, but it just doesn't look like it's supported.
Here is example how to pass resolve value to directive via controller:
.state('something.edit', {
url: '/:id',
template: '<something-edit title="title"></something-edit>',
controller: function($scope, $title){
$scope.title = $title;
},
resolve: {
$title: ()=>{
return 'Something Edit';
}
}
});
They added support for this in 2016.
This is the github thread:
https://github.com/angular-ui/ui-router/issues/2664#issuecomment-204593098
The important part is:
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>,
I have a directive and a controller. The directive defines a function in its isolate scope. It also references a function in the controller. That function takes a callback. However, when I call it from the directive and pass in a callback, the callback is passed through as undefined. The code below will make this more clear:
Directive
directive('unflagBtn', ["$window", "api",
function($window, api) {
return {
restrict: "E",
template: "<a ng-click='unflag(config.currentItemId)' class='btn btn-default'>Unflag</a>",
require: "^DataCtrl",
scope: {
config: "=",
next: "&"
},
controller: ["$scope",
function($scope) {
$scope.unflag = function(id) {
$scope.next(function() { //this callback does not get passed
api.unflag(id, function(result) {
//do something
return
});
});
};
}
]
};
}
]);
Controller
controller('DataCtrl', ['$rootScope', '$scope', 'api', 'dataManager', 'globals',
function($rootScope, $scope, api, dataManager, globals) {
...
$scope.next = function(cb) { //This function gets called, but the callback is undefined.
// do something here
return cb ? cb() : null;
};
}
]);
HTML
<unflag-btn config="config" next="next(cb)"></unflag-btn>
I've read here How to pass argument to method defined in controller but called from directive in Angularjs? that when passing parameters from directives to controller functions, the parameters need to be passed in as objects. So I tried something like this:
$scope.next({cb: function() { //this callback does not get passed still
api.unflag(id, function(result) {
//do something
return
});
}});
But that did not work. I am not sure if this matters, but I should note that the directive is placed inside a form, which in its place is inside a controller. Just to illustrate the structure:
<controller>
<form>
<directive>
<form>
<controller>
Hope this is clear and thanks in advance!
Try this
controller: ["$scope",
function($scope) {
$scope.unflag = function(id) {
$scope.next({
cb: function() { //this callback does not get passed
api.unflag(id, function(result) {
//do something
return;
});
}
});
};
}
]
So I unintentionally figured out whats wrong after not being able to pass an object back to the controller as well. What happened, (and what I probably should have mentioned in the question had I known that its relevant) is that the parent scope of this directive unflagbtn is actually the scope of another directive that I have, call it secondDirective. In its turn the secondDirective is getting its scope from "DataCtrl". Simplified code looks like this:
directive("secondDirective", [function(){
require: "^DataCtrl" // controller
scope: {
next: "&" // function that I was trying to call
}
...
// other code
...
}]);
directive("unflagbtn", [function(){
require: "^DataCtrl" // controller
scope: {
next: "&"
},
controller: ["$scope", function($scope){
$scope.unflag = function(){
$scope.next({cb: {cb: callBackFunctionIWantedToPass}); // this is what worked
}
}
}]);
So passing a callback in that manner solved my problem as it made its way back to the controller. This is ugly most likely due to my poor understanding of angular, so I apologize as this is most likely not the correct way to do this, but it solved my problem so I though I'd share.
Cheers,