Angular directive breaks scope? - javascript

First off, I'm new to Angular, and realize that I may be missing a core concept...
Consider the jsfiddle: http://jsfiddle.net/D4dFv/
I'd like to be able to click on each link, and see the {{driveState.currentView}} update in the DOM.
Everything works fine until I add in a directive that helps me detect when all images on the page have loaded successfully. With that directive in place, the binding appears to break, and you can no longer click on each link and see driveState.currentView update.
Why is this?
To test this in the jsfiddle, note that the following works fine:
<img width='10' height='10' src='http://www.w3schools.com/images/pulpit.jpg'>
...and this breaks the data binding somehow:
<img imageonload width='10' height='10' src='http://www.w3schools.com/images/pulpit.jpg'>
Thanks in advance.

The reason is that the Directive is defining its own controller. This makes a new instance of the controller class and is somehow messing up the scope.
To fix, take out the controller: 'Ctrl', in the Directive definition.
Here is the new Directive code:
myApp.directive('imageonload', function () {
return {
restrict: 'A',
link: function ($scope, element) {
element.bind('load', function () {
_viewsLoaded++;
if (_viewsLoaded === $scope.appViews.length) {
alert('init layout here');
}
});
}
};
});
And an updated fiddle for you.

Related

$document.ready() on directive template?

I have a directive that returns a template that does not appear to be sizing itself as it should. Elements in the template have their height set to 100%, however, it seems the parent's height (outside the directive) is not being set quick enough (also from 0 to 100%).
I do not have an issue if I refresh the page, this only comes up when resizing the window.
Example: http://codepen.io/sweatherly/pen/rLYPvE (decrease the window size, then refresh to see)
Please note the the example does not use a directive, just highlights the problem.
(function() {
"use strict";
angular
.module("ngApp")
.directive("currentCard", function() {
return {
templateUrl: 'components/orders/current/current-card.tpl.html',
scope: {
orders: "=",
cardTitle: "#cardTitle"
}
}
});
})();
Is it possible to somehow use $document.ready() on/with the template?
Edit: It turned out to be a stupid CSS issue (targeting wrong element), but I know understand a bit about directive's link function.
You can simply use the link function...
Link is a built in feature for directive, this function is executed when the directive is loaded or appears in the parent template.
Reference here ; example here
(function() {
"use strict";
angular
.module("ngApp")
.directive("currentCard", function() {
return {
templateUrl: 'components/orders/current/current-card.tpl.html',
scope: {
orders: "=",
cardTitle: "#cardTitle"
},
link: function(){
console.log("ready")
}
}
});
})();
You can use link function which will be executed after the template is loaded.
Usually any DOM manipulation, adding/removing event handlers should be done in link function.
Please refer difference between compile and link function .

AngularJS directive won't detect controller scope that binds variables using "this"

I have the following angularJS controller and directive:
angular.module('twitterApp', [])
.controller('AppCtrl', AppCtrl)
.directive('enter', EnterFunc);
function AppCtrl($scope) {
$scope.loadMoreTweets = function() {
alert('loading more tweets!');
}
}
function EnterFunc() {
return function(scope, element, attrs) {
element.bind('click', function() {
scope.loadMoreTweets();
});
}
}
And the following HTML
<body ng-app="twitterApp">
<div class="container" ng-controller="AppCtrl">
<div enter>
Roll over to load more tweets
</div>
</div>
</body>
Now, this works perfectly, since all I'm doing is accessing the controller's scope from a directive. However, I'm trying to adapt my controller to bind variables to the "this" scope so I can use the "controller as" syntax on the html to make it easier to read, however, when I change my controller function to the following:
function AppCtrl() {
var vm = this;
vm.loadMoreTweets = function() {
alert('loading more tweets!');
}
}
My example stops working and upon clicking on the directive I get the following error:
Uncaught TypeError: scope.loadMoreTweets is not a function
Can someone explain how to get this directive to work without going back to binding to $scope? Here is the Plunkr for the not/working "Controller As" version:
http://plnkr.co/edit/PyIA4HVOMLn0KNJ5V2bc?p=info
I fixed it for now, but posting my solution in case other people stumble upon this. To fix this, instead of using "scope.loadMoreTweets()" on my directive, I used "scope.ctrl.loadMoreTweets()". Even tough this works, I'm not very happy that there isn't a way to access the parent scope without the ".ctrl" since ".$parent" didn't work either. If someone has a better solution please let me know. There should be more documentation on using directives with Controller's Controller As syntax.
I have modified your code to pass a reference to which controller's scope you want to pass to the directive. Then you can call the method on the reference. See if this works for you
Directive:
function EnterFunc() {
return {
restrict: 'A',
scope: {
controller: '='
},
link: link
}
function link(scope, element, attrs) {
element.bind('click', function() {
//scope.$apply(attrs.action);
scope.controller.loadMoreTweets();
});
}
}
HTML:
<body ng-app="twitterApp">
<div class="container" ng-controller="AppCtrl as ctrl">
<div enter controller="ctrl">
Roll over to load more tweets
</div>
</div>

