Directive child element no scope update - angularjs

I'm trying to update the attribute of a child element of my directive by hovering over one of the other child elements but the scope is not updated. I've referred to What are the nuances of scope prototypal / prototypical inheritance in AngularJS?, but can't figure out why it won't work:
I've tried adding scope.$apply() since it's updated inside the click event, but doesn't seem to work wherever I placed it.
Template:
<div ng-controller="DashboardCtrl">
<svg country>
<path province class="province" id="{{$index}}" ng-attr-d="{{p.d}}" ng-repeat="p in paths">
</path>
<use id="use" ng-xlink-href="#{{current.province}}" />
</svg>
</div>
JS:
angular.module('dashboard').controller("DashboardCtrl", ["$scope", function($scope) {
$scope.paths = [{..snip..}]
}]);
angular.module("map-directive", [])
.directive("country",function () {
return {
restrict: "A",
scope: {},
controller: function ($scope) {
$scope.current = {};
this.hovered = function (province_id) {
$scope.current.province = province_id;
}
}
}
}).directive("province", function () {
return {
require: "^country",
restrict: "A",
link: function (scope, element, attrs, countryCtrl) {
element.on("mouseover", function () {
countryCtrl.hovered(attrs.id);
})
}
}
})

Part of the problem I can see right away is that you use ng-repeat, this creates a new scope and won't inherit from the parent scope - which is what you are hoping for.
Pass in an isolate scope value so the directive province so it will receive the value of id.
.directive("province", function () {
return {
require: "^country",
scope: {
id: '#'
},
restrict: "A",
link: function (scope, element, attrs, countryCtrl) {
element.on("mouseover", function () {
countryCtrl.hovered(scope.id);
})
}
}
})
But I am not 100 percent sure it will be passed as the id is wrapped in {{}} and needs to be evaluated first before it can be passed in, but maybe as it's only done with mouseover it will work. Otherwise wrap it in a $timeout - which is kinda hacky - but then u can see what is happening.

Related

Get element scope from attribute directive

I'm trying to extend functionality of any directive by simply attaching an attribute directive, but I'm having trouble getting the scope of the element on which the attribute is defined.
For example, I have this template:
<div class="flex-item-grow flex-item flex-column report-area">
<sv-report sv-reloadable id="reportId"></sv-report>
</div>
Here, sv-reloadable has some implicit understanding of sv-report, but sv-report has no idea about sv-reloadable.
I've defined sv-reloadable as:
angular
.module( 'sv-reloadable', [
'sv.services',
])
.directive('svReloadable', function(reportServices, $timeout) {
return {
restrict: 'A',
controller: function($scope, $timeout) {
$scope.$on('parameter-changed', function(evt, payload) {
evt.stopPropagation();
$scope.viewModel = getNewViewModel(payload);/* hit the server to retrieve new data */
});
}
};
});
Now, $scope in sv-reloadable is the parent scope of sv-report. I'm wanting sv-reloadable to be able to attach a listener to sv-report's scope, and swap out properties of that scope. I understand that it's possible to grab the sibling scopes, but that causes problems when trying to figure out exactly which element it's attached to.
I attempted the following:
link: function(scope, element, attrs) {
ele = element;
var actualScopyThingy = element.scope();
},
Which I had assumed would give me the scope of the element the attribute was defined on, but alas, it still returns the parent scope of the element.
If it's important, sv-report is defined as the following, but I'd like to be able to keep it the same (since sv-reloadable is going to be attached to many different elements, all of which must have viewModel defined on their scope)
return {
restrict: 'E',
replace: true,
templateUrl: 'sv-report/sv-report.tpl.html',
scope: {
id: '=',
reportParameters: '='
},
controller: function ($scope, svAnalytics) {
/* unrelated code here */
},
link: function(scope, element, attrs) {
initialLoadReport(scope);
}
};
After a bit of digging around, isolateScope() is what I was after (rather than scope()). sv-reloadable's directive becomes:
return {
restrict: 'A',
link: function(scope, element, attrs) {
var elementScope = element.isolateScope();
elementScope.$on('parameter-changed', function(evt, payload) {
...
});
}
};

Define button click listener within isolate scope in angularjs

I want to create a directive as a component, such that its not dependent on any controllers as such.
I have been trying to find out how to get a button click listener defined. But couldnt suceed yet.
angular.module('nestedDirectives', [])
.directive("parent", function () {
function linker(scope, element, attribute, controllers) {
console.log("linker called");
element.on("click", function clicked(event) {
console.log("clicked");
console.dir(this);
element.prepend("<h1>Hello</h1>");
});
}
return {
restrict: 'A',
template: '<div><h5>An Item</h5><button ng-click="clicked()">Click Me</button></div>',
link: linker,
scope: {}
}
})
In the template, no matter what i click the element.on("click") would get called. I want to call a clicked() method when button is clicked.
Here is the Plunker for the same.
The link function gets the scope (an isolated scope in your case) as the first argument, so you can do something like:
.directive("parent", function () {
function linker(scope, element, attribute, controllers) {
console.log("linker called");
//add the "clicked" function to your scope so you can reference with ng-click="clicked" in your template
scope.clicked = function() {
console.log("clicked");
console.dir(this);
element.prepend("<h1>Hello</h1>");
};
}
return {
restrict: 'A',
template: '<div><h5>An Item</h5><button ng-click="clicked()">Click Me</button></div>',
link: linker,
scope: {}
};
});
Here is your updated plunkr http://plnkr.co/edit/7mlcSB4phPO5EdEQqTj0

