understanding directive angular - angularjs

everyone.
I am not experienced with angular, so help needed.
i have a directive :
app.directive('ngDirective', [ '$timeout', function ($timeout) {
return {
templateUrl: '../tpl/tpl.html',
restrict: 'E',
scope:{
item:'=',
},
link:function(scope, element, attrs) {
//some func here
scope.myFunction =function(item){
$(element).find('.myitem').css('-webkit-transform','scale(0.6)').animate({opacity:0},function(){
$timeout(function(){
var ListIndex = scope.$parent.$index;
scope.$parent.$parent.ItemsList.splice(ListIndex, 1);
scope.$parent.$parent.updateSomeStuff();
});
})
};
}
};
}]);
the problem is that scope.myFunction fires jQuery changes twice, first at the element, and then at its sibling at the list;at the same time it only deletes one element from list. if i remove parent scope functionality-jQuery works fine and fires one single time.if i remove jquery line -parent scope func works perfect, how i need to organize this correctly?
i think that deleting from a list somehow binds jquery to run again, but i have no understanding of the process...
What i am missing?

Related

Not able to access angular directive's isolated scope

First Directive:
app.directive("myDirectiveOne", function($rootScope){
return {
templateUrl : "/custom-one-html.html",
restrict: "AE",
replace:true,
scope: {
somedata: "=",
flags: "=",
functionone: "&"
}
,controller: function($rootScope,$scope, $element) {
$scope.firstFunction = function(){
console.log("First function is getting called")
}
$scope.$on('firstBroadcast',function(event, data){
$rootScope.$broadcast('secondBroadcast', data)
});
}
Second Directive:
app.directive("myDirectiveTwo", function($rootScope){
return {
templateUrl : "/custom-two-html.html",
restrict: "AE",
replace:true,
scope: {
data: "=",
functiontwo: "&"
}
,controller: function($rootScope,$scope, $element) {
$scope.secondFunction = function(){
console.log("Second function is getting called")
$rootScope.$broadcast('firstBroadcast', {})
}
$scope.$on('secondBroadcast',function(event, data){
$scope.callSomeFunctionWithData(data);
});
$scope.secondFunction();
$scope.editFunction = function(x){
console.log("This is the edit function", x);
}
Parent Controller:
$scope.parentFuntion = function(){
console.log("No trouble in calling this function")
}
So, the problem is when I try calling a function from a myDirectiveTwo html template, the controller which is active is the parent controller and not the isolated one.
May be it has something to do with the broadcasts I am using?
Html Code:
<div ng-repeat="x in data">
<h5>{{x.name}}</h5>
<button ng-click="editFunction(x)">Edit</button>
</div>
The strange thing is I get data values and ng-repeat works fine on load. But, when I click on the button it doesnt do anything. And if I add the same function in the parent controller, it gets called.. :(
How do I make the isolated scope controller active again..?
The problem is that ng-repeat creates a child scope, therefore the editFunction ends up being on the parent scope.
From docs
... Each template instance gets its own scope, where the given loop variable is set to the current collection item ...
Docs here
You can verify that this is the issue by getting your button element's scope and checking the $parent, as such angular.element(document.getElementsByTagName("button")).scope()
Although considered code smell, you can append $parent to your function call in order to access it, though keep in mind this now places a dependency on your HTML structure.
<button ng-click="$parent.editFunction(x)">Edit</button>
The issue was that I was using a deprecated method replace:true. This caused the unexpected scenarios.. As #Protozoid suggested, I looked at his link and found the issue.. To quote from the official documentation:
When the replace template has a directive at the root node that uses transclude: element, e.g. ngIf or ngRepeat, the DOM structure or scope inheritance can be incorrect. See the following issues: Incorrect scope on replaced element: #9837 Different DOM between template and templateUrl: #10612
I removed replace:true and its fine now :)
This is the link:
Here

communicating between main controller and directive/ directive controller

I have a main controller in which i want to emit/ broadcast an event
main controller
.controller('gameCtrl', function(){
function moveToTileBy(moves)
{
var numberOfTiles = ctlr.board.tiles.length,
player = getCurrentPlayer(),
currentTileNumber = player.currentPositionTileNumber;
if((moves + currentTileNumber) > numberOfTiles)
{
// alert player not enough moves
return nextTurn();
}
// create a drag and drop
$scope.$emit('movePlayer', currentTileNumber);
$scope.$emit('activateTile', moves + currentTileNumber);
}
})
I also have a directive on an ng-repeatitems, each item has an isolated scope whose only connection to the main controller is the scope's model
directive
.directive('phTile', ['$rootScope', 'Drake', function ($rootScope, Drake) {
return {
restrict: 'A',
scope: {
tile: '#ngModel'
},
link: function (scope, element, attr) {
var elem = element[0];
console.log(scope.tile);
$rootScope.$on('movePlayer', function(){
console.log('root link move player ', arguments);
});
scope.$on('movePlayer', function(){ console.log('link scope move player', arguments);})
}
};
html
<div ng-controller="gameCtrl as ctlr">
<div ng-repeat="(i, tile) in ctlr.board.tiles" class="w3-col tile tile-{{tile.number}}" ng-repeat-end-watch="ctlr.resize()" ng-model="tile" ph-tile><span>{{tile.number}}</span></div>
</div>
I want the above event in the controller to trigger a series of dom manipulations to 1 or more item DOM elements
The dilemma i am facing is that I am not sure how/ where to build the logic to listen to the said event and proceed with the logic for dom manipulation....
Should it be
1) In a directive controller
return {
restrict: 'A',
scope: {
tile: '=ngModel'
}, controller: function($scope){ $scope.$on('move', function(){ /* manipulate dom element */}); }
2) Or in the link
return {
restrict: 'A',
scope: {
tile: '=ngModel'
}, link: function(scope, element, attr){ scope.$on('move', function(){ /* manipulate dom element */}); }
In addition I need to access the isolated scope's "tile" object and the directive's "element" in order to proceed with the dom manipulation.
It looks like you missed finishing submitting your question, but, in a nutshell, the manipulation of DOM elements should be in link.
Based on what you are starting to write at the bottom ('In addition I need to access the scope's "tile" object and "element" in order to proceed with the'), having the full directive and html, preferably in a demo, would help myself or somebody troubleshoot. I will update this if more information is provided.
Mark Rajcok has done an excellent job explaining the differences between links and controllers here: Difference between the 'controller', 'link' and 'compile' functions when defining a directive

Adding ngDisabled inside directive

I'm trying to "overload" all inputs in my application. And in doing so, I'd also like to have ngDisabled used based upon a flag in the directive's scope.
Here's what I got and where I'm stuck is getting the ng-disabled to work on the element. I'm guessing I need to re-compile the element or something after I modify it? I'm also calling the directive by using the object notation:
angular.module("MimosaApp").directive({
"textarea": appInputs,
"input": appInputs
});
var appInputs = function($compile, Device) {
return {
restrict: 'E',
require: '?ngModel',
priority: 101,
template: function(tElement, tAttrs) {
tElement.attr("ng-disabled", 'isDisabled');
return tElement;
},
link: function($scope, element, attrs) {
$compile(element);
element.on("focus", function() {
console.log($scope);
})
},
controller: function($scope, $element) {
$scope.isDisabled = true;
console.log($scope);
}
}
};
What I'm seeing is... nothing is disabled even though I set isDisabled to true in the scope. What am I missing?
Update 1
Ok, so maybe I do need to clarify it a bit. When a user interacts with an input of some kind, I currently have a message being sent back to the server and then sent out to all the other connected clients. This way the user's view changes based upon another user's interactions.
To take advantage of Angular better, I was thinking of trying to use the angular ngDisabled directive. When a user focuses an element, other users would see the element get disabled.
I currently keep track of a 'global' UI state on the server and send this JSON object out to the clients which then update themselves. So I was hoping to have elements get disabled (or other CSS classes) based upon a scope flag (Or other behavior). Something like $scope.fieldsDisabled[fieldName] and set it to true/false.
Maybe I'm thinking about it wrong by going the directive way.
This making any sense? haha
In the directive life cycle template function gets called before compile so ideally it should work fine because you are setting the attribute inside template function. Can you try changing the attribute inside the compile function. Something like this.
var appInputs = function($compile, Device) {
return {
restrict: 'E',
require: '?ngModel',
priority: 101,
compile: function(tElement, tAttrs) {
tElement.attr("ng-disabled", 'isDisabled');
return function($scope, element, attrs) {
element.on("focus", function() {
console.log($scope);
});
}
},
controller: function($scope, $element) {
$scope.isDisabled = true;
console.log($scope);
}
}
};

Angularjs detecting routeChangeStart from directive inside a template

I've got a pretty standard directive that lives on an anchor element, which parses a string to see whether the current route matches that link, e.g.
Dashboard
This directive runs each time the route changes (as the links can live outside of the ng-view which changes, so their state needs to be refreshed on a route change), using $routeChangeStart. This works fine within my main navigation, which lives within a standard view, but if I use this directive within an ng-included file (like my subnavigations), it fails to run any code inside the routeChangeStart callback. I've tried injecting $rootScope instead, but it makes no difference. The directive is as follows:
angular.module('myApp').directive('navItem', ['$rootScope','$location', function ($rootScope, $location) {
return {
restrict: 'A',
scope: false,
link: function postLink(scope, element, attrs) {
console.log('All directive elements execute this!');
$rootScope.$on('$routeChangeStart', function() {
console.log('ng-included elements work execute this!');
});
}
}
}]);
How can I get access to this event from inside a directive within an ng-include template? The directive runs, but just won't pick this up.
Thanks
I've been trying to recreate it on plunker but it works for me
http://plnkr.co/edit/9hbTLxGjoTNsM44zq6rw?p=preview
app.directive('navItem', ['$rootScope','$location', function ($rootScope, $location) {
return {
restrict: 'A',
scope: false,
link: function postLink(scope, element, attrs) {
console.log('All directive elements execute this!');
$rootScope.$on('$routeChangeStart', function() {
console.log('ng-included elements work execute this!');
});
}
}
}]);
check my plunker maybe you can find a difference between yours and mine code

Angularjs passing object to directive

Angular newbie here. I am trying to figure out what's going wrong while passing objects to directives.
here's my directive:
app.directive('walkmap', function() {
return {
restrict: 'A',
transclude: true,
scope: { walks: '=walkmap' },
template: '<div id="map_canvas"></div>',
link: function(scope, element, attrs)
{
console.log(scope);
console.log(scope.walks);
}
};
});
and this is the template where I call the directive:
<div walkmap="store.walks"></div>
store.walks is an array of objects.
When I run this, scope.walks logs as undefined while scope logs fine as an Scope and even has a walks child with all the data that I am looking for.
I am not sure what I am doing wrong here because this exact method has worked previously for me.
EDIT:
I've created a plunker with all the required code: http://plnkr.co/edit/uJCxrG
As you can see the {{walks}} is available in the scope but I need to access it in the link function where it is still logging as undefined.
Since you are using $resource to obtain your data, the directive's link function is running before the data is available (because the results from $resource are asynchronous), so the first time in the link function scope.walks will be empty/undefined. Since your directive template contains {{}}s, Angular sets up a $watch on walks, so when the $resource populates the data, the $watch triggers and the display updates. This also explains why you see the walks data in the console -- by the time you click the link to expand the scope, the data is populated.
To solve your issue, in your link function $watch to know when the data is available:
scope.$watch('walks', function(walks) {
console.log(scope.walks, walks);
})
In your production code, just guard against it being undefined:
scope.$watch('walks', function(walks) {
if(walks) { ... }
})
Update: If you are using a version of Angular where $resource supports promises, see also #sawe's answer.
you may also use
scope.walks.$promise.then(function(walks) {
if(walks) {
console.log(walks);
}
});
Another solution would be to add ControllerAs to the directive by which you can access the directive's variables.
app.directive('walkmap', function() {
return {
restrict: 'A',
transclude: true,
controllerAs: 'dir',
scope: { walks: '=walkmap' },
template: '<div id="map_canvas"></div>',
link: function(scope, element, attrs)
{
console.log(scope);
console.log(scope.walks);
}
};
});
And then, in your view, pass the variable using the controllerAs variable.
<div walkmap="store.walks" ng-init="dir.store.walks"></div>
Try:
<div walk-map="{{store.walks}}"></div>
angular.module('app').directive('walkMap', function($parse) {
return {
link: function(scope, el, attrs) {
console.log($parse(attrs.walkMap)(scope));
}
}
});
your declared $scope.store is not visible from the controller..you declare it inside a function..so it's only visible in the scope of that function, you need declare this outside:
app.controller('MainCtrl', function($scope, $resource, ClientData) {
$scope.store=[]; // <- declared in the "javascript" controller scope
ClientData.get({}, function(clientData) {
self.original = clientData;
$scope.clientData = new ClientData(self.original);
var storeToGet = "150-001 KT";
angular.forEach(clientData.stores, function(store){
if(store.name == storeToGet ) {
$scope.store = store; //declared here it's only visible inside the forEach
}
});
});
});

Resources