I have created a directive for my application which is mentioned in the following question
How do you serve a file for download with AngularJS or Javascript? Directive code is as like below
appModule.directive('fileDownload', function ($compile) {
var fd = {
restrict: 'A',
link: function (scope, iElement, iAttrs) {
scope.$on("downloadFile", function (e, url) {
var iFrame = iElement.find("iframe");
if (!(iFrame && iFrame.length > 0)) {
iFrame = $("<iframe style='position:fixed;display:none;top:-1px;left:-1px;'/>");
iElement.append(iFrame);
}
iFrame.attr("src", url);
});
}
};
return fd;
});
Here scope.$on is used, when I call this event via $scope.$emit or $scope.$broadcast, it is not working. My controller code is like below
function reportsController($scope, $http) {
var self = this;
$scope.$broadcast("downloadFile", 'http://google.com');
$scope.$emit("downloadFile", 'http://google.com');
}
and my html file is as below
<div ng-controller="reportsController" id="repctrl">
<a file-download></a>
</div>
What I am doing wrong here?
#Edit: Added the subscribe ($on) in the compile phase so as to avoid the usage of $timeout in controller. Here you can look the example
I think your controller is being initialized before your directive.. so the $on starts listening after the $emit, $broadcast already happened.
see this plunker
open the console and you can see when the console.logs happen:
controller init happened script.js:16
link happened script.js:7
$scope.test() happened script.js:21
scope.$on happened script.js:9
scope.$on happened
If you initialize the controller with ng-view or do the emit/broadcast after the directive is created, it should work.
something similar happen to me when i try to call a function in the directive of a modal,
what i had to do was to make a delay after I made the call to show the modal:
$timeout(function () {
$rootScope.$broadcast('obtiene-plantillas');
}, 500);
Related
I am using directives to create a component library in AngularJS 1.5. Hence, my directives need to have isolate scopes.
Some of my directives have callbacks so you can pass in a function to get invoked by the directive. However, when that callback is invoked by the directive, it doesn't seem like the changes to $scope attributes are fully updated like I would expect them to be.
Here is a Plunker that shows this behavior:
http://embed.plnkr.co/Rg15FHtHgCDExxOYNwNa/
Here is what the code looks like:
<script>
var app = angular.module('myApp', []);
app.controller('Controller', ['$scope',function($scope) {
// initialize the value to something obvious
$scope.clickersValue = "BEFORE";
// when this call back is called we would expect the value to be updated by updated by the directive
$scope.clickersCallback = function() {
//$scope.$apply(); // $apply is not allowed here
$scope.clickersValueRightAfterCall = $scope.clickersValue;
console.log("clickersCallback: scope.clickersValue", $scope.clickersValue);
};
}
]);
app.directive('clicker', [function() {
return {
restrict: 'EA',
template: '<div ng-click="clicked()">click me!</div>',
controller: ['$scope', function($scope) {
$scope.clicked = function() {
console.log("you clicked me.");
$scope.newValue = 'VALID';
$scope.myUpdate();
}
}],
scope: {
"newValue": "=",
"myUpdate": "&"
}
};
}]);
</script>
So when clickersCallback gets invoked the clickersValue attribute still has the old value. I have tried using $scope.$apply but of course it isn't allowed when another update is happening. I also tried using controller_bind but got the same effect.
Wrap the code inside clickersCallback function in a $timeout function.
$timeout(function() {
$scope.clickersValueRightAfterCall = $scope.clickersValue;
console.log("clickersCallback: scope.clickersValue", $scope.clickersValue);
});
Updated plunker
The $timeout does not generate error like „$digest already in progress“ because $timeout tells Angular that after the current cycle, there is a timeout waiting and this way it ensures that there will not any collisions between digest cycles and thus output of $timeout will execute on a new $digest cycle.
source
Edit 1: As the OP said below, the user of the directive should not have to write any "special" code in his callback function.
To achieve this behavior I changed the $timeout from de controller to the directive.
Controller callback function (without changes):
$scope.clickersCallback = function() {
$scope.clickersValueRightAfterCall = $scope.clickersValue;
console.log("clickersCallback: scope.clickersValue", $scope.clickersValue);
};
Directive code (inject $timeout in the directive):
$scope.clicked = function() {
console.log("you clicked me.");
$scope.newValue = 'VALID';
$timeout(function() {
$scope.myUpdate();
});
}
Updated plunker
I want to add a directive that will do something when an element class is changed.
My directive is:
(function (angular) {
angular.module('myApp')
.directive('myClassWatch', myClassWatch);
function myClassWatch() {
return {
restrict: 'A',
link: function (scope, element, attrs, controller) {
scope.$watch(function () {
return element.attr('class');
}, function (newValue, oldValue) {
debugger;
if(oldValue !== newValue)
console.log('class changed from ' + oldValue + ' to ' + newValue);
});
}
}
}
})(angular);
The html is:
<div class="top-icons-item popup-container popup-link-container" my-class-watch>
</div>
I do some actions elsewhere that toggles the class "open" in my div element - it is visible in the html - yet the debugger is never called (also no console logs of course). I can see that the link function is called on page load and the debugger also stops, but thats only on page load and not afterwards when I actually do actions that adds another class.
I have read several issued here before including Directive : $observe, class attribute change caught only once but I can't understand why I don't get the same result. What can I do to try check why this occures?
Update: The class change is made using jQuery not in a controller but in an old jquery watches code. May this be cause? could angular be unaware of class change when its not done from an angular code?
Wrap your jQuery code into $apply.
It similar to you are making changes to $scope out of angular context(jquery ajax, setTimeout, etc). Use $apply to make angular know about the changes done.
angular.element(document.getElementById('app')).injector().invoke(['$compile', '$rootScope', function($compile, $rootScope) {
$rootScope.$apply(function() {
//your jquery code goes here...
var a = document.getElementById('abc');
angular.element(a).addClass('hello');
});
}]);
From what I understand it only runs once before the page is rendered. But is there ever a case where it runs after the page has been rendered?
I tried testing a few things with this plnkr:
angular
.module('app', [])
.directive('test', function($http) {
return {
restrict: 'E',
template: '<input />',
link: function(scope, el, attrs) {
var input = angular.element(el[0].children[0]);
input.on('change', function() {
console.log('change event');
scope.$apply(function() {
console.log('digest cycle');
});
});
input.on('keyup', function() {
console.log('keyup event');
var root = 'http://jsonplaceholder.typicode.com';
$http.get(root+'/users')
.success(function() {
console.log('http request successful');
})
.error(function() {
console.log('http request error');
});
});
console.log('link function run');
}
};
});
Does typing in the input field cause the link function to run?
Do event listeners cause the link function to run?
Do http requests (made with $http?) cause the link function to run?
Do digest cycles cause the link function to run?
The answer to all of these questions seem to be "no".
The link function runs when an instance of the directive is compiled, in this case, when a <test></test> element is created. That can be when angular's bootstrapping compiles the page, when it comes into being from a ng-if, when a ng-repeat makes it, when it's made with $compile, etc.
link will never fire twice for the same instance of the directive. Notably, it fires right after the template has been compiled in the directive's lifecycle.
1 - No, it causes to change the only ng-model if you have it binded.
2 - No, it will only launch the code inside the event binds.
3 - Again no, the event bind will launch the $http.get(). And please don't put an $http directly on your directive. Use a factory or something like that.
4 - Dunno
As Dylan Watt said, the directive link runs only when the directive is compiled (only once) per element/attr.... You can compile it in different ways. Plain http, $compile, ng-repeat....
You can create a $watch inside your directive to "relaunch" some code on a binded element change.
This maybe can help you: How to call a method defined in an AngularJS directive?
So, we have a directive grid, which exposes directive controller to the angular controller, so that controller can call functions on directive (just like a form controller).
Now, in my angular controller every thing works as long as I access the directive controller from a callback action eg. some $scope.xx which are called on some event like click or any thing.
But when i try to access the directive controller at controller initialization time, the directive controller is undefined, that means, directives link function is not called yet.
Here's some code
function controller() {
$scope.init = function() {
$scope.grid.search(xx)
}
$scope.init() // this will fail, because $scope.grid is undefined.
$scope.onClick = function() {
$scope.grid.search(xx) // this will work
}
}
is there any other way, other then watching the $scope.grid, to have the $scope.grid.search called on controller initialization
You can just broadcast event from link function in your directive.
<div ng-controller="MyCtrl">
<div abc></div>
</div>
myApp.directive('abc', function($rootScope) {
return {
restrict: "EA",
link: function(scope, element, attrs){
$rootScope.$broadcast("initialize");
}
}
});
function MyCtrl($scope) {
$scope.$on("initialize", function(){
alert("Link function has been initialized!");
});
}
I've created JSFiddle for you.
The problem is that parent controllers are always called before child controllers.
If you want to run code after your child directive is initialized, you can create a directive and put the initialization code inside of its link function.
This works because parent link functions are always called after child link (and controller) functions.
I have a webpage written in angular with an ngCloak directive. It is loaded in a dynamically sized iframe with pym.js.
The trouble is that the page does not appear unless I resize the browser or trigger a resize event, or call pymChild.sendHeight() after the page loads.
I don't see any events associated with ngCloak though. Is there an angular event for "page is rendered, controllers are initialized"?
There is the $timeout service:
$timeout(function() {
// this code will execute after the render phase
});
You could write a directive that execute a callback in postLink function, since the postLink will be called last in the $compile life cycle.
.directive('onInitialized', function ($parse) {
return {
restrict: 'A',
priority: 1000, // to ensure that the postLink run last.
link: function postLink(scope, element, attrs) {
$parse(attrs.onInitialized)(scope);
}
}
});
and place it at the element that you would like to know when it and all its template-ready decendants have got compiled, for example:
<body ng-controller="MainCtrl" on-initialized="hello()">
and in the MainCtrl controller:
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.hello = function () {
console.log('Hello ' + $scope.name);
};
})
For template-ready, I mean all directives except: directives with templateUrl and the template haven't ready in the $templateCache yet, since they will get compiled asynchronously.
Hope this helps.