I wrote a small directive working under Angular 1.6.1 and material 1.1.1.
It is a simple lock/unlock button icon.
I had to update material to 1.1.3 (for the date picker), but since then the directive doesn't work anymore.
I do not understand why a material update would do this....
The plunker below works, but if you change the material version to 1.1.2, it stops working.
http://plnkr.co/edit/ZamxN3WTXaOl5cTv4aWI?p=info
index.html:
<html lang="en" >
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/angular-material/1.1.3/angular-material.css">
<link rel="stylesheet" href="//fonts.googleapis.com/icon?family=Material+Icons">
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular-animate.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular-aria.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-material/1.1.1/angular-material.js"></script>
<!-- <script src="//cdnjs.cloudflare.com/ajax/libs/angular-material/1.1.1/angular-material.js"></script> -->
<script src="script.js"></script>
</head>
<body ng-app="app" ng-controller="controller as ctrl">
<ju-lock ng-model="ctrl.lock"></ju-lock>|{{ctrl.lock}}|
</body>
</html>
script.js:
angular
.module('app', ['ngMaterial'])
.directive('juLock', function(){
return {
restrict: 'E',
scope: {
bindModel: '=ngModel'
},
template:
'<md-button class="md-icon-button">'+
'<md-icon class="material-icons">lock_open</md-icon>'+
'</md-button>|{{bindModel}}',
link: function(scope, element, attributes){
element.on('click', function (ev) {
scope.bindModel = !scope.bindModel;
});
scope.$watch('bindModel', function(){
angular.element(element[0].querySelector('.material-icons')).text(scope.bindModel ? 'lock' : 'lock_open');
});
}
};
})
.controller('controller', function(){
var vm = this;
vm.lock=true;
});
I have investigated as much as I could before asking the stack community, does anyone have some insight into this?
Avoid using ng-model as an attribute in custom directives. If you do so, don't use isolate scope two-way binding with it. Use the ngModelController API which that attribute instantiates.
The main problem is the jqLite click handler needs to notify the AngularJS framework of changes to scope with scope().$apply():
element.on('click', function (ev) {
scope.bindModel = !scope.bindModel;
//USE $apply
scope.$apply();
});
Angular modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and Angular execution context. Only operations which are applied in Angular execution context will benefit from Angular data-binding, exception handling, property watching, etc... You use $apply() to enter Angular execution context from JavaScript.
Keep in mind that in most places (controllers, services) $apply has already been called for you by the directive which is handling the event. An explicit call to $apply is needed only when implementing custom event callbacks, or when working with third-party library callbacks.
— AngularJS Developer Guide - Integration with the browser event loop
The DEMO on PLNKR
Also instead of manipulating DOM to change lock and unlock icons. It can done with the ng-show and ng-hide directives:
app.directive('juLock', function(){
return {
restrict: 'E',
scope: {
bindModel: '=myModel'
},
template:
'<md-button class="md-icon-button">'+
'<md-icon ng-show="bindModel" class="material-icons">lock</md-icon>'+
'<md-icon ng-hide="bindModel" class="material-icons">lock_open</md-icon>'+
'</md-button>|{{bindModel}}',
link: function(scope, element, attributes){
element.on('click', function (ev) {
scope.bindModel = !scope.bindModel;
//USE $apply
scope.$apply();
});
/*
scope.$watch('bindModel', function(){
angular.element(element[0].querySelector('.material-icons')).text(scope.bindModel ? 'lock' : 'lock_open');
});
*/
}
};
})
I am not sure about your exact problem among versions, but check this fiddle, this code will work with both versions.
The key point is using ng-click, which is from angularJs, rather than depending on element.on(), which may vary if you inject jQuery or if you not. Plus this way is more declarative
The only change I did was in your directive
.directive('juLock', function(){
return {
restrict: 'E',
scope: {
bindModel: '=ngModel'
},
template:
'<md-button ng-click="bindModel = !bindModel" class="md-icon-button">'+
'<md-icon class="material-icons">{{lock_text}}</md-icon>'+
'</md-button>|{{bindModel}}',
link: function(scope, element, attributes){
scope.lock_text = '';
scope.$watch('bindModel', function(){
scope.lock_text = scope.bindModel ? 'lock' : 'lock_open';
});
}
};
})
I also removed dom inspection in your watch, and I did it more in an Angular-Style
Related
I want to make a simple directive that has a button that will show if the cursor is placed over it. However, every time I include this new directive into my index.html I receive this error in return.
Here is the following error:
Error shown on console
Here is my template code:
<div>
<button ng-show="ishovering">DELETE</button>
</div>
Here is my directive code:
app.directive('deleteArea',function(){
return {
scope: {},
require: 'ng-show',
restrict: "AE",
replace: true,
templateUrl: "./templates/delete.html",
link: function(scope,elem,attrs){
elem.bind('mouseover',function(){
elem.css('cursor','pointer');
scope.$apply(function(){
scope.ishovering = true;
});
});
}
};
});
Any help is appreciated, thank you.
Just use bootstrap for angular called ui.bootstrap .
Use the popover directive that will give you the effect you need.
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>
Hi I am using one small directive for hide and show bootstrap modal from controller which was working fine when i was not using ngAnimate. But after inclusing ngAnimate it shows
element.modal is not a function
below is my directive
app.directive('akModal', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
scope.$watch(attrs.akModal, function(value) {
if (value) element.modal('show');
else element.modal('hide');
});
}
};
});
any fixes?
Managed to do it myself.
actually we should load bootsrap.js befor NGAnimate to avoid clashes.
Reason:
element.modal function is created in bootsrap.js so it should be loaded in order to use it afterwards.
I know how to detect the document end while scrolling in plain JS, my problem is - how to implement that in AngularJS, I know I should attach the scroll event to both the $window and $document, my question is about where to implement the behavior?, directives?, services?, if anyone can show me what is the right way to implement that kind of detection in AngularJS I'll be very thankful.
After struggling with it for a long time- stumbled across the library: http://binarymuse.github.io/ngInfiniteScroll/documentation.html.
Based on your use-case, you could do something like:
<div infinite-scroll="addMoreItems()">
<div ng-repeat="item in items">Item number {{$index}}: {{$item}}</div>
</div>
As it allows you to attach this to any div- you could almost do anything in the function you want to.
Depending on what you're doing it can go in a combination of places. Typically when dealing with DOM manipulation (which is what I assume will be happening) you should use a directive - something like:
app.directive("scrollDetector", ["$document", "$window", function($document, $window) {
return {
restrict: "A",
link: function(scope, elem, attrs) {
angular.element($window).bind("scroll", function() {
//scroll logic here
});
}
}
}]);
And then implement the scroll-detector directive. (ex: <div scroll-detector></div>)
I know I can apply to the template/templateURL, but currently the content will load from another place in my application.
JSbin: http://jsbin.com/imuseb/1/
HTML
<html ng-app="app">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.min.js"></script>
</head>
<body>
<div alert>here</div>
</body>
</html>
JS code:
var app = angular.module('app', []);
app.directive('alert', function (){
return {
link: function (scope, element, attrs) {
element.on("click", function (){
alert("here");
});
}
};
});
$(function (){
$("body").append("<div alert>here too</div>");
});
The new DOM must be accessible to the angular app in order to be compiled correctly. Here is one way to do this (not the only way). For applying the new DOM to the app's $rootScope, change this:
$(function (){
$("body").append("<div alert>here too</div>");
});
to:
app.run(function($rootScope){
$rootScope.$apply($("body").append("<div editable>here too</div>"));
});
According to the Angular docs:
$apply() is used to execute an expression in angular from outside of the angular framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
It would be best if you could load your extra content from within the angular framework. Check out ng-include.
If that can't be used to do what you need, you'll have to manually call the angular compile service on the element, to compile it and link it onto the scope yourself using $compile.
Compiling elements touches the DOM, and therefore should be done within a custom directive if possible.