I have ng-click="foo()" which alert "foo"
In my own directive, if ng-click is found, I want to add another function to alert "bar"
I tried this
DEMO: http://plnkr.co/edit/1zYl0mSxeLoMU3yjoGBV?p=preview
and it did not work
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<script src="http://code.angularjs.org/1.2.7/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.foo = function() { alert('foo'); }
$scope.bar = function() { alert('bar'); }
});
app.directive("myAttr", function() {
return {
link: function(scope, el, attrs) {
el.attr('ng-click', attrs.ngClick+';bar()');
}
}
})
</script>
</head>
<body ng-controller="MyCtrl">
<a my-attr ng-click="foo()" href="">click here!</a>
</body>
</html>
I was also not able to another ng-* directive to this to make it work, i.e. el.attr('ng-focus', 'bar()');. It seems that I cannot change or add ng-* directive once it is rendered.
How can I achieve this, and what was I doing wrong?
app.directive("myAttr", function() {
return {
priority: 1,
compile: function(el, attrs) {
attrs.ngClick += ';bar()';
}
}
})
First of all you want a compile function, for when link is called, the ng-click directive is already set up.
The second important thing is to change the priority. You want to ensure that your directive is called before ng-click. ng-click has the default priority 0, so 1 is enough.
The last and important thing, which is not obvious, is that you don't want to change the element, but attrs itself. It is created only once per element. So when ng-click accesses it it would still contain the same value, if you changed the attribute on the element directly.
I think you can do what you want with ngTransclude.
app.directive("myAttr", function() {
return {
transclude:true,
template: '<span ng-click="bar()" ng-transclude></span>',
link: function(scope, el, attrs) {
}
}
});
Does that work?
EDIT
Okay what about this one?
app.directive("myAttr", function($compile) {
return {
link: function(scope, el, attrs) {
el.attr('ng-click', 'bar()');
el.removeAttr('my-attr');
$compile(el)(scope);
}
}
});
While this could be done with compile as outlined above, that approach doesn't guarantee the order in which the ng-click items would be added to a DOM node (as you have already discovered), and is inherently slow (as has been pointed out by Words Like Jared.
Personally, I would just do something like this:
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<link rel="stylesheet" href="style.css">
<script src="http://code.angularjs.org/1.2.7/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.foo = function() { alert('foo'); }
$scope.bar = function() { alert('bar'); }
});
app.directive('myAttr', function() {
return {
scope: true,
link: function(scope, el, attrs) {
if(attrs.hasOwnProperty('ngClick')){
scope.foo = function(){
scope.$parent.foo();
scope.$parent.bar();
}
}
}
};
});
</script>
</head>
<body ng-controller="MyCtrl">
<a my-attr ng-click="foo()" href="">click here!</a>
</body>
</html>
Whats going on:
scope: true: By default directives do not create new scopes, simply sharing their parent scope. By setting scope: true, every instance of this directive will create a child scope, that will prototypically inherit from the parent scope.
Then you can simply override the method desired (foo()) and voila
Live demo:
http://plnkr.co/edit/8A8y96wAhqGEowFaRQUH?p=preview
I freely admit I may entirely misunderstand what you are trying to do. However, given the example you provided, I think you might be better served by separating concerns a little more.
It seems from your example that you are trying to trigger foo and bar together whenever your directive is present. If both foo and bar are concerns of the controller, then why not wrap them both up in another function and assign that function to the ng-click of your element. If foo is a concern of the controller, but bar is a concern of the directive, why not trigger the bar functionality directly from the directive code?
If the functionality wrapped up in 'foo' and 'bar' is suppose to be defined by the controller creator...
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<link rel="stylesheet" href="style.css">
<script src="http://code.angularjs.org/1.2.7/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.foo = function() { alert('foo'); }
$scope.bar = function() { alert('bar'); }
$scope.pak = function() {
$scope.foo();
$scope.bar();
}
});
</script>
</head>
<body ng-controller="MyCtrl">
<a ng-click="pak()" href="">click here!</a>
</body>
</html>
Or, if the functionality wrapped up in 'foo' is suppose to be defined by the controller creator, but the functionality wrapped up in 'bar' is suppose to be defined by the directive creator...
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<link rel="stylesheet" href="style.css">
<script src="http://code.angularjs.org/1.2.7/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.foo = function() { alert('foo'); }
});
app.directive('myAttr', function(){
return {
link: function(scope, element, attrs){
element.click(function(){
alert('bar');
});
}
}
});
</script>
</head>
<body ng-controller="MyCtrl">
<a ng-click="foo()" href="">click here!</a>
</body>
</html>
Related
Please refer to code below.
I'd like to avoid using ng-trasclude, as it's extra wrapping elements, + ng-transclude make own scope. So my goal is to render <div foo title="FOO!">FOO!</div> in the end.
$compile(el.html())(scope) breaks, since again, it needs a wrapping element.
template: "<div ng-transclude></div>" will fail to acces scope.title.
Thanks
EDIT
Added plunker link: https://plnkr.co/edit/R1CAc5pksOVMJoFLhsTu?p=preview
And snippet
angular.module('app', []).directive('foo', function() {
return {
restrict: 'A',
scope: {
title: '#'
}
}
});
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular.min.js"></script>
</head>
<body>
<div foo title="FOO">{{title}}</div>
<span>expecting "FOO!" above this line, but, sigh...</span>
</body>
</html>
EDIT 2
I'd like to keep isolate scope, so that attributes (<div foo title="FOO!">{{title}}</div> are then applied via scope: {title:'#'}.
EDIT 3
Updated the snippet.
You assigned isolated scope for the directive. if you want access it make it as local by given scope : true.
angular.module('app', []).directive('foo', function() {
return {
restrict: 'A',
scope: {
title : '='
},
template : '{{title}}'
}
});
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular.min.js"></script>
</head>
<body>
<div foo title="'Foo!'"></div>
<span>expecting "FOO!" above this line, but, sigh...</span>
</body>
</html>
you need to have a template to render scope.foo so in your directive you need to specify a template. also if you aren't going to have any values coming from the parent you don't have to put scope: {} as a scope will be create for you already
angular.module('app', []).directive('foo', function() {
return {
restrict: 'A',
template: '<span ng-bind="foo"></span>', // <-- add this line
link: function(scope, el) {
scope.foo = 'FOO!';
}
}
});
try with it
link: function(scope, el,attr) {
scope.$parent.foo = "foo"
scope.foo = 'FOO!';
}
I have the following problem.
I have a custom directive with some content.
There is a spacial place inside this directive that should be dynamic (some custom functionality).
The directive looks something like this:
var app = angular.module('plunker', []);
app.directive('mydirective', mydirective);
function mydirective() {
return {
restrict : "E",
scope : {
customContent: '#'
},
template : "<div>Some common directive stuff in here! <div id='customContent'></div></div>",
compile: function(element, attr) {
return {
post: function($scope, element, attr) {
console.log("POST");
},
pre: function($scope, element, attr) {
console.log("PRE: " + $scope.customContent);
if($scope.customContent) {
var customContent = (angular.element(document.getElementById('customContent')));
customContent.append($scope.customContent);
}
}
};
}
}
}
As you can see there is a div tag with id='customContent'
Now whenever I use this directive from a controller, I would like to be able not only to inject special custom content inside this directive, but also to provide the scoping functionality.
The required result should be:
controller
app.controller('MainCtrl', function($scope) {
$scope.func = function() { // THIS DOESN'T WORK
alert(1);
}
});
html
<mydirective custom-content="<button ng-controller='MainCtrl' ng-click='func()'>Test</button>"></mydirective>
As you can see I am injecting the passed custom-content as parameter in the PRE linking function. Unfortunately I was not able to hook the func() function into the controller's scope, because that's the place that I would like to be able to control the things from.
Thank you in advance for any help!
This is the plunker: https://plnkr.co/edit/I4WAQ20ugeDiackxXqUz?p=preview
This should do what you want:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.func = function() {
alert(1);
}
});
app.directive('mydirective', mydirective);
function mydirective() {
return {
restrict : "E",
transclude: true,
scope : {
},
template : "<div>Some base directive stuff in here! <ng-transclude></ng-transclude></div>",
}
}
and
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js#1.5.x" src="https://code.angularjs.org/1.5.8/angular.js" data-semver="1.5.8"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<mydirective><button ng-controller='MainCtrl' ng-click='func()'>Test</button></mydirective>
</body>
</html>
Working plunker
You can use ng-transclude to insert content inside your directive while still having access to the parent controller's scope.
Further reading on ng-transclude:
This stack overflow answer nicely explains the use of
'ng-transclude'.
For more info refer to the AngularJS documentation for ng-transclude
directive.
I have a directive, in this directive we compile ngRepeat as you see.
my problem is :
I can't call $scope.delete() from controller, and i don't know how can compile it in my directive.
Note: run the sample
var app = angular.module("app", []);
app.controller("ctrl", function ($scope, $http) {
var root = "http://jsonplaceholder.typicode.com";
$scope.list = [];
$http.get(root + "/users").success(function (data) {
$scope.list = data;
});
///i can't call this scope
$scope.delete = function (item) {
alert("delete called");
}
});
app.directive("mydata", ["$compile", "$filter", function ($compile, $filter) {
return {
restrict: "A",
scope: {
list: "="
},
link: function (scope, element) {
var ngRepeat = element.find(".repeat").attr("ng-repeat", "item in list");
$compile(ngRepeat)(scope);
}
}
}]);
<!DOCTYPE html>
<html ng-app="app" ng-controller="ctrl">
<head>
<title></title>
</head>
<body>
<ul id="parent" mydata data-list="list">
<li class="repeat">
{{item.name}}
<button ng-click="delete()">delete</button>
</li>
</ul>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
</body>
</html>
Don't know why u are trying to do stuffs like this but a quick solution for your code is to compile the ngRepeat with the controller's scope instead of the directive the scope;
$compile(ngRepeat)(scope.$parent);
Your delete() won't fire since u are creating an isolated scope on your my-data directive. The delete() method will not get inherited.
For more conception about isolated scope and scope inheritance, check https://docs.angularjs.org/guide/directive#isolating-the-scope-of-a-directive and https://docs.angularjs.org/guide/scope
I am writing a custom directive with a single field in its scope. This field is a dictionary with arrays in values.
I want the directive to react to any change made on this field : new entry, additional value in list, etc...
I'm just trying to figure out why :
my directive does not react when I change values in the dictionary.
directive is not even initialized with the initial dictionary.
Here is a simplified version of my script, where I only perform some logging in the sub-directive.Nothing happens when the button is clicked on the dictionary is modified :
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Test</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
angular.module("myApp", [])
.controller("myCtrl", function($scope) {
$scope.dico = {};
$scope.dico["Paris"] = [];
$scope.dico["Paris"].push("Tour Eiffel");
$scope.dico["Paris"].push("Champs Elysees");
$scope.dico["London"] = [];
$scope.dico["London"].push("British Museum");
$scope.addPOI = function() {
$scope.dico["Wellington"] = [];
$scope.dico["Wellington"].push("Botanic Garden");
console.log($scope.dico);
};
})
.directive('subdirective', function() {
return {
restrict: 'E',
template: '<div><span ng-repeat="key in myDico">{{key}}</span></div>',
link: function(scope, element, iAttrs) {
console.log("test");
scope.$watch("myDico", function(newVal, oldVal) {
console.log("watch!");
console.log(newVal);
//update values...
}, true);
},
scope: {
myDico: '='
}
};
});
</script>
</head>
<body ng-app="myApp">
<div ng-controller="myCtrl">
<button ng-click="addPOI()">
Add POI
</button>
<div>
<subdirective myDico="dico"></subdirective>
</div>
</div>
</body>
</html>
I have tried to use $watch, $watchCollection, deep watch, but it does not seem to do the job.
You are missing scope binding definition in your Directive Definition Object.
scope: {
myDico: '='
}
I need some help on better understanding custom animations in AngularJS 1.3.
The objective
Click on an element
Animate separate element on the DOM
I have created the following plunkr with no success
http://plnkr.co/edit/zg3BglCY9VfgPJc2pfNg?p=preview
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-animate.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="app">
<ul>
<li animate-trigger> Click on me to animate </li>
</ul>
<div class="divtoanimate animated">
Animate Action Baby
</div>
</body>
</html>
JS
'use strict';
var app = angular.module('app', ['ngAnimate'])
app.directive('animateTrigger', ['$animate', function ($animate) {
return function (scope, elem, attrs) {
elem.on('click', function (elem) {
var el = angular.element(document.getElementsByClassName("divtoanimate"));
console.log("clicked");
var promise = $animate.addClass(el, "bounceIn");
promise.then(function () {
$animate.removeClass(el, "bounceIn");
});
});
}
}]);
Use $scope.apply for the initial animation and inside your promise to both add and remove the classes. Check out the code below and the attached plunkr, which demonstrates the animation repeating each time the animage-trigger directive is clicked.
working-plunkr
var app = angular.module('app', ['ngAnimate'])
app.directive('animateTrigger', ['$animate', function ($animate) {
return function (scope, elem, attrs) {
elem.on('click', function (elem) {
scope.$apply(function() {
var el = angular.element(document.getElementsByClassName("divtoanimate"));
var promise = $animate.addClass(el, "bounceIn");
promise.then(function () {
scope.$apply(function() {
$animate.removeClass(el, "bounceIn");
});
});
});
});
}
}]);
Since you're using the jquery event handler, you need to call scope.$apply(function() {...}) to perform your $animate calls.
Here's plunkr updated with scope.$apply:
http://plnkr.co/edit/qOhLWze8pGkO9dGRp1Sg?p=preview
More on scope.$apply:
https://github.com/angular/angular.js/wiki/When-to-use-$scope.$apply()