get scope in directive - angularjs

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

Related

What is the correct way to access controller that was required inside directive controller?

I have a directive with require property:
require: '^testBox'
now I want to get testBox controller inside controller of my directive. How should I do it?
I was trying to do so:
controller: function(){
this.testBox.user
}
but looks like it does not work.
It's clear for me how to get required controller inside link function. But is there a way to get it inside controller without using link?
Code on plunker.
This is still an open issue. So at the moment you can not just inject the required controller into your directive controller. I have updated your Plunker. It's definitely a bit hacky but the problem is; You cannot expose the TextBoxCtrl to the UserCtrl in either the pre or post link function because the controller gets executed first. So my idea is to use a watcher to observe a scope varibale called textBox. Once the value is defined I declare a variable on the UserCtrl and remove the watcher. Now you can simply use it in your template like so:
{{ user.textBox.name }}
Here is the code for the link function and the controller of the user directive:
link: function($scope, $element, $attrs, ctrl) {
$scope.textBox = ctrl
},
controller: function($scope) {
var vm = this;
var watcher = $scope.$watch('textBox', function(newVal) {
if(newVal) {
vm.textBox = newVal;
watcher();
}
});
}
However, you can also go with a link function instead. The required controller will be injected as the fourth parameter.
When you use controllerAs it's just added as a property of the underlying scope object (using the name you've defined). Knowing this, you can attach the parent controller instance as a property of your child controller instance as follows:
function exampleDirective() {
return {
require: '^testBox',
link: function (scope, element, attrs, testBox) {
scope.example.testBox = testBox;
},
controllerAs: 'example',
controller: function() {
// silly example, but you get the idea!
this.user = this.testBox.user;
}
}
};

Find closest controller scope