Call Javascript After Directive Renders DOM for ShareThis

In an Angular (1.3) app, I am displaying list of records using ng-repeat. There is a directive with a template inside the ng-repeat. Within the template I'm using ShareThis controls which are activated after the DOM is loaded.
On initial load of the app, the ShareThis Javascript works correctly and activates the buttons. On route change it does not activate. I've found to references to activate the controls manually via stButtons.makeButtons() or stButtons.locateElements();, but I'm unsure where to call this function in the directive or page cycle. I've tried within:
the directive link function - using $timeout or scope.$watch
the template <script>stButtons.locateElements();</script> - activates before model binding
the controller after binding - activates before DOM rendered
My understanding is the function to activate needs to be called after binding and after DOM rendering, but Angular does not know when the DOM is ready. There is a method to dynamically render the ShareThis controls using only Javascript, but I want the HTML defined in the template not Javascript for this case.
I've seen several questions out there related, but none of the answers seem to work 100% for my scenario (and many are broken as of Angular 1.3).
item-list.html (view)
<div ng-repeat="item in vm.itemList">
<item-directive item="item"></item-directive>
</div>
item-list.cs (controller)
{ ... vm.itemList = getItems(...) ... }
item-directive.js (directive)
(function () {
angular.module('app');
function itemDirective() {
var directive = { templateUrl: 'item.html', link: linkFunc, controller: ItemDirective };
return directive;
function linkFunc(scope, element, attr, ctrl) { var item = scope.item }
}
ItemDirective.$inject = ['$scope'];
function ItemDirective($scope) { ... }
}
item.html (directive template)
...
<div class="item-share-section">
<span class='st_sharethis_large' st_url="{{vm.item.url}}" st_title="{{vm.item.name}}"></span>
</div>
...
if I understood well, you want to call the function after the dom is completely render, right? Try this inside the postLink of your directive:
$scope.$watch('viewContentLoaded', stButtons.locateElements())
While my solution is a little hackish, I still prefer it over using $watch, since that is inefficient. Instead, I initialize the function which loads the buttons when the particular view you want to load the buttons with is rendered. The technique is as follows:
Here is the function which you should put in your controller:
$scope.loadShareThis = function() {
stButtons.makeButtons();
}
You'd then add to your item-list.html as such:
<div ng-repeat="item in vm.itemList" ng-init="loadShareThis()">
<item-directive item="item"></item-directive>
</div>
The dynamic URL's might give you additional problems, but that's another issue all together.

when ngView changed by links within itself, my functions are stripped from $scope

New to ngView, I wrote some code a little bit different from sample code about ngView. Usually the URL to manipulate ngView is outside of it, whereas in my code the link is within the ngView template. The problem is when I click on the ShowOrder link below, I get a TypeError, function $scope.show() is not defined. I printed the $scope object both before and after I click the link. My suspect is confirmed. Before the click $scope has function Show() defined, afterward $scope becomes naked as is just born by angular, without any customized properties.
Please help me to find out where I did wrong, maybe a little bit inside on how the ngView is loaded, destroyed within a scope. Thanks.
Here is the main html code:
<body ng-app="sampleApp" ng-controller="rootCtrl">
<div ng-view></div>
</body>
App.js:
var sampleApp = angular.module('sampleApp', ['ngRoute']);
sampleApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/ShowOrder', {
templateUrl: 'templates/show_order.html',
controller: 'ShowOrderController'
});
}]);
sampleApp.controller('ShowOrderController', function($scope) {
$scope.show = function {console.log("here")}
});
template code:
<h2>Show Orders</h2>
Show Order
{{show()}}
Yours says
$scope.show = function {console.log("here")}`
should be
$scope.show = function() {console.log("here");}
I kind of stumbled on a solution to my problem. Every time when I click a href to change route, the corresponding controller get re-created. It seems Angular create a new $scope.prototype first, then tries to do the binding with directives in HTML. When it meets Show(), Angular proclaims it to be undefined. Finally I take the advice of #Lizzie to use ng-click to make my life easier.

Prevent default anchor behaviour AngularJS

When the user clicks a link, I want to execute some code in a function inside the controller. But I want to prevent that the URL changes.
I tried the following possibilities
Removed the href-attribute. Didn't work, still changes the url to '/'
Tried ng-click='deleteUser(user.id, $event)' and $event.preventDefault() in my deleteUser()-function. Didn't work.
What did work is a hack I've found on GitHub about an unintended reload.
This is how I do it now:
<a ng-click="deleteUser(user.id)" href="javascript:">Delete user</a>
Question
What is the'clean' method to prevent a link from changing the URL?
<a ng-click="deleteUser(user.id)" href="">Delete user</a>
If you look at the source code for the a element directive (which is a part of the Angular core), it states at line 29 - 31:
if (!element.attr(href)) {
event.preventDefault();
}
Which means Angular already is solving the issue of links without a href. The only issue you still have is the css problem. You can still apply the pointer style to anchors that have ng-clicks, e.g.:
a[ng-click] {
/* Styles for anchors without href but WITH ng-click */
cursor: pointer;
}
So, you could even make your site more accessible by marking real links with a subtle, different styling then links that perform functions.
Happy coding!
The real problem is in the a directive
That's right, every <a></a> element is actually an AngularJS directive.
It seems to fix some issues with IE if you look the comments in the code.
But everything for me is working great when I just removed the directive from the AngularJS core code.
I was having the same problem as you did and tried all of the other solutions. The difference is that I had the problem only in IE.
If you don't want to play with building the AngularJS script from source, just search for htmlAnchorDirective in the angular.js file and remove/comment it out.
I believe there is a bigger problem here which should be addressed in the AngularJS core, but I haven't found it yet.
UPDATE: This solution is most probably outdated now! You should try using the latest AngularJS version.
What exactly didn't work when you removed the href attribute?
That's exactly what you should do. See this fiddle for an example:
http://jsfiddle.net/terebentina/SXcQN/
As #Justus pointed out, if the source code is like:
if (!element.attr(href)) {
event.preventDefault();
}
Making element.attr(href) as null string '' should get you inside the if condition, as !'' evaluates to true. That is the solution by #umur
I have always been doing deleteUser($event,user.id) and it seemed to work. A possible problem would be the ordering of the variables to your click handler. The first argument should probably be the $event object.
I use my custom directive to accomplish this. Its mechanism is to operate ng-click directive itself.
angular.module('delete', [])
.directive('buttonDelete', ['$parse', function ($parse) {
var confirmFunc = function (scope, element, attr, event, ngClick) {
if (! confirm("Are you sure?")) {
event.preventDefault();
}
else {
// Copy from ngEventDirectives initialization.
var fn = $parse(ngClick);
scope.$apply(function() {
fn(scope, {$event:event});
});
}
};
return {
restrict: 'C',
compile: function (element, attr) {
var ngClick = attr.ngClick;
attr.ngClick = '';
return {
pre: function (scope, element, attr) {
element.click(function (e) {
confirmFunc(scope, element, attr, e, ngClick);
});
}
};
}
};
}]);
This is admittedly a hack, but I did:
<span class="link" ng-click="deleteUser(user.id)">Delete user</span>
and in my css had
span.link {
/* style like a link */
}
The following worked great for me:
<a ng-href="javascript: return false;" ng-click="alertSearchCtrl.deleteAlert()">Remove</a>

Categories