AngularJS observe directive attribute expressions, inherit scope dynamically - javascript

My code is as simple:
.controller('Ctrl', ['$scope', '$timeout', function ($scope, $timeout) {
$timeout(function () {
$scope.x = 5;
}, 2000);
}])
.directive('ngHey', ['$parse', function ($parse) {
return {
'restrict': 'A',
'scope': true,
'link': function($scope, el, atr) {
var go = function () {
if ($parse(atr.ngHey)()) {
alert('oiiiiiii');
}
};
atr.$observe('ngHey', function (val) {
if (val) {
go();
}
});
}
};
}]);
//view.html
<div ng-controller="Ctrl">
<span ng-hey="x > 3"></span>
</div>
I would like to be able to fire when directive expression changes and when it's true or false, but at the moment the alert never happen...
It works only if i do something like:
<div ng-controller="Ctrl">
<span ng-hey="{{x > 3}}"></span>
</div>
which is not what i would like, i would like the directive to execute expressions as for ng-if or ng-hide etc...
Any tip or help appreciated,
thanks

You can't use $observe in this case, as it Observes an interpolated attribute. (documentation). In this case you can use $watch on the scope like this:
.directive('ngHey', ['$parse',
function($parse) {
return {
scope: true,
link: function($scope, el, atr) {
var go = function(value) {
if (value) {
alert('oiiiiiii');
}
};
$scope.$watch(atr.ngHey, function(val) {
if (val) {
go(val);
}
});
}
};
}
]);
Demo: http://plnkr.co/edit/XakjA2I7lpJdo9YAZFEH?p=preview
UPD. Based on the OP's comments, updated directive looks like:
.directive('ngHey', ['$parse',
function($parse) {
return {
scope:{ngHey: '='},
link: function($scope, el, atr) {
var go = function(value) {
if ($scope.ngHey) {
alert('oiiiiiii');
}
};
$scope.$watch('ngHey', function(val) {
if (val) {
go();
}
});
}
};
}
]);
Note, how you can use $scope.ngHey in this case, not need to $eval attribute.
Demo: http://plnkr.co/edit/XakjA2I7lpJdo9YAZFEH?p=preview

JSFIDDLE DEMO
As the $timeout is setting the value of x later, the condition inside the directive checking for attribute was returning false always. So use $watch to check the condition in go() whenever x changes.
var myApp = angular.module('myApp',[]);
myApp.directive('ngHey', function () {
return {
'restrict': 'A',
'scope': true,
'link': function($scope, el, attr) {
var go = function () {
if ($scope.$eval(attr.ngHey)) {
alert('oiiiiiii');
}
};
$scope.$watch('x', function (val) {
if (val) {
go();
}
});
}
};
});
Instead of $parse use $scope.$eval and also similarly instead of $observe use $watch.

Related

$watch on array in service not working in directive

I am trying to watch when an array in a service is updated. I update this array in the service using a function in a directive based controller. Now for some reason the watch function does not get called in the link function of the second directive. Why is watch not being called in the second directive. I am trying to update the scope of a variable in the second directive so that it updates when the first directive function updates the service.
The Service
var productServices = angular.module('productServices', ['ngResource']);
productServices.factory('PlayerListS', [function() {
var playerList = [];
function getList() {
console.log(playerList);
return playerList;
}
function addToList(name) {
playerList.push(name);
}
return {
addToList :addToList,
getList: getList
}
}]);
The Directives
'use strict';
bands.directive("player",['PlayerListS', function (PlayerlistS) {
return {
restrict: 'E',
scope: {
person:'#person',
add:'&add'
},
replace: false,
templateUrl: "partials/player.html",
controller: function($scope, $element, $compile) {
$scope.playerList = ["A", "B"];
$scope.add = function(name) {
PlayerlistS.addToList(name);
PlayerlistS.getList();
}
},
link: function(scope, el, attrs) {
}
};
}]);
bands.directive("playerList", ['PlayerListS', function (PlayerlistS) {
return {
restrict: 'E',
replace: false,
template: "<p>Test</p>",
controller: function($scope, $element, $compile) {
},
link: function($scope, $el,$attrs) {
console.log('added');
var x = PlayerlistS.getList()
/*THIS IS WHERE THE WATCH IS HAPPENING*/
$scope.$watch('x', function (newVal, oldVal) {
console.log("CHANGED");
}, true);
}
};
}]);
The Controller
var bands = angular.module('bands', []);
bands.controller('ViewHousesCtrl', ['$scope', '$element', '$routeParams', '$q',
function ViewHousesCtrl($scope, $element, $routeParams, $q) {
$scope.playerLis = ["A","B","C"];
}]);
HTML
<player ng-show="true" person="RandomName" add="add()"></player>
<player-list ng-show="true" ng-repeat="a in playerLis"></player-list>
What your watcher is really doing, is trying to watch a variable called x on the directive scope. But your variable x is just a regular local variable, so your watcher doesn't trigger. So what your watcher basically translates to is this:
$scope.$watch(function(scope){
return scope['x'];
}, function (newVal, oldVal) {
console.log("CHANGED");
}, true);
You can probably see why it doesn't trigger. There is no variable $scope.x. Instead you should try watching the service directly, by specifying the watch function. Like this:
$scope.$watch(function(){
return PlayerlistS.getList();
}, function (newVal, oldVal) {
console.log("CHANGED");
}, true);
You have a spelling mistake in your HTML, it should be:
<player-list ng-show="true" ng-repeat="a in playerList"></player-list>

