Get value of scope variable in controller AngularJS - angularjs

I have to fetch value of scope variable defined in directives. I have to get value of that scope variable in controller using AngularJS. How can i fetch value of scope variable?
Directive
app.directive('checkToggle', function() {
return {
scope: true,
link: function ($scope, element, attrs) {
$(element).on('click', function() {
$(element).find('i').toggleClass('icon-check icon-check-empty');
if ($(element).find('i').hasClass('icon-check')) {
$scope.isChecked = 'true';
} else {
$scope.isChecked = 'false';
}
});
}
}
});
I have to get $scope.isChecked value in controller.

If I understand your use-case correctly you would like to toggle an icon on click. If so you don't need to write any directive for this. And provided that you would like to write a directive your shouldn't go about it as you've started. Your code is very imperative, jQuery-like while AngularJS power is in driving declarative UI based on model changes.
Anyway, toggling an icon can be easily done with standard AngularJS directives:
<i ng-class="{'icon-star' : isChecked, 'icon-star-empty': !isChecked}" ng-click="isChecked = !isChecked"></i>
Here is a working plunk: http://plnkr.co/edit/nXXQA41w00Cpeo6tTibg?p=preview

Related

AngularJS directive doesn't update scope value even with apply

I'm usin a directive to show a div on the screen only when the screen size is smaller than 600px. The problem is, the scope value isn't being updated, even using $apply() inside the directive.
This is the code:
function showBlock($window,$timeout) {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
scope.isBlock = false;
checkScreen();
function checkScreen() {
var wid = $window.innerWidth;
if (wid <= 600) {
if(!scope.isBlock) {
$timeout(function() {
scope.isBlock = true;
scope.$apply();
}, 100);
};
} else if (wid > 600) {
if(scope.isBlock) {
$timeout(function() {
scope.isBlock = false;
scope.$apply();
}, 100);
};
};
};
angular.element($window).bind('resize', function(){
checkScreen();
});
}
};
}
html:
<div ng-if="isBlock" show-block>
//..conent to show
</div>
<div ng-if="!isBlock" show-block>
//..other conent to show
</div>
Note: If I don't use $timeout I'll get the error
$digest already in progress
I used console logs inside to check if it's updating the value, and inside the directive everything works fine. But the changes doesn't go to the view. The block doesn't show.
You should use do rule in such cases to get the advantage of Prototypal Inheritance of AngularJS.
Basically you need to create a object, that will will have various property. Like in your case you could have $scope.model = {} and then place isBlock property inside it. So that when you are inside your directive, you will get access to parent scope. The reason behind it is, you are having scope: true, which says that the which has been created in directive is prototypically inherited from parent scope. That means all the reference type objects are available in your child scope.
Markup
<div ng-if="model.isBlock" show-block>
//..conent to show
</div>
<div ng-if="!model.isBlock" show-block>
//..other conent to show
</div>
Controller
app.controller('myCtrl', function($scope){
//your controller code here
//here you can have object defined here so that it can have properties in it
//and child scope will get access to it.
$scope.model = {}; //this is must to use dot rule,
//instead of toggle property here you could do it from directive too
$scope.isBlock = false; //just for demonstration purpose
});
and then inside your directive you should use scope.model.isBlock instead of scope.isBlock
Update
As you are using controllerAs pattern inside your code, you need to use scope.ag.model.isBlock. which will provide you an access to get that scope variable value inside your directive.
Basically you can get the parent controller value(used controllerAs pattern) make available controller value inside the child one. You can find object with your controller alias inside the $scope. Like here you have created ag as controller alias, so you need to do scope.ag.model to get the model value inside directive link function.
NOTE
You don't need to use $apply with $timeout, which may throw an error $apply in progress, so $timeout will run digest for you, you don't need to worry about to run digest.
Demo Here
I suspect it has something to do with the fact that the show-block directive wouldn't be fired if ng-if="isBlock" is never true, so it would never register the resize event.
In my experience linear code never works well with dynamic DOM properties such as window sizing. With code that is looking for screens size you need to put that in some sort of event / DOM observer e.g. in angular I'd use a $watch to observe the the dimensions. So to fix this you need to place you code in a $watch e.g below. I have not tested this code, just directional. You can watch $window.innerWidth or you can watch $element e.g. body depending on your objective. I say this as screens will be all over the place but if you control a DOM element, such as, body you have better control. also I've not use $timeout for brevity sake.
// watch window width
showBlock.$inject = ['$window'];
function bodyOverflow($window) {
var isBlock = false;
return {
restrict: 'EA',
link: function ($scope, element, attrs) {
$scope.$watch($window.innerWidth, function (newWidth, oldWidth) {
if (newWidth !== oldWidth) {
return isBlock = newWidth <= 600;
}
})
}
};
}
// OR watch element width
showBlock.$inject = [];
function bodyOverflow() {
var isBlock = false;
return {
restrict: 'EA',
link: function ($scope, element, attrs) {
$scope.$watch($element, function (new, old) {
if (newWidth) {
return isBlock = newWidth[0].offsetWidth <= 600;
}
})
}
};
}