Is there a way to find the closest parent scope inside a directive that is a controller scope?
Use case.
The important part is at line 212 is the js file.
The template I render from the directive has scope expressions passed in the directive attribute and I need to link them to the controller scope.
Because the directive has an isolated scope, I link it to scope.$parent because in this case the parent is the controller.
But this might not always be the case, so how can I find the closest parent controller scope.
Can I loop trough the scope checking if it's a controller scope?
var template = angular.element(html);
var linkFn = $compile(template);
var child = linkFn(scope.$parent); //HERE IS THE PROBLEM
$(element).append(child);
You may try to use jqLite/jQuery methods from within your directive to traverse DOM tree up and locate the required parent (element.parent(), etc). Then you can get the scope object related to this parent by calling scope() method on it, which you should be able to pass as an argument to your compilation function.
It's not the cleanest approach, but you can always pass in an object to the directive's scope.
angular.app('myApp', [])
.controller('myCtrl', [function() {
var self = this;
self.directiveConfig = {
method1: function() { /* do something cool */ },
method2: function() { /* do something cool */ }
};
}])
.directive('myElem', [function() {
var myElem = {
restrict: 'AE',
controller: function() { /* some custom controller */ },
link: function(scope, element, attributes, ctrl) {
/**
* scope.config will now be tied to self.directiveConfig
* as defined in the controller above
*/
},
scope: {
config: '='
}
};
});
Then, assuming your controllerAs is set to ctrl, you could do.
<my-elem config="ctrl.directiveConfig"></my-elem>
A better solution would be to put any functions you need to use like that inside a service that can be reused.
See there are many ways . One of the way could be , you could check for an variable that is defined on that controller scope like this
if(angular.isDefined(scope.$parent.variableName))
{
// controller is defined
}
else
{
// controller is not defined
}
If this is defined then it is your controller , otherwise something else. Second way would be to check parent scope of the parent like this
if(angular.isDefined(scope.$parent.$parent)) {
//controller is defined
}
else
{
//controller is not defined
}
As every controller will have a parent as $rootScope or $scope of some other controller . That means if it is not a controller then this would result in undefined and will go else condition.
requiring ngController seems to be the best solution.
.directive("mobilitTree", function() {
return {
...
require: "ngController",
...
link: function(scope, element, attrs, ngCtrl) {
...
}
};
})
.controller("AppController", function($scope, ...) {
//Only variable applied on this are public
this.controllerOnSort = 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.

AngularJS: Parent scope is not updated in directive (with isolated scope) two way binding

I have a directive with isolated scope with a value with two way binding to the parent scope. I am calling a method that changes the value in the parent scope, but the change is not applied in my directive.(two way binding is not triggered). This question is very similar:
AngularJS: Parent scope not updated in directive (with isolated scope) two way binding
but I am not changing the value from the directive, but changing it only in the parent scope. I read the solution and in point five it is said:
The watch() created by the isolated scope checks whether it's value for the bi-directional binding is in sync with the parent's value. If it isn't the parent's value is copied to the isolated scope.
Which means that when my parent value is changed to 2, a watch is triggered. It checks whether parent value and directive value are the same - and if not it copies to directive value. Ok but my directive value is still 1 ... What am I missing ?
html :
<div data-ng-app="testApp">
<div data-ng-controller="testCtrl">
<strong>{{myValue}}</strong>
<span data-test-directive data-parent-item="myValue"
data-parent-update="update()"></span>
</div>
</div>
js:
var testApp = angular.module('testApp', []);
testApp.directive('testDirective', function ($timeout) {
return {
scope: {
key: '=parentItem',
parentUpdate: '&'
},
replace: true,
template:
'<button data-ng-click="lock()">Lock</button>' +
'</div>',
controller: function ($scope, $element, $attrs) {
$scope.lock = function () {
console.log('directive :', $scope.key);
$scope.parentUpdate();
//$timeout($scope.parentUpdate); // would work.
// expecting the value to be 2, but it is 1
console.log('directive :', $scope.key);
};
}
};
});
testApp.controller('testCtrl', function ($scope) {
$scope.myValue = '1';
$scope.update = function () {
// Expecting local variable k, or $scope.pkey to have been
// updated by calls in the directive's scope.
console.log('CTRL:', $scope.myValue);
$scope.myValue = "2";
console.log('CTRL:', $scope.myValue);
};
});
Fiddle
Use $scope.$apply() after changing the $scope.myValue in your controller like:
testApp.controller('testCtrl', function ($scope) {
$scope.myValue = '1';
$scope.update = function () {
// Expecting local variable k, or $scope.pkey to have been
// updated by calls in the directive's scope.
console.log('CTRL:', $scope.myValue);
$scope.myValue = "2";
$scope.$apply();
console.log('CTRL:', $scope.myValue);
};
});
The answer Use $scope.$apply() is completely incorrect.
The only way that I have seen to update the scope in your directive is like this:
angular.module('app')
.directive('navbar', function () {
return {
templateUrl: '../../views/navbar.html',
replace: 'true',
restrict: 'E',
scope: {
email: '='
},
link: function (scope, elem, attrs) {
scope.$on('userLoggedIn', function (event, args) {
scope.email = args.email;
});
scope.$on('userLoggedOut', function (event) {
scope.email = false;
console.log(newValue);
});
}
}
});
and emitting your events in the controller like this:
$rootScope.$broadcast('userLoggedIn', user);
This feels like such a hack I hope the angular gurus can see this post and provide a better answer, but as it is the accepted answer does not even work and just gives the error $digest already in progress
Using $apply() like the accepted answer can cause all sorts of bugs and potential performance hits as well. Settings up broadcasts and whatnot is a lot of work for this. I found the simple workaround just to use the standard timeout to trigger the event in the next cycle (which will be immediately because of the timeout). Surround the parentUpdate() call like so:
$timeout(function() {
$scope.parentUpdate();
});
Works perfectly for me. (note: 0ms is the default timeout time when not specified)
One thing most people forget is that you can't just declare an isolated scope with the object notation and expect parent scope properties to be bound. These bindings only work if attributes have been declared through which the binding 'magic' works. See for more information:
https://umur.io/angularjs-directives-using-isolated-scope-with-attributes/
Instead of using $scope.$apply(), try using $scope.$applyAsync();

angular.js Getting the element from inside $evalAsync in directive

I'm finding that I'm using scope.$evalAsync inside a directive quite a lot. Mainly to do DOM stuff/jquery plugins that need all the template {{vars}} compiled.
I can get at the scope object from inside $evalAsync but not the element. In latest case in question, I'm manipulating an element that gets rendered with an ngRepeat. I'm currently getting the element by composing a jquery selector based on the scope object e.g.
scope.$evalAsync(function (scope) {
$("#item-" + scope.id).runJQplugin();
})
Although this works, to me it would be more intuitive to be able to do this
scope.$evalAsync(function (scope,element) {
element.runJQplugin();
})
Am I approaching this right or have I misunderstood something fundamental with directives?
You always have access to the element from the link and the controller of a directive through the closure scope. So in link function:
link: function(scope, elem, attrs) {
...
scope.$evalAsync(function(scope) {
elem.runJQplugin();
});
...
},
Controller (you need to specify the special $element dependency):
controller: ["$scope", "$element", function($scope, $element) {
...
scope.$evalAsync(function(scope) {
$element.runJQplugin();
});
...
}],

Resources