Directive scope got undefined - angularjs

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;
});
}
});

Related

Angularjs passing variable to a directive's controller

I am creating Angularjs Directive. However i need to pass a paramter to the directive and use it in its controller to populate its items using $http service.
i am passing a "listId" parameter to the directive, the controller inside the directive expects this parameter to retrieve items of that list from the server.
The code in the controller embedded in the directive is commented.
<script type="text/javascript">
var app = angular.module('app', []);
app.controller('metadataCtrl', function ($scope, $http) {
});
app.directive('mydirective', function ($http) {
return {
restrict: 'AE',
template: '<div ng-repeat="model in items">{{ model.name}} </div>',
replace: true,
scope: {
listId: '='
},
controller: function ($scope) {
//console.log(scope.listId);
// console.log(listId);
//$http({ method: 'GET', url: 'http://localhost:62624/home/listvalues?listid=' }).then(function (response) {
// $scope.items = response.data;
//}, function (result) { alert("Error: No data returned"); });
},
link: function (scope, element, attrs) {
console.log(scope.listId);
}
};
});
</script>
The HTML code
<body ng-app="app">
<form name="myForm" ng-controller="metadataCtrl" class="my-form">
<mydirective list-id="99"></mydirective>
</form>
</body>
The listId can be accessed in the link() function in the directive (i am using console.log() to test that). However, this doesn't work in the controller function.
In the controller, use injected $scope.
controller: function ($scope) {
//USE $scope
console.log($scope.listId);
//
//console.log(scope.listId);

Dynamically creating directive template

I am trying to create template dynamically. When I inject the hard coded value through directives attribute it works fine. But when I assign it using angular variable it does not seem to work. Below is the js code
(function(angular) {
'use strict';
angular.module('docsTemplateUrlDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
$scope.nameTempl = 'customer-name.html';
$scope.addressTempl = 'customer-address.html';
}])
.directive('myCustomer', function() {
return {
templateUrl: function(elem, attr){
return attr.type;
}
};
});
})(window.angular);
this is html part
<body ng-app="docsTemplateUrlDirective">
<div ng-controller="Controller">
<div my-customer type="{{nameTempl}}"></div>
<div my-customer type="{{addressTempl}}"></div>
</div>
</body>
Instead of using variables if i directly use values it seems to be working fine.
I dont understand why is this happening?
Plunker code
Your problem is that angular first process the template and then the DOM. So when he gets to the attr.type, it's still {{nameTemp1}}, angular has yet manipulated the DOM. My suggestion is, try to pass the address in another way. This plunker shows how you can try creating a service that would hold an object, and then bind the url to the object. Then, inject the service to the directive and use the url. Just make sure to put an "ng-if" on the directive the checks if the scope had created that object and bound it to the service's object
Here is the service
service('service', [function() {
return {
template: {}
};
}])
And this is the controller:
controller('Controller', ['$scope', 'service', function($scope, service) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
$scope.addressTempl = 'customer-address.html';
service.template.url = $scope.addressTempl;
}])
and the directive looks like this:
directive('myCustomer', function(service) {
return {
templateUrl: function(elem, attr){
return service.template.url;
}
};
})
You can use different templates as follows:
Live example on plunker.
(function(angular) {
'use strict';
angular.module('docsTemplateUrlDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
$scope.nameTempl = 'customer-name.html';
$scope.addressTempl = 'customer-address.html';
}])
.directive('myCustomer', function() {
return {
scope: {
type: "#",
customer:"=myCustomer",
},
template: '<div ng-include="type"></div>'
};
});
})(window.angular);
And html part
<body ng-app="docsTemplateUrlDirective">
<div ng-controller="Controller">
<div my-customer="customer" type="{{nameTempl}}"></div>
<div my-customer="customer" type="{{addressTempl}}"></div>
</div>
</body>
You cannot access scope in templateURL function however there is a way to load template at run time. Plunker - http://plnkr.co/edit/n20Sxq?p=preview
app.directive("cellItem", ["$compile", '$http', '$templateCache', '$parse', function ($compile, $http, $templateCache, $parse) {
return {
restrict: 'A',
link: function(scope , element, attrs) {
scope.$watch(attrs.template, function (value) {
if (value) {
loadTemplate(value);
}
});
function loadTemplate(template) {
$http.get(template, { cache: $templateCache })
.success(function(templateContent) {
element.replaceWith($compile(templateContent)(scope));
});
}
}
}
}]);
Hope this solve your issue.

