I try to create a directive with two params, class and pageYOffset. I would like to check the scrolling position of my element and add a class name if the pageYOffset is bigger than the number in the attr. Somehow I cant't trigger the class changes.
HTML
<div scroll offset="1500" ng-class="{active : scrollClass}"></div>
Directive
app.directive('scroll', function($window) {
return {
scope: {
offset: "#offset"
},
link: function (scope, element, attr) {
angular.element($window).bind("scroll", function() {
if (this.pageYOffset >= scope.offset) {
scope.scrollClass = true;
}
scope.$apply();
});
}
}
});
As you are using isolated class, your directive scope is different scope than you controller scope, controller scope will not get inherited to directive scope because scope: {..}, As you want to change flag of parent scope you need to pass that value inside your directive with = which will perform two way binding as inner scope variable change will affect to the parent scope variable value.
Makrup
<div scroll offset="1500" scroll-class="scrollClass" ng-class="{active : scrollClass}"></div>
Directive
app.directive('scroll', function($window) {
return {
scope: {
offset: "#offset",
scrollClass: '='
},
link: function (scope, element, attr) {
angular.element($window).bind("scroll", function(event) {
if (element.pageYOffset >= scope.offset) {
//this will change the parent scope variable value to true
scope.scrollClass = true;
}
scope.$apply(); //need full to run digest cycle
});
}
}
});
You can try something like tihs:
app.directive('scroll', function($window) {
return {
scope: {
offset: "#offset",
toggleScroll: "&toggle"
},
link: function (scope, element, attr) {
angular.element($window).bind("scroll", function() {
if (this.pageYOffset >= scope.offset) {
toggle();
}
//scope.$apply();
});
}
}
});
<div scroll offset="1500" toggle-scroll="changeActiveClass()" ng-class="{active : scrollClass}"></div>
Then define changeActiveClass in your parent scope:
$scope.changeActiveClass = function() {
$scope.scrollClass = !scrollClass;
}
Related
I have a custom directive:
.directive('test', function () {
return {
scope: {},
link: function (scope, element, attr) {
scope.$parent.$watch(attr.selectedItem, function(newValue, oldValue){
scope.selectedItem = newValue;
});
}
}
This will one way bind my directive's scope's selectedItem property to the value set in the attribute as such
<div test selectedItem="thePropertyOnTheController"></div>
But what if I want to two way bind? Is there an easy way to set this up without $watch'ing the directive's scope's selectedItem property and $parse'ing the attr.selectedItem expression and calling assign witht he parsed expression on scope.$parent?
$scope.thePropertyOnTheController might have some value like "Hello"
HTML
<div ng-repeat="photosets in userPhotoSetList">
<photosets photosetsarray="photosets.photosetDetail">
</div>
script :
.directive('photosets', function () {
return {
scope: {
photosetslist : "=photosetsarray"
},
link: function (scope, element, attr) {
console.log(scope.photosetslist);
//"Hello" is output
}
}
If you see photosetsarray="photosets.photosetDetail"" photosetsarray and
scope: {
photosetslist : "=photosetsarray" **//this name is same as assignee attr**
},
leftside variable name in html must = right side variable name in directive
Be careful with variable naming in these situations. Binding to an attribute that is declared as camel case in the directive cannot be accessed as such from the DOM.
.directive('test', function () {
return {
scope: {
item : "=selectedItem"
},
link: function (scope, element, attr) {
//do some stuff
}
}
So to correctly bind this attribute to a variable on the controller:
<div test selected-item="thePropertyOnTheController"></div>
I have the following markup:
<div class="controller" ng-controller="mainController">
<input type="text" ng-model="value">
<div class="matches"
positions="{{client.positions | filter:value}}"
select="selectPosition(pos)">
<div class="match"
ng-repeat="match in matches"
ng-click="select({pos: match})"
ng-bind="match.name">
Then, inside my matches directive I have
app.directive('matches', function()
{
return {
scope: {
select: '&'
},
link: function(scope, element, attrs)
{
scope.matches = [];
attrs.$observe('positions', function(value)
{
scope.matches = angular.fromJson(value);
scope.$apply();
})
}
}
}
When I do this, I can console log scope.matches, and it does change with the value from my input. However, the last div .match doesn't render anything! If I remove scope: {...} and replace it with scope: true, then it does render the result, but I want to use the & evaluation to execute a function within my main controller.
What do i do?
Use scope.$watch instead, you can watch the attribute select whenever changes are made from that attribute.
app.directive('matches', function()
{
return {
scope: true,
link: function(scope, element, attrs)
{
scope.matches = [];
scope.$watch(attrs.select, function(value) {
scope.matches = angular.fromJson(value);
});
}
}
}
UPDATE: Likewise, if you define select itself as a scope attribute, you must use the = notation(use the & notation only, if you intend to use it as a callback in a template defined in the directive), and use scope.$watch(), not attr.$observe(). Since attr.$observe() is only used for interpolation changes {{}}, while $watch is used for the changes of the scope property itself.
app.directive('matches', function()
{
return {
scope: {select: '='},
link: function(scope, element, attrs)
{
scope.matches = [];
scope.$watch('select', function(value) {
scope.matches = angular.fromJson(value);
});
}
}
}
The AngularJS Documentation states:
$observe(key, fn);
Observes an interpolated attribute.
The observer function will be invoked once during the next $digest
following compilation. The observer is then invoked whenever the
interpolated value changes.
Not scope properties defined as such in your problem which is defining scope in the directive definition.
If you don't need the isolated scope, you could use $parse instead of the & evaluatation like this:
var selectFn = $parse(attrs.select);
scope.select = function (obj) {
selectFn(scope, obj);
};
Example Plunker: http://plnkr.co/edit/QZy6TQChAw5fEXYtw8wt?p=preview
But if you prefer the isolated scope, you have to transclude children elements and correctly assign the scope of your directive like this:
app.directive('matches', function($parse) {
return {
restrict: 'C',
scope: {
select: '&',
},
transclude: true,
link: function(scope, element, attrs, ctrl, transcludeFn) {
transcludeFn(scope, function (clone) {
element.append(clone);
});
scope.matches = [];
attrs.$observe('positions', function(value) {
scope.matches = angular.fromJson(value);
});
}
}
});
Example Plunker: http://plnkr.co/edit/9SPhTG08uUd440nBxGju?p=preview
I have a nested directive. I am trying to access the scope of the parent directive (which is isolated) but can't seem to make it work. I get undefined errors when trying to log it out to the console.
Here's an example of what I am trying to get to work.
app.directive("myParentControl", function() {
return {
restrict: "A",
scope: {},
controller: function($scope) {
$scope.propertyOne = "PropertyOne"
},
link: function(scope, element) {
console.log(scope.propertyOne);
}
}
});
app.directive("myChildControl", function() {
return {
require: "^myParentControl",
link: function(scope, element, attrs, myParentControlCtrl) {
//Undefined
console.log(myparentControlCtrl.propertyOne);
//Not visible in scope inspector
myParentControlCtrl.newValue = "New Value";
}
}
})
You are setting the variable to the $scope: $scope.propertyOne = "PropertyOne", but try to access it from the controller: console.log(myparentControlCtrl.propertyOne). Of course it is undefined.
Set the property in the controller:
controller: function($scope) {
this.propertyOne = "PropertyOne";
},
If you need to access it from the template of myParentControl, put the controller in the scope using the controllerAs property:
app.directive("myParentControl", function() {
return {
...
controllerAs: "ctrl",
...
};
});
From the template access it as:
<span>{{ ctrl.propertyOne }</span>
You can directly access the scope of the parent directive using scope in child directives.
myApp.directive("myChildControl", function() {
return {
require: "^myParentControl",
link: function(scope, element, attrs, myParentControl) {
console.log(scope.propertyOne);
//Not visible in scope inspector
myParentControl.newValue = "New Value";
}
}
})
SEE DEMO HERE
I have a custom directive:
.directive('myDirective', function() {
return {
scope: {ngModel:'='},
link: function(scope, element) {
element.bind("keyup", function(event) {
scope.ngModel=0;
scope.$apply();
});
}
}
});
This works as planned, setting the variables to 0 on keyup, but it doesn't reflect the changes on the input themselves. Also when initialized, the values of the model are not in the input. Here is an example:
http://jsfiddle.net/prXm3/
What am I missing?
You need to put a watcher to populate the data since the directive creates an isolated scope.
angular.module('test', []).directive('myDirective', function () {
return {
scope: {
ngModel: '='
},
link: function (scope, element, attrs) {
scope.$watch('ngModel', function (val) {
element.val(scope.ngModel);
});
element.bind("keyup", function (event) {
scope.ngModel = 0;
scope.$apply();
element.val(0); //set the value in the dom as well.
});
}
}
});
Or, you can change the template to
<input type="text" ng-model="$parent.testModel.inputA" my-directive>
the data will be populated thought it will break your logic to do the event binding.
So it is easier to use the watcher instead.
Working Demo
I'm trying to understand directives, and I'm having problems with two way data binding.
My directive will be used to submit a form when "enter" is pressed in a textarea.
I found a solution in another SO thread (see the code below in the scope definition of the directive), but I don't like it because it means that if I change the model name, I need to change the directive as well..
--> Here is the problem in codepen.io
Here is the html part:
<div ng-app="testApp" ng-controller="MyController">
<textarea ng-model="foo" enter-submit="submit()"></textarea><br/>
Binding: {{foo}}
</div>
Here is the javascript part:
var app = angular.module('testApp', []);
function MyController($scope) {
$scope.foo = "bar"
$scope.submit = function() {
console.log("Submitting form");
}
}
app.directive('enterSubmit', function () {
return {
restrict: 'A',
scope: {
submitFn: '&enterSubmit',
foo: '=ngModel' // <------------------- dont't like this solution
},
link: function (scope, elem, attrs) {
elem.bind('keydown', function(event) {
var code = event.keyCode || event.which;
if (code === 13) {
if (!event.shiftKey) {
event.preventDefault();
scope.submitFn();
}
}
});
}
}
});
Thanks for your help !
When multiple directives are used on an element, normally you don't want any of them to use an isolate scope, since that forces all of them to use the isolate scope. In particular, isolate scopes should not be used with ng-model – see Can I use ng-model with isolated scope?.
I suggest creating no new scope (the default, i.e., scope: false):
app.directive('enterSubmit', function () {
return {
restrict: 'A',
//scope: {
// submitFn: '&enterSubmit',
// foo: '=ngModel' // <------------------- dont't like this solution
//},
link: function (scope, elem, attrs) {
elem.bind('keydown', function(event) {
var code = event.keyCode || event.which;
if (code === 13) {
if (!event.shiftKey) {
event.preventDefault();
scope.$apply(attrs.enterSubmit);
}
}
});
}
}
});