ng-style Variable Not Updating When Scope is Updated

I'm monitoring a CSS style and updating a variable in the scope that's based on that CSS style's value. It works the first time around but when the browser is resized, the scope gets updated but ng-style does not update with the new scope parameter.
JS:
.directive('monitorStyle', function() {
return {
link: function(scope, element, attrs) {
scope[attrs.updateVariable] = $(element).css(attrs.monitorStyle);
angular.element(window).on('resize', function() {
scope[attrs.updateVariable] = $(element).css(attrs.monitorStyle);
});
}
}
})
HTML:
<p class="text" monitor-style="font-size" update-variable="textHeight">Press "<img class="mini up" src="img/select-arrow.png" src="Up" ng-style="{'height': textHeight}">
I'm trying to do this outside of the controller because that's what people recommend. Why is ng-style not updating when the scope gets updated?
The window event isn't an angular event, so angular don't know he have to update the model/scope. You have to add scope.$apply() to tell angular to refresh it :
angular.element(window).on('resize', function() {
scope[attrs.updateVariable] = $(element).css(attrs.monitorStyle);
scope.$apply();
});
Data bindig only works when your model is updated with angular event like $http, $timeout, ng-click, ...
A great article about it : http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
Yo, I made this sick solution.
So if you want to watch styles (even an array of them on a particular element) and then send their values to the $scope you can use this JS:
.directive('monitorStyle', function($timeout) {
return {
link: function(scope, element, attrs) {
function addToScope() {
var updateVariable = attrs.updateVariable.split(',');
var monitorStyle = attrs.monitorStyle.split(',');
for (var i = 0; i < updateVariable.length; i++) {
scope[updateVariable[i]] = $(element).css(monitorStyle[i]);
}
}
addToScope();
angular.element(window).on('resize', function() {
addToScope();
scope.$apply();
});
}
}
})
And apply it like this:
<h2 monitor-style="font-size,line-height" update-variable="headerHeight,headerLineHeight">
This will update the $scope on initialization and on window resizes. You can of course modify it to your own purpose.
Each time the $scope changes you can update other styles like this:
<div ng-style="{'height': headerHeight, 'line-height': headerLineHeight}">

Angular Directive attrs.$observe