Proper "Angular" way to pass behavior to directive?

When looking for information regarding Angular directives and passing behavior to directives, I get ended up being pointed in the direction of method binding on an isolate scope, i.e.
scope: {
something: '&'
}
The documentation for this functionality is a bit confusing, and I don't think it'll end up doing what I want.
I ended up coming up with this snippet (simplified for brevity), that works by passing a scope function in HomeCtrl, and the directive does it's work and calls the function. (Just incase it matters, the real code passes back a promise from the directive).
angular.module('app', []);
angular.module('app')
.directive('passingFunction',
function() {
var changeFn,
bump = function() {
console.log('bump() called');
internalValue++;
(changeFn || Function.prototype)(internalValue);
},
internalValue = 42;
return {
template: '<button ng-click="bump()">Click me!</button>',
scope: {
onChange: '<'
},
link: function(scope, element, attrs) {
if (angular.isFunction(scope.onChange)) {
changeFn = scope.onChange;
}
scope.bump = bump;
}
};
})
.controller('HomeCtrl',
function($scope) {
$scope.receive = function(value) {
console.log('receive() called');
$scope.receivedData = value;
};
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.4/angular.min.js"></script>
<div ng-app="app" ng-controller="HomeCtrl">
<passing-function on-change="receive"></passing-function>
<p>Data from directive: {{receivedData}}</p>
</div>
Is this a proper "Angular" way of achieving this? This seems to work.
What you need is to pass the function to the directive. I'll make a very small example.
On controller:
$scope.thisFn = thisFn(data) { console.log(data); };
In html:
<my-directive passed-fn="thisFn()"></my-directive>
On directive:
.directive('myDirective', [
() => {
return {
restrict: 'E',
scope: {
passFn: '&'
},
template: '<div id="myDiv" ng-click="passFn(data)"></div>',
link: (scope) => {
scope.data = "test";
}
}
}]);

Calling a Directive from another Directive

Please consider this tryout on Plunkr.
I have a simple set up:
<body ng-app="myApp">
<div ng-controller="myController">
<parent-directive></parent-directive>
<child-directive></child-directive>
</div>
</body>
With the parent directive defined like:
app.directive("parentDirective", [
"$compile",
function (
$compile) {
return {
scope: {
person: "="
},
restrict: "E",
template: "<h3>I'm a parent</h3>",
controller: [
"$scope",
function ($scope) {
// --- PRIVATE --- //
var self = {};
$scope.ClickMe = function() {
alert('Parent clicked');
};
}],
link: function ($scope, $elem, $attrs) {
}
};
}]);
And child directive defined like:
app.directive("childDirective", [
"$compile",
function (
$compile) {
return {
scope: {
person: "="
},
restrict: "E",
require: "^?parentDirective",
template: "<h3>I'm a child, click <button ng-click='ClickMe()'>here</button></h3>",
controller: [
"$scope",
function ($scope) {
// --- PRIVATE --- //
var self = {};
$scope.ClickMe = function() {
alert('child clicked');
$scope.parentDirective.ClickMe();
};
}],
link: function ($scope, $elem, $attrs) {
}
};
}]);
The child click is handled, but the click defined on the `parent', returns undefined:
TypeError: Cannot read property 'ClickMe' of undefined
looking at the console.
Any idea what's going wrong?
Any idea what's going wrong?
You cannot require a sibling directive.
The required directives controller methods dont get exposed automagically onto your scope.
You should expose methods on the controller itself, not on the assigned $scope.
You can require a directive that is defined on the same element as the requiring directive, or on a parent element.
<child-directive parent-directive></child-directive>
<parent-directive>
<child-directive></child-directive>
</parent-directive>
When you require the controller (aka. exposed API) of another directive, it doesn't magically end up on the $scope of the requiring directive.
It does however end up in your link function as the fourth argument.
Like so:
.directive('child', function () {
return {
require: '?^parentDirective',
link: function (scope, el, attrs, parentDirectiveController) {
scope.clickMe = function () {
parentDirectiveController.clickMe();
};
}
};
});
Expose the methods you want available in other directives onto this instead of $scope, as the $scope way of doing it won't work the way you intend it to when you have isolated scopes.
.directive('parent',
controller: function () {
this.clickMe = function () {};
}
}
To get your example working;
<parent>
<child></child>
</parent>
.directive('parent', function () {
return {
controller: function () {
this.clickMe = function () {};
}
};
}
.directive('child', function () {
return {
require: '^?parent',
link: function (scope, el, attrs, parentCtrl) {
scope.clickMe = function () {
parentCtrl.clickMe();
};
} 
};
});
Simplified (& working) version of your plunker: http://plnkr.co/edit/nao4EvbptQm7gDKkmZS2?p=preview
Put your child directive in your parent directive template. Then use $scope.$parent.ClickMe(). Here's what it would look like.
Simple setup:
<body ng-app="myApp">
<div ng-controller="myController">
<parent-directive></parent-directive>
</div>
</body>
Parent directive:
app.directive("parentDirective", [
function () {
return {
scope: {},
restrict: "E",
template: "<h3>I'm a parent</h3><child-directive></child-directive>",
controller: [
"$scope",
function ($scope) {
$scope.ClickMe = function() {
alert('Parent clicked');
};
}
]
};
}
]);
Child directive:
app.directive("childDirective", [
function () {
return {
restrict: "E",
template: "<h3>I'm a child, click <button ng-click='ClickMe()'>here</button></h3>",
controller: [
"$scope",
function ($scope) {
$scope.ClickMe = function() {
alert('child clicked');
$scope.$parent.ClickMe();
};
}
]
};
}
]);
I might be thinking about your problem a little differently but I would take a look at $broadcast. The idea is you can broadcast an event and have "n" number of directives in your case listening for that event.
http://plnkr.co/edit/wBmX2TvC3yMXwItfxkgl
brodcast:
$scope.ClickMe = function() {
alert('child clicked');
$rootScope.$broadcast('child-click');;
};
listen:
$scope.$on('child-click', function (event, args) {
alert('Parent clicked');
});

How to call my own function inside an Angular Directive?

I'd like to call "myFunc()" inside my angular directive, how might I do this?
myApp.directive("test", function () {
return {
restrict: 'A',
template: "<div class='box'></div>",
myFunc: function() {
console.log('myFunc');
},
link: function ($scope, element, attrs) {
element.bind('click', function () {
myFunc(); //<------------- doesn't work
});
}
} // of return
});
You can't define the function as a property of your return value in your call to directive. It either needs to be defined before your return:
myApp.directive('test', function() {
var myFunc = function() {
console.log('myFunc');
};
return {
restrict: 'A',
template: '<div class="box"></div>',
link: function($scope, element, attrs) {
element.bind('click', myFunc);
}
};
};
Or the same way inside of your link function.
Just to play around :)
var app = angular.module('myApp', []);
app.controller('MainController',function() {
});
app.directive('one', function() {
return angular.extend({}, app.directive, {myfunct:function(){
alert('hello');
}});
});
app.directive('two', function(oneDirective) {
return {
link:function($scope,$element){
console.log(oneDirective[0].myfunct)
$element.on('click',oneDirective[0].myfunct);
}
};
});
or use the method binding "&":
app.directive('myDir', function() {
return {
scope: {
callback: "&"
},
link:function($scope,$element){
element.bind('click', function () {
$scope.evalAsync(function() {
$scope.callback({param1: value, param2: value2});
})
});
}
};
});
Usage:
<my-dir callback="myControllerMethod(param1, param2)"></my-dir>

How to implement a disable when loading directive

Hi I have a variable on my scope named loadingdata. It will have the values true or false to determine if data is loading or not. I would like to put an attribute on an element to disable it if data is loading. Here is the code I already have but it is not working:
module.directive('disableWhenLoadingData', function () {
return {
restrict: 'A',
scope: {},
link: function ($scope, element, attrs) {
$scope.$watch('loadingData', function(newValue, oldValue) {
element.attr('disabled', newValue);
});
}
};
});
any ideas
You can use Angular's own ngDisabled directive instead of writing your own.
Service:
module.factory('GetDataService', function ($http) {
return {
getCustomers: function() {
return $http({ url: '/someurl', method: 'GET'});
}
}
});
Directive:
module.directive('disableWhenLoadingData', function (GetDataService) {
return {
restrict: 'A',
scope: {},
link: function ($scope, element, attrs) {
$scope.loadingData = true;
GetDataService.getCustomers().success(function (data) {
$scope.loadingData = false;
});
}
};
});
Generally I set $scope.loading in my controller, and my button or whatever i set ng-disabled.
In my controller:
$scope.loadData = function () {
$scope.loading = true;
$http
.get('url')
.success(function (ret) {
$scope.loading = false;
});
}
In my view:
<button ng-disabled="loading" ng-click="loadData()">{{loading? 'loading Data' : 'Submit'}}</button>

Categories