Accessing parent "isolated" scope from child directive

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

How do you not override other angular directives when using isolate scope and still being able to call methods in the scope?

I have an anchor tag that I wish to hide or show depending on a value in the model.
<table>
<tr ng-repeat="item in items">
<td>Other Stuff</td>
<td>
<a href="#/somewhere" ng-show="model.showIt" myCustomDir="some value" onClick="bar(item)" />
</td>
</tr>
</table>
Now in my directive I have the following:
app.directive('myCustomDir', function() {
var def = {
restrict: 'A',
scope: {
onClick: "&"
},
link: function(scope, element, attrs) {
var hover = angular.element("<div><b>Some Text</b></div>");
var button = hover.find('b');
button.on('click', function() {
scope.$apply(function() {
scope.onClick();
})
});
}
};
return def;
})
The problem is as soon as I include my directive the ng-show one I think no longer works and that is because if I am correct it is because my directive works in isolate scope so the model from the parent scope is no longer present.
How would I get my directive to play nicely with ng-show while still being able to let someone what method they want to call when the tag is clicked.
Plunker for all those interested. http://plnkr.co/edit/BLMCgB
You directive creates an isolated scope. So you need to use $parent to get the value of the current repeater item
ng-show="$parent.item.visible"
If you want to make it more generic, you can take the scope off to make it compatible with other directives. Then you can use scope.$eval to call the function passed in.
myApp.directive('myDirective', function ($document) {
var definition = {
restrict: 'A',
link: function (scope, element, attrs) {
element.on('click', function () {
...
button.on('click', function () {
scope.$apply(function () {
scope.$eval(attrs.onClick);
hover.remove();
})
});
});
}
}
return definition;
})
If you want allow any global directive - don't declare private scope.
If you want allow only few directives, add links in scope declaration:
scope: {
onClick: "&",
ngShow : "&"
},
To your question in comments:
Declare controller in directive and declare method in this controller. Then in directive template assign ng-click to this method.
var def = {
restrict: 'A',
controller: function($scope){
$scope.callMe = function(){
console.log('foo');
}
}
}
in template:
<div ng-click="callMe()">content</div>
This method will be accessible only inside your directive.

Two way binding, shallow $watch, isolate scope not working together

Please refer to this fiddle for the questions. http://jsfiddle.net/AQR55/
1) Why a watch that is attached to an isolate scope property - which is bidirectionally bound to a parent property, is not triggering on changing the parent scope property.
In the fiddle, the below metioned watch is not getting triggered, on changing the parent scope property to which it is bound.
$scope.$watch('acts', function(neww ,old){
console.log(neww)
})
2) ng-click="addaction()" addaction="addaction()". Can this code be put in more elegant way? Because, to perform an action in isolated scope, it seems we need to set bidirectional binding and the attach to ng-click.
3)Can i declare methods inside the isolated scope like shown below? If i do like this, I'm getting .js error.
<isolate-scope-creating-cmp ng-click="isolateCmpClickHandler()"></isolate-scope-creating-cmp>
scope:{
isolateCmpClickHandler:function(){
//If i do like this, I'm getting .js error
}
}
Question 1.
Since you are adding a item to the acts array, you need to set the third parameter in $watch() to true
$scope.$watch('acts', function (neww, old) {
console.log(neww)
}, true);
Demo: Fiddle
Question 2.
Since there is an isolated scope, you need to call the $parent scope's function
<input type="button" bn="" acts="acts" ng-click="$parent.addaction()" value="Add Action" />
Demo: Fiddle
Question 3.
Yes you can, but you need to use a controller
animateAppModule.directive('bn', function () {
return {
restrict: "A",
scope: {
acts: '='
},
link: function ($scope, iElement, iAttrs) {
$scope.$watch('acts', function (neww, old) {
console.log(neww)
}, true)
},
controller: function($scope){
$scope.dosomething = function(){
console.log('do something')
}
}
}
})
Demo: Fiddle
An overall solution could look like
<input type="button" bn="" acts="acts" addaction="addaction()" value="Add Action" />
JS
animateAppModule.controller('tst', function ($scope) {
$scope.acts = [];
$scope.addaction = function () {
$scope.acts.push({
a: "a,b"
})
}
})
animateAppModule.directive('bn', function () {
return {
restrict: "A",
scope: {
acts: '=',
addaction: '&'
},
link: function ($scope, iElement, iAttrs) {
$scope.$watch('acts', function (neww, old) {
console.log(neww)
}, true);
iElement.click(function(){
$scope.$apply('addaction()')
})
}
}
})
Demo: Fiddle

Resources