Variable is undefined in the scope, but it's actually defined - angularjs

I'm really confused, I don't know what heck is going on!
.directive("blGraph", ['$http', function($http) {
return {
restrict: 'EA',
scope: {
botid: "=blGraph"
},
link: function(scope, element, attr) {
console.log(scope, scope.botid);
}
};
}]);
Result,
as you see in the picture, the botid exist in the scope, but when i try to get it by scope.botid it tell that it is not defined!

put a $watch on the scope variable. Control will go twice in the watch handler function. You will see what is happening.
Also what is your HTML markup, may be you are binding it to undefined.

Due to the sequence of running the controller and link functions, you need to use a $timeout in order to allow the variables to load onto the scope. Here's how I solved it:
controller: function($scope, $timeout) {
$timeout(function(){
console.log($scope.myVariable)
},0);
}

Most likely, scope.botid is not defined when you are printing it, and is defined after the console.log statement.
When you use console.log on an object, it shows the state of the object at the end of the JS execution.
Instead of just using scope in the console.log statement, try the following:
console.log(JSON.stringify(scope), scope.botid);

Related

get scope in directive

everybody. I am new to AngularJS and find it very interesting, but I am a bit unclear about the following situation.
app.controller("myCtrl", ['$scope', '$http', '$filter', function ($scope, http, filter)
{
$http({
method: CTHocUri,
url: 'get',
async: true,
}).then(function (response) {
$scope.CTH = response.data; //response.data=two Object
})
}])
app.directive("myCustom1",['$http', function ($compile,$http) {
return {
link: function (scope, element, attr) {
console.log(scope.CTH); // I can't get... scope.CTH=undefined
}
}])
I can't get value scope.CTH. ??
There is a VERY simple way to SEE what the issue is:
In your html, merely surround your directive with an ng-if conditional based on CTH:
<span ng-if="CTH">
<my-custom-1></my-custom-1>
</span>
That's it.
What this does is that your directive will only be born/instantiated when CTH is set to non-null/non-undefined, i.e. when $http returns asynchronously. With this, your code will work. As such, there is no need for watching or broadcasting for this type of simple serialization of asynchronous events when you can simply leverage Angular's built-in '$watch's.
NOTE 1: I do not know what your architecture is and am not suggesting what you need to do. I am merely showing you why your code won't work and how you have been caught in a simple asynchronicity trap.
NOTE 2: I assume your directive is 'as -is'. In other words you have access to the parent's scope (i.e. the controller's scope). If your directive's scope were isolated (i.e. you had a scope:{..(attrs)..} defined in the directive) you will not have 'simple' access to the parent scope. Your code will be different--eg you can pass bits and pieces of your scope to the directive attrs. However, the ng-if will still work since it is on the controller's scope.
I hope this helps.
The directive and the controller are two completely different entities. If it helps you can think of them as different classes. They will not share the same scope.
You could create an isolated scope on the directive and pass the CTH variable into it. Conceptually something like this:
app.directive("myCustom1",['$http', function ($compile,$http) {
return {
scope { cth : "=" },
link: function (scope, element, attr) {
console.log(scope.cth);
}
Then in your HTML, do something like this:
<div ng-controller="myCtrl">
<my-Custom1 cth="CTH">
</div>
when the directive initializes, the scope.CTH is still not initialized since its initialization accurses inside an $http call.
one way to overcome this is to broadcast and event from the controller and catch it from inside the directive. see this plnkr and angularjs scope's docs
app.controller('MainCtrl', function($scope, $timeout) {
$scope.name = 'World';
$timeout(function() {
$scope.test = "test";
$scope.$broadcast('MyEvent')
}, 500);
});
app.directive('test', function() {
return {
link: function(scope, elm, attr) {
scope.$on('MyEvent', function() {
console.log(scope.test);
})
}
}
})

Using scope.$watch to observe when a function executes

Suppose I have a custom directive, named my-directive, that has the default scope settings (scope: false, which means it shares the scope of the (parent) controller enclosing the directive), and I have a function my_function, attached to the controller's scope. I wonder if in the custom directive's link function if I can use scope.$watch to observe when my_function executes?
Here's a code for illustration:
<div ng-controller="MyCtrl">
<my-directive></my-directive>
</div>
the controller:
app.controller('MyCtrl', function($scope) {
$scope.my_function = function() {
...
}
});
the custom directive:
app.directive('myDirective', function() {
return {
restrict: 'E',
[scope: false,]
...
link: function(scope, element, attrs) {
// can I scope.$watch my_function here to check when it executes?
}
}
});
Update:
Here's my reason for asking this:
When my_function (which essentially is a retrieve function through $http.get) executes, it should update an object, say my_data. If I try to scope.$watch my_data, there may be a situation where my_function executes and there is no change in my_data (such as when I edit an entry in my_data and decides to cancel the update anyway), thus a portion of the code that I want to execute in link function is not executed. So I want to make sure my code in link function executes whenever my_function executes, whether or not there is a change my_data.
Here are two ways to solve your problem that I could think of.
1. Use a counter
You can always increment a counter when invoking my_function:
// This will reside in your controller
$scope.counter = 0;
$scope.my_function = function () {
// do your logic
$scope.counter++;
};
// This will reside in your directive
$scope.$watch( "counter", function () {
// do something
});
2. Simply watch my_data
Assuming my_function will always override the my_data variable, you can use the default Angular behavior for $watch. You don't have to care if your backend has news for you or not, the reference will never be the same.
// in your controller
$scope.my_function = function () {
$http.get(...).then(function ( response ) {
$scope.my_data = response.data;
});
};
// in your directive
$scope.$watch( "my_data", function () {
// do something
});
Obviously, this is the cleaner way to do this, but you may get in trouble if you don't unit test because of one of the following:
You add any sort of logic around my_data that may conflict with your watcher;
You want to cache your HTTP calls.
I hope I was able to help you! Good luck.
Depending on what you're trying to do, you may not need $watch at all.
If the function isn't ever going to change, you can just make a dummy wrapper.
link: function(scope, element, attrs) {
var oldFunc = scope.my_function;
scope.my_function = function(sameArgs){
//method_was_called();
if(oldFunc){oldFunc(sameArgs);}
}
}
Then you can do whatever logic you want inside your wrapper function, either before or after you run the 'super' method.
Check also
Javascript: Extend a Function

AngularJS passing json to directive

I'm still new in Angular but i'm doing some progress.. i think :)
I have problem passing json file from controller to directive using isolated scope.
This is my controller which talk to factory "dataArchive":
.controller('graphCtrl', ['$scope', 'dataArchive', function($scope, dataArchive){
dataArchive.get().then(function(data){
$scope.getData = data;
});
}]);
Then i have directive which is using isolated scope.
.directive('dataGraph', function($compile){
return {
restrict: 'AE',
replace: true,
scope: {
getArchiveData: '='
},
template: '<div id="chartdiv"></div>',
link: function (scope, element, attrs){
var dati = scope.getArchiveData;
console.log(dati);
};
};
});
And this is my HTML:
<div ng-controller="graphCtrl">
<data-graph get-archive-data="getData"></data-graph>
</div>
In console i always get 'undefined'.
Where i am wrong and is this a good way?
Thanks you all.
Since this code is async:
dataArchive.get().then(function(data){
$scope.getData = data;
});
The link function will run before the data is set on getData, and therefore the isolated scope variable will not be set at this time. So, I believe you are seeing a timing issue.
To make sure that your directive binding is correct. Try to set $scope.getData to a static value (e.g. $scope.getData = [{ data: 'value'}]). This should work.
Also, Angular checks for changes (to rebind) based on object reference. So, you might need to define $scope.getData in the controller (outside of the async call). Then you might want to push all the data in (instead of replace the entire object with the assignment).
Hope this helps.

Simple Angular Directive

I'm attempting to write my first Angular directive to add pagination functionality to my app.
I'm keeping it simple for now and want to have the directive just share the scope of the controller.
In my controller, I have a scoped var called:
$scope.pages
Inside my directive's linking function, I can do:
console.dir($scope)
to see the contents of the controller's scope object, but if I try to access the pages property, it comes back as undefined.
What am I missing?
Thanks in advance.
myDirectives.directive("mdPagination", function(){
return {
template: '',
restrict: 'A',
priority: 1000,
scope: false,
link: function($scope, el, attrs){
console.dir($scope.pages); //returns undefined
el.text('hello world');
},
}
});
Edit: I'm aware there's pagination modules out there, but my needs are modest and I thought building my own would be an easy way to start learning how to build directives.
The problem you have is directives linking functions are run before controllers are, so you typically can't access scope values during this linking phase. There are a couple other ways to do this. The most immediate way to solve your problem is with $scope.$watch. So something like this should work:
$scope.watch('pages', function(pages) {
console.dir(pages);
});
This works just fine for lost of circumstances. I would also suggest decoupling your controller from the scope it is declared in a bit by using an attribute to pass in an expression (property name basically) for your watch. A simple example might look like this:
<div my-directive="myProperty">
link: function($scope, el, attrs) {
$scope.watch(attrs.myDirective, function(value) {
//do something you need to do with the value here
});
}
However as you add complexity to your directive you may want to give your directive it's own controller to manage state without directly calling watches. This controller can either use a child scope belonging to the directive itself with scope: true or just use the parent scope with scope: false. Your choice will likely depend upon need. Then you could have:
myDirectives.directive("mdPagination", function(){
return {
template: '',
restrict: 'A',
priority: 1000,
controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
console.dir($scope.pages);
}],
link: function($scope, el, attrs){
$element.text('hello world');
}
}
});
By default, the directive shares its scope with the controller. To do that, you must not having a "scope" attribute in your object, like that:
myDirectives.directive("mdPagination", function(){
return {
template: '',
restrict: 'A',
priority: 1000,
link: function($scope, el, attrs){
console.dir($scope.pages);
el.text('hello world');
},
}
});
#Sandro is right. You set the scope property in your directive to false which means that the scope will not inherit (or share) its properties with the controller. If you remove the false, it should work as you intend it.
The docs for how to use $scope are here: http://code.angularjs.org/1.2.0-rc.3/docs/api/ng.$rootScope.Scope
The developer guide for creating directives has a lot of good info about how to setup your directive so that it gives you the scope you want. Those docs are here: http://code.angularjs.org/1.2.0-rc.3/docs/guide/directive
Hope that helps.

Manipulating DOM in directive, now what?

I have made a directive (inline-edit) and manipulated the DOM in the compile function, but how can I make the other directives that I have added to work? I guess I need to compile it, but how? See my jsfiddle here: http://jsfiddle.net/tidelipop/m4gbZ/
ng-click does not work as it is, but the strange thing is, why do ng-bind work? You can see that it does work if you unhide the textarea in dev tools.
angular.module('MyApp', [], function($compileProvider){
$compileProvider.directive("inlineEdit", function($compile, $q){
return {
restrict: "A",
scope: true,
controller: function($scope){
$scope.editMode = true;
$scope.save = function(){
console.log("Saving...");
};
},
compile: function(tElement, tAttrs){
tElement
.attr("ng-hide", "editMode")
.attr("ng-click", "editMode=!editMode")
.after("<textarea ng-show=\"editMode\" ng-model=\""+tAttrs.ngBind+"\"></textarea><button ng-click=\"save()\">Save</button>");
//var scopeResolver = $q.defer();
//$compile(tElement.parent().contents())(scopeResolver.promise);
return function(scope, element, attrs, controller){
//scopeResolver.resolve(scope);
//$compile(element.parent().contents())(scope);
console.log(element.parent().contents());
};
}
};
});
})
.controller("UserAdminCtrl", function($scope){
$scope.data_copy = {
user: {
user_id: 'sevaxahe',
comment: 'test'
}
};
});
It looks like your directive is conflicting with the ng-bind, I don't really know why, but the question I asked myself looking at your code was : Wouldn't it be easier using a template and a custon attribute for the model (instead of ng-bind) ?
And the answer is yes !
Actually that's just my opinion, but here is what I did by modifying your code http://jsfiddle.net/DotDotDot/m4gbZ/73/
I let you have a look, I had to change some parts (the ng-click doesn't work well on the textarea so I put this behavior on the Save button) but I think this is almost what you wanted. On code side, I modified the HTML to avoid calling ng-bind, using a custom scope variable which will be caught in the directive :
<span inline-edit ff="data_copy.user.comment">First</span>
On the directive side, I got rid of all the compile/controller stuff, and I added a template
return {
restrict: "A",
template:'<div><span ng-hide="editMode" ng-click="editMode=!editMode">{{aModel}}</span><textarea ng-show="editMode" ng-model="aModel"></textarea> <button ng-click="save()">{{getLabel()}}</button></div>',
replace:true,
scope: {aModel:'=ff'},
link: function(scope, element, attrs){
console.log(element)
scope.editMode = true;
scope.save = function(){
console.log("Saving...");
scope.editMode=!scope.editMode;
};
scope.getLabel=function(){
if(scope.editMode)
return "Save";
else
return "Change";
}
console.log(element.parent().contents());
}
}
Why ? The template, because angular will compile it itself without any intervention.
I added replace:true to replace the line, but it's optionnal
The scope part is more important. scope: {'=ff'} tells angular that I want to use an isolated scope, and I want the scope.aModel value to be bound with the ff variable passed in the HTML.
The '=' means that the modifications will be evaluated from the parent scope, and every modification will be reflected in the parent and in the directive
I replaced your controller and your compile function (no element to compile, and adding function can be done here instead of a dedicated controller) by a linking function containing the functions needed. As I said before, I added the editMode change behavior to the Save button, so I added a bit more code, but it's not the main point, I think you may have to change things here to reflect your expected behavior
I hope this will help you, as I don't really answer your question, but I think you could also explore this way
++

Resources