I'm trying to create an AngularJS directive that calls a service created using $resource with the value of the attribute in the template and makes the result available in the scope of the element.
I have a very simple service:
services.factory('Concept', ['$resource', function($resource) {
return $resource('http://example/api/:id');
}]);
And a directive:
directives.directive('anConcept', ['Concept', function(Concept) {
return {
scope: {
anConcept: '#'
},
restrict: 'A',
controller: function($scope, $element) {
$scope.results = Concept.get({id: concept});
}
}
}]);
I then try to invoke this in my view:
<div an-concept="1">
{{results}}
</div>
Looking at the network pane in my browser's debugger I can see the requests getting made, but the result isn't being made available in the scope of the element.
I'm sure I'm making a very basic mistake but I can't figure out what it is.
Note that when you're working with a $resource, you're receiving back a promise. Additionally, you may be running into some trouble with the results property of your $scope if the directive's scope is isolated.
angular.module('myApp', ['ngResource'])
.factory('Concept', ['$resource',
function($resource) {
return $resource('https://api.stackexchange.com/2.2/info');
}
])
.directive('anConcept', ['Concept',
function(Concept) {
return {
restrict: 'A',
link: function($scope, $element, $attrs, $controllers) {
Concept.get({
site: $attrs.anConcept
}, function(results) {
$scope.results = results.items[0].total_users;
});
}
};
}
]);
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-resource.js"></script>
<div ng-app='myApp'>
<div an-concept="stackoverflow">
Total stackoverflow users: {{results}}
</div>
</div>
It's because the result of that get is a promise. You need to do something like:
Concept.get({id: concept}).success(function(data) {
$scope.results = data;
};
EDIT
Sorry I think I made a mistake, $resource works a bit different then $http :
Concept.get({id: concept}, function(data) {
$scope.results = data;
};
If you want to see that value out of directive,I will say that you will not see it as a simple.because your directive is ISOLATED-SCOPE directive.It means your values in that scope will be isolated from parent scope and you will not see that value out of directive(There is a way to get that isolated scope).But if you want to see it in directive,I will say your code is correct code.If response has been accepted,you result will be defined.
Related
I have (sort of) the following html:
<div ng-controller="MyController">
<my-sub-directive></my-sub-directive>
</div>
how the controller looks is not important:
app.controller("MyController", function($scope) {
$scope.foo = "bar";
})
and my directive looks like this:
function mySubDirective() {
return {
restrict: "E",
templateUrl:"aTemplate.html",
require: "^MyController",
link: function($scope, element) {
}
};
}
app.directive("mySubDirective", mySubDirective);
In the documentation they always specify another directive in the require-property, but it says that it means you require the controller. So I wanted to try this solution. However I get the error
"Controller 'MyController', required by directive 'mySubDirective', can't be found".
Is it not possible to require a controller from the directive if it is set by ng-controller?
You can only do:
require: "^ngController"
So, you can't be more specific than that, i.e. you can't ask for "MainCtrl" or "MyController" by name, but it will get you the controller instance:
.controller("SomeController", function(){
this.doSomething = function(){
//
};
})
.directive("foo", function(){
return {
require: "?^ngController",
link: function(scope, element, attrs, ctrl){
if (ctrl && ctrl.doSomething){
ctrl.doSomething();
}
}
}
});
<div ng-controller="SomeController">
<foo></foo>
</div>
I don't think, though, that this is a good approach, since it makes the directive very dependent on where it is used. You could follow\ the recommendation in the comments to pass the controller instance directly - it makes it somewhat more explicit:
<div ng-controller="SomeController as ctrl">
<foo ctrl="ctrl"></foo>
</div>
but it still is a too generic of an object and could easily be misused by users of your directive.
Instead, expose a well-defined API (via attributes) and pass references to functions/properties defined in the controller:
<div ng-controller="SomeController as ctrl">
<foo do="ctrl.doSomething()"></foo>
</div>
You can use element.controller() in the directive link function to test the closest controller specified by ngController. A limitation of this method is that it doesn't tell you which controller it is. There are probably several ways you can do it, but I'm opting to name the controller constructor, and expose it in the scope, so you can use instanceof
// Deliberately not adding to global scope
(function() {
var app = angular.module('my-app', []);
// Exposed in so can do "instanceof" in directive
function MyController($scope) {}
app.controller('MyController', MyController);
app.directive("foo", function(){
return {
link: function($scope, $element){
var controller = $element.controller();
// True or false depending on whether the closest
// ngController is a MyController
console.log(controller instanceof MyController);
}
};
})
})();
You can see this at http://plnkr.co/edit/AVmr7Eb7dQD70Mpmhpjm?p=preview
However, this won't work if you have nested ngControllers, and you want to test for one that isn't necessarily the closest. For that, you can defined a recursive function to walk up the DOM tree:
app.directive("foo", function(){
function getAncestorController(element, controllerConstructor) {
var controller = element.controller();
if (controller instanceof controllerConstructor) {
return controller;
} else if (element.parent().length) {
return getAncestorController(element.parent(), controllerConstructor);
} else {
return void(0); // undefined
}
}
return {
link: function(scope, element){
var controller = getAncestorController(element, MyController);
// The ancestor controller instance, or undefined
console.log(controller);
}
};
})
You can see this at http://plnkr.co/edit/xM5or4skle62Y9UPKfwG?p=preview
For reference the docs state that the controller function can be used to find controllers specified with ngController:
By default retrieves controller associated with the ngController directive
I've got an Angular view thusly:
<div ng-include="'components/navbar/navbar.html'" class="ui centered grid" id="navbar" onload="setDropdown()"></div>
<div class="sixteen wide centered column full-height ui grid" style="margin-top:160px">
<!-- other stuff -->
<import-elements></import-elements>
</div>
This is controlled by UI-Router, which is assigning the controller, just FYI.
The controller for this view looks like this:
angular.module('pcfApp')
.controller('ImportElementsCtrl', function($scope, $http, $location, $stateParams, $timeout, Framework, OfficialFramework) {
$scope.loadOfficialFrameworks();
// other stuff here
});
The <import-elements> directive, looks like this:
angular.module('pcfApp').directive('importElements', function($state, $stateParams, $timeout, $window, Framework, OfficialFramework) {
var link = function(scope, el, attrs) {
scope.loadOfficialFrameworks = function() {
OfficialFramework.query(function(data) {
scope.officialFrameworks = data;
$(".ui.dropdown").dropdown({
onChange: function(value, text, $item) {
loadSections($item.attr("data-id"));
}
});
window.setTimeout(function() {
$(".ui.dropdown").dropdown('set selected', data[0]._id);
}, 0);
});
}
return {
link: link,
replace: true,
templateUrl: "app/importElements/components/import_elements_component.html"
}
});
I was under the impression that I'd be able to call the directive's loadOfficialFrameworks() method from my controller in this way (since I'm not specifying isolate scope), but I'm getting a method undefined error on the controller. What am I missing here?
The problem is that your controller function runs before your link function runs, so loadOfficialFrameworks is not available yet when you try to call it.
Try this:
angular.module('pcfApp')
.controller('ImportElementsCtrl', function($scope, $http, $location, $stateParams, $timeout, Framework, OfficialFramework) {
//this will fail because loadOfficialFrameworks doesn't exist yet.
//$scope.loadOfficialFrameworks();
//wait until the directive's link function adds loadOfficialFrameworks to $scope
var disconnectWatch = $scope.$watch('loadOfficialFrameworks', function (loadOfficialFrameworks) {
if (loadOfficialFrameworks !== undefined) {
disconnectWatch();
//execute the function now that we know it has finally been added to scope
$scope.loadOfficialFrameworks();
}
});
});
Here's a fiddle with this example in action: http://jsfiddle.net/81bcofgy/
The directive scope and controller scope are two differents object
you should use in CTRL
$scope.$broadcast('loadOfficialFrameworks_event');
//And in the directive
scope.$on('loadOfficialFrameworks_event', function(){
scope.loadOfficialFrameworks();
})
I get an issue with passing data to angular directives inside ng-repeat, it always got undefined. Here are my code
The Controller:
angular.module('module').controller('ModuleController', ['$scope', 'MyService', function($scope, MyService) {
$scope.getData = function() {
$scope.data = MyService.myGetRequest(); // returning array of objects
};
});
View:
<div ng-controller="ModuleController" ng-init="getData()" ng-switch="data.length > 0">
<div ng-repeat="d in data" ng-switch-when="true">
<my-directive data="d.object"></my-directive>
</div>
</div>
Directive:
angular.module('module').directive('myDirective', [function() {
return {
restrict: 'E',
template: '<div></div>' // let's ignore the template for now,
scope: { data: '=' },
link: function(scope, el, attrs) {
console.log(scope.data); // always undefined
}
};
}]);
Service:
angular.module('module').factory('MyService', ['$resource', function($resource) {
return $resource('/data/:id',
{ id: '#_id' },
{
myGetRequest: { method: 'GET', isArray: true }
});
}]);
I thought it was because the $scope.data still empty when the template loaded. If yes, anyone know what is the solution? Thanks in advance. :)
EDIT: btw, if I put <my-directive data="data"></my-directive> instead of <my-directive data="d.object"></my-directive> the scope.data is not undefined anymore, it will show my array of object from resource.
EDIT2: this <my-directive data="d"></my-directive> will also resulting scope.data in my directive got undefined.
EDIT3: Add service code snippet
I think Shomz found the problem. You should use a promise with async call. Like this:
angular.module('module').controller('ModuleController', ['$scope', 'MyService',
function($scope, MyService) {
$scope.data = [];
MyService.myGetRequest().$promise.then(function(data) {
$scope.data = data;
});
}
});
How can I use a different sets of initialization variables for each instance of controller in my app?
in view:
<div ng-controller="showProjectList">
{{project_list}}<!--user 1-->
</div>
<div ng-controller="showProjectList">
{{project_list}}<!--user 2-->
</div>
in controller
myapp.controller('showProjectList',function($http)
{ $scope.project_list= <Here I have a http request with argument user_id to fetch project_list>
}
Now how do I initialize each controller with a different user_id? One solution I have readon stackexchange & on google-groups is the use of ng-init.(link google-grp: https://groups.google.com/forum/#!topic/angular/J6DE8evSOBg) .However the use of ng-init is cautioned against in the same threads. So how do you initialize a controller with data then ?
You could use a combination of a controller, a directive and a service for that matter.
The controller is holding the user id's.
The directive is rendering the project list.
The service is responsible for fetching the data from the server. You could implement a cache and/or use $resource in here.
Here is the template code:
<div ng-controller="Projects">
<!-- here you can put an input element with
ng-model="users" to modify the user list on the fly -->
<div ng-repeat="user in users">
<project-list user="user" />
</div>
</div>
The controller:
myapp.controller('Projects', ['$scope', function($scope) {
$scope.users = [1, 2, 3];
}]);
The directive:
myapp.directive('projectList', ['UserService', function(UserService) {
return {
restrict: 'E',
scope: {
user: "="
},
templateUrl: 'project-list.html',
link: function($scope, $element, $attrs) {
UserService.getUserProject($scope.user).then(function(response) {
$scope.userProjects = response;
});
}
};
}]);
The service:
myapp.factory('UserService', ['$http', function($http) {
var getUserProject = function(user) {
var promise = $http.get('users/' + user + '/project');
return promise;
}
return {
getUserProject: getUserProject
}
}]);
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,