I found this Angular Directive online to add a twitter share button. It all seems staright forward but I can't work out what the attrs.$observe is actually doing.
I have looked in the docs but can't see $observe referenced anywhere.
The directive just seems to add the href which would come from the controller so can anyone explain what the rest of the code is doing?
module.directive('shareTwitter', ['$window', function($window) {
return {
restrict: 'A',
link: function($scope, element, attrs) {
$scope.share = function() {
var href = 'https://twitter.com/share';
$scope.url = attrs.shareUrl || $window.location.href;
$scope.text = attrs.shareText || false;
href += '?url=' + encodeURIComponent($scope.url);
if($scope.text) {
href += '&text=' + encodeURIComponent($scope.text);
}
element.attr('href', href);
}
$scope.share();
attrs.$observe('shareUrl', function() {
$scope.share();
});
attrs.$observe('shareText', function() {
$scope.share();
});
}
}
}]);
Twitter
In short:
Everytime 'shareTwitterUrl' or 'shareTwitterText' changes, it will call the share function.
From another stackoverflow answer: (https://stackoverflow.com/a/14907826/2874153)
$observe() is a method on the Attributes object, and as such, it can
only be used to observe/watch the value change of a DOM attribute. It
is only used/called inside directives. Use $observe when you need to
observe/watch a DOM attribute that contains interpolation (i.e.,
{{}}'s). E.g., attr1="Name: {{name}}", then in a directive:
attrs.$observe('attr1', ...). (If you try scope.$watch(attrs.attr1,
...) it won't work because of the {{}}s -- you'll get undefined.) Use
$watch for everything else.
From Angular docs: (http://docs.angularjs.org/api/ng/type/$compile.directive.Attributes)
$compile.directive.Attributes#$observe(key, fn);
Observes an interpolated attribute.
The observer function will be invoked once during the next $digest fol
lowing compilation. The observer is then invoked whenever the interpolated value changes.
<input type="text" ng-model="value" >
<p sr = "_{{value}}_">sr </p>
.directive('sr',function(){
return {
link: function(element, $scope, attrs){
attrs.$observe('sr', function() {
console.log('change observe')
});
}
};
})

Angular ng-blur not working with ng-hide

Using a directive focus-me="inTextModeInput" in a text input
app.directive('focusMe', function($timeout) {
/*focuses on input
<input type="text" focus-me="focusInput">
*/
return {
scope: { trigger: '=focusMe' },
link: function(scope, element) {
scope.$watch('trigger', function(value) {
if(value === true) {
$timeout(function() {
element[0].focus();
scope.trigger = false;
});
}
});
}
};
});
Actually having 2 inputs, both uses focus-me
When i programatically set the value to focus on an input the ng-blur of other is not called.
NOTE : i am also using this in an ng-repeat.
Isolated scope
The blur is called, but you're not seeing that because you've created a directive with an isolated scope. The ng-blur is executed on the $parent scope. You should only use an isolated scope when the directive is implementing re-useable templates.
Two way binding on trigger
The line 'scope.trigger = false' is also setting a different boolean value because it's on a different scope. If you want to assign a value to a variable from a directive you should always wrap the value inside another object: var focus = { me: true } and set it like trigger=focus.me.
A better solution
But I wouldn't set the trigger to false at all. AngularJS is a MVC/MVVM based framework which has a model state for the user interface. This state should be idempotent; meaning that if you store the current state, reload the page and restore the state the user interface should be in the exact same situation as before.
So what you probably need is a directive that
Has no isolated scope (which allows all other directives to work: ng-blur, ng-focus, ...)
Keeps track of a boolean, which indicates the focus state
Sets this boolean to false when the element has lost focus
It's probably easier to see this thing in action: working plunker.
Maybe this (other) plunker will give you some more insight on scopes and directives.
Code
myApp.directive('myFocus', function($parse, $timeout) {
return {
restrict: 'A',
link: function myFocusLink($scope, $element, $attrs, ctrls) {
var e = $element[0];
// Grab a parser from the provided expression so we can
// read and assign a value to it.
var getModel = $parse($attrs.myFocus);
var setModel = getModel.assign;
// Watch the parser -- and focus if true or blur otherwise.
$scope.$watch(getModel, function(value) {
if(value) {
e.focus();
} else {
e.blur();
}
});
function onBlur() {
$timeout(function() {
setModel($scope, false);
});
}
function onFocus() {
$timeout(function() {
setModel($scope, true);
});
}
$element.on('focus', onFocus);
$element.on('blur', onBlur);
// Cleanup event registration if the scope is destroyed
$scope.$on('$destroy', function() {
$element.off('focus', onFocus);
$element.off('blur', onBlur);
});
}
};
});

Angular.js -- Directive to controller communication

I am very new to angular so please excuse my lack of understanding.
I have a directive called "draggable" which I want to be able to track the x position of and perform some logic on it in the controller. When the user drags the element (a stick figure) to the right, additional stick figures should appear directly behind it. The controller should know the x position and based upon where it is, increment a counter which will dictate how many stick figures appear behind the draggable element.
This code does not currently work as the controller does not have receive the value of x.
My directive:
app.directive('draggable', function() {
return {
restrict: 'A',
scope: "=x",
link: function (scope, element, attrs) {
$(element).draggable({
containment: "parent",
axis: "x",
drag: function(){
scope.x = $(this).offset().left;
}
});
}
};
});
My controller:
app.controller("main-controller", function($scope) {
$scope.range = function(n) {
return new Array(figures);
};
$scope.$watch("x", function(){
console.log($scope.x);
figures = x / (stick_figure_height)
});
});
My HTML:
<div class="human-slider" ng-controller="main-controller">
<div draggable class="human-draggable">
<img src="images/stickfigure.png"/>
</div>
<div ng-repeat="i in range()">
<img src="images/stickfigure.png"/>
</div>
</div>
The reason the controller was not picking up the updated value of x from the draggable directive was because of where the value of x is being updated. X is updated in a turn that has been created in a method outside of the angularJS library (the drag event handler). The solution to this problem was to use $.apply which will update the binding.
The updated code:
// Create our angular app
var app = angular.module('celgeneApp',[]);
// Main controller
app.controller("main-controller", function($scope) {
$scope.x = 0;
$scope.figures = 0;
$scope.range = function(n) {
return new Array($scope.figures);
};
$scope.$watch('x', function(){console.log($scope.x);});
});
// Draggable directive
app.directive('draggable', function() {
return {
restrict: 'A',
scope: false,
link: function (scope, element, attrs) {
$(element).draggable({
containment: "parent",
axis: "x",
drag: function(){
// Need to use $apply since scope.x is updated
// in a turn outside a method in the AngularJS library.
scope.$apply(function(){scope.x = element.offset().left;});
}
});
}
};
});
You can communicate between a directive and a controller through a service. A directive can also access a controller's scope variables via parameters. You can access the variables in different ways, depending on your needs:
As just text with the # prefix
With a one way binding with the & prefix
With a two bay binding with the = prefix
Check out this excellent article about directives, especially the scope section
Take a look at this directive I made, it is just a wrapper around jQuery's draggable just like yours, maybe you can get some ideas:
angular-draggable
Check my this for how parent controller and directive communicates :)
http://plnkr.co/edit/GZqBDEojX6N87kXiYUIF?p=preview plnkr

Resources