AngularJS Directive $scope is undefined

I have the following directive. When I trigger the open function and get to the debugger I get an error message in the console that says Uncaught ReferenceError: $scope is not defined(…).
How is it possible for $scope.open to be called when $scope is undefined?
app.directive('photo', ['$http', 'modal', function($http, modal) {
return {
replace: true,
templateUrl: '/assets/photo.html',
transclude: false,
scope: {
result: '=',
index: '#'
},
controller: ['$scope', '$http', 'modal', function($scope, $http, modal) {
$scope.prev = $scope.index - 1;
$scope.open = function() {
debugger;
};
}]
}
}]);
Here is my DOM:
<div ng-repeat="r in results" photo result="r" index="$index"></div>
If I insert console.log($scope) just before my open function, and then again right before the debugger in that function, I get the following results. Left is before open is called, right is after open is called.
You inject the $http and modal in the directive definition (as you did), no need to in the controller function, just do:
controller: function($scope) {
$scope.prev = $scope.index - 1;
$scope.open = function() {
debugger;
};
}
Try adding a statement that uses $scope in $scope.open. Chrome has probably optimized $scope away when you're in $scope.open because you're not using it.
$scope.open = function() {
console.log($scope);
debugger; //now you should see $scope.
};
its worked for me
var app = angular.module("moduleTest",[]);
app.directive("testDirective",function(){
return {
restrict: "A",
scope: true,
link: function(scope, element){
//code
//and $scope is scope
}
}
});
This should work:
app.directive('photo', ['$http', 'modal', function($http, modal) {
return {
replace: true,
templateUrl: '/assets/photo.html',
transclude: false,
scope: {
result: '=',
index: '#'
},
controller: function($scope, $http, modal) {
$scope.prev = $scope.index - 1;
$scope.open = function() {
debugger;
};
}
}
}]);
You need to define the $Scope at the top i.e.:
app.directive('photo', ['$http', '$Scope','modal', function($http, $Scope, modal)
It will work fine now.

directive unable to retrieve data from a service

I have a little SPA using angular. The concept is simple, after login, $routeProvider redirects to a home page where I have a homeController specified.
this is from my home view that is rendered by ng-view while navigating to "/home" :
<my-directive datas=getData()></my-directive>
<ul>
<li ng-repeat="data in datas"> {{data.title}} {{data.content}} </li>
</ul>
my directive is written as:
angular.module('app').directive('myDirective', ['myService', function (myService) {
return {
restrict: "E",
scope: {
data: '='
},
templateUrl: "partials/my-directive.html",
controller: function ($scope) {
$scope.getDatas = function()
{
myService.retData();
}
}
};
}]);
the home controller is:
angular.module('app').controller('homeController', homeController);
homeController.$inject = ['myService', '$scope'];
function homeController(myService, $scope) {
var vm = this;
vm.data = [];
initController();
function initController() {
vm.data = myService.retData();
}
}
and finally my service is
angular.module('app').service('myService', myService);
function myService () {
var data = [
{ id: 1, title: 'Albert Einstein', content: 'Random Content' }
];
return {
retData: function () {
return data;
},
addData: function (title, content) {
var currentIndex = data.length + 1;
data.push({
id: currentIndex,
title: title,
content: content
});
}
};
}
now that i mentioned everything, here comes the problem. the directive is not able to retrieve data from the service. Actually when i run the project in VS2013, myDirective.js is not even loaded. I included all services, directives, controllers etc in the main HTML page.
What is causing this problem?
Does it have something to do with the scope being isolated in the directive?
What is a better approach to sharing data between a controller, directive and service?
I may have made some silly mistakes while rewriting all the code. Please do point them out, however keep in mind my actual issue and what error may be causing that.
Better to use isolated scope to pass data controller to directive.
Html:
<my-directive datas="getData()" data="data"></my-directive>
Directive:
angular.module('app').directive('myDirective', [function () {
return {
restrict: "E",
scope: {
data: '='
},
templateUrl: "partials/my-directive.html",
link: function (scope) {
//Here you got the isolated scope data
var details = scope.data;
}
};
}]);
OR
app.directive('myDirective', function() {
return {
restrict: 'E',
templateUrl: 'partials/my-directive.html',
scope: {
date: '=',
},
controller : ['$scope', 'myService', function($scope, myService) {
myService.retData();
}],
link: function(scope, elem, attr) {
//
}
};
});

AngularJS pass $resource as a directive parameter

I have just came up with a directive that loads a dropdown box according to a list coming from an API call ($resource).
Controller:
App.controller(
'TestCtrl', [
'$scope', 'countriesFactory',
function($scope, countriesFactory){
/* Call API */
countriesFactory().then(function(data){
$scope.countryList = data;
});
}])
The API call returns:
{"country":[{"code":"ABW","label":"Aruba"},{"code":"AFG","label":"Afghanistan"},{"code":"AGO","label":"Angola"}]}
Template:
<input-select model-ref-list="countryList"></input-select>
Directive:
App
.directive("inputSelect"
, function() {
var Template =
'<select ng-options="item.label for item in modelRefList" required></select>';
return {
restrict: 'EA',
template: Template,
scope: {
modelRefList: '='
},
link: function(scope){
console.log(scope.modelRefList);
}
};
}
);
First of all: I simplified a lot the overall issue, so that it looks that the directive is completely overkill in that situation, but in the end, it is not :D.
Problem: My console.log is always undefined.
I made a bit of research and realized that I needed to play with promises to wait for my country list to appear to be actually given to the directive.
So I tried modifying my controller and not use the result of the API call promise, but directly the resource itself:
New Controller:
App.controller(
'TestCtrl', [
'$scope', 'countriesFactory',
function($scope, countriesFactory){
/* Call API */
$scope.countryList = resourceAPICall();
}])
But still undefined :/.
How can I pass direclty the resource (containing the promise I can then use to defer the load of the select) to the directive?
SOLUTION FOR ANGULARJS 1.2:
Directive:
App
.directive("inputSelect"
, function() {
var Template =
'<select ng-options="item.label for item in modelRefList" required></select>';
return {
restrict: 'EA',
template: Template,
scope: {
modelRefList: '='
},
link: function(scope){
scope.modelRefList.$promise.then(function(data){
console.log(data);
}
};
}
);
To pass a API call result to a directive, you need to pass its resource and play with its promise inside the directive itself.
Thanks everybody for the help.
Here we simulated async call factory by using wrapper with $q.
We changed modelReflist to modelRefList
added ng-model="item" to template
HTML
<div ng-controller="TestCtrl">
<input-select model-ref-list="countryList"></input-select>
</div>
JS
var App = angular.module('myModule', ['ngResource']);
App.controller(
'TestCtrl', [
'$scope', 'countriesFactory',
function ($scope, countriesFactory) {
/* Call API */
countriesFactory.resourceAPICall().then(function (data) {
$scope.countryList = data.country;
console.log($scope.countryList);
});
}])
App.$inject = ['$scope', 'countriesFactory'];
App.directive("inputSelect", function () {
var Template = '<select ng-model="item" ng-options="item.label as item.label for item in modelRefList" required></select>';
return {
restrict: 'EA',
template: Template,
scope: {
modelRefList: '='
},
link: function (scope) {
console.log(scope.countryList);
}
};
});
App.factory('countriesFactory', ['$resource', '$q', function ($resource, $q) {
var data = {
"country": [{
"code": "ABW",
"label": "Aruba"
}, {
"code": "AFG",
"label": "Afghanistan"
}, {
"code": "AGO",
"label": "Angola"
}]
};
var factory = {
resourceAPICall: function () {
var deferred = $q.defer();
deferred.resolve(data);
return deferred.promise;
}
}
return factory;
}]);
Demo Fiddle
modelReflist needs to be fully camel-cased in your directive scope. modelRefList.

Resources