Custom range angular directive with events - angularjs

I'd like to create an range directive to repeat the included html x times.
Like this:
...
.directive('raichuRange', function () {
return{
scope:{
range: '=raichuRange'
},
transclude: 'element',
link : function(scope, element, attrs, ctrl, transcludeFn){
var parent = element.parent();
for(var i = 1;i<= scope.range;++i){
var childScope = scope.$new();
childScope.$index = i;
transcludeFn(childScope, function (clone) {
parent.append(clone);
})
}
}
}
})
The problem with this is: any ng-click directives within don't respond anymore.

Why doesn't this work? https://jsfiddle.net/pq1wkp90/
Because the directive uses an isolate scope, the parent functions are not inherited by the directive scope or their children.
To access the functions on the parent scope of the directive use:
ng-click="$parent.$parent.test($index)"
Use one $parent to access the isolate scope from the transcluded element. The second $parent to access the parent of the isolate scope.
For more information on directive scopes, AngularJS $compile API Reference -- scope.
PS. Also change $scope.$index = $index + 1; to $scope.$index = $scope.$index + 1;

Related

Find closest controller scope

Is there a way to find the closest parent scope inside a directive that is a controller scope?
Use case.
The important part is at line 212 is the js file.
The template I render from the directive has scope expressions passed in the directive attribute and I need to link them to the controller scope.
Because the directive has an isolated scope, I link it to scope.$parent because in this case the parent is the controller.
But this might not always be the case, so how can I find the closest parent controller scope.
Can I loop trough the scope checking if it's a controller scope?
var template = angular.element(html);
var linkFn = $compile(template);
var child = linkFn(scope.$parent); //HERE IS THE PROBLEM
$(element).append(child);
You may try to use jqLite/jQuery methods from within your directive to traverse DOM tree up and locate the required parent (element.parent(), etc). Then you can get the scope object related to this parent by calling scope() method on it, which you should be able to pass as an argument to your compilation function.
It's not the cleanest approach, but you can always pass in an object to the directive's scope.
angular.app('myApp', [])
.controller('myCtrl', [function() {
var self = this;
self.directiveConfig = {
method1: function() { /* do something cool */ },
method2: function() { /* do something cool */ }
};
}])
.directive('myElem', [function() {
var myElem = {
restrict: 'AE',
controller: function() { /* some custom controller */ },
link: function(scope, element, attributes, ctrl) {
/**
* scope.config will now be tied to self.directiveConfig
* as defined in the controller above
*/
},
scope: {
config: '='
}
};
});
Then, assuming your controllerAs is set to ctrl, you could do.
<my-elem config="ctrl.directiveConfig"></my-elem>
A better solution would be to put any functions you need to use like that inside a service that can be reused.
See there are many ways . One of the way could be , you could check for an variable that is defined on that controller scope like this
if(angular.isDefined(scope.$parent.variableName))
{
// controller is defined
}
else
{
// controller is not defined
}
If this is defined then it is your controller , otherwise something else. Second way would be to check parent scope of the parent like this
if(angular.isDefined(scope.$parent.$parent)) {
//controller is defined
}
else
{
//controller is not defined
}
As every controller will have a parent as $rootScope or $scope of some other controller . That means if it is not a controller then this would result in undefined and will go else condition.
requiring ngController seems to be the best solution.
.directive("mobilitTree", function() {
return {
...
require: "ngController",
...
link: function(scope, element, attrs, ngCtrl) {
...
}
};
})
.controller("AppController", function($scope, ...) {
//Only variable applied on this are public
this.controllerOnSort = function(...) { ... };
});

AngularJS - exposing controller api to directive

How do I make controller functions visible to a directive? Do I attach the methods to the scope, and inject the scope into the directive? Is it a good idea in the first place? I want to manipulate model data from within the UI.
It really dependes on what you want to do.
Since you want to access the controller's scope from the directive, I suggest you declare your directive with it's scope shared with the parent controller by setting it's scope prop to false:
app.directive('directiveName', function() {
scope: false,
link: function(scope) {
// access foo from the controler's scope
scope.foo;
}
});
This is a nice example with how directives can be hooked up to a controller
http://jsfiddle.net/simpulton/GeAAB/
DIRECTIVE
myModule.directive('myComponent', function(mySharedService) {
return {
restrict: 'E',
controller: function($scope, $attrs, mySharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'Directive: ' + mySharedService.message;
});
},
replace: true,
template: '<input>'
};
});
HOOKUP IN CONTROLLER
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
VIEW
<my-component ng-model="message"></my-component>
Adding to #grion_13 answer, scope:true would also work since it creates a new scope that is child of the parent scope so has access to parent scope data.
But a true reusable directive is one which get it input data using isolated scope. This way as long as your html+ controller can provide the right arguments to the directive isolated scope, you can use the directive in any view.

Variables in directive isolate scope not available

I'm experiencing some strange results in my code. For example, when you comment out the isolate scope line (scope: {local:'='}), the directive's scope has a local value, yet when you link the two, the local value is clearly sent to the parent scope, yet its deleted in the directive scope?
http://plnkr.co/edit/uBkCmnRQoj4B539nAkeW?p=preview
angular.module('My', [])
.controller('ctrl', function($scope){
$scope.parent = 2;
})
.directive('directive', function(){
return {
scope: { local: "=" }, // comment out this line... wtf?
link: function(scope, el, attr, ctrl){
scope.local = 88;
scope.doIt = function(){
scope.local = 77;
};
}
};
});
and
<body ng-app="My">
<div class="ctrl" ng-controller="ctrl">
<h3>ctrl</h3>
parent: {{parent}}
<div directive local="parent" class="directive">
<h3>directive</h3>
<div>local: {{local}}</div>
<button ng-click="doIt()">click</button>
</div>
</div>
</body>
Also, the ng-click="doIt()" doesn't seem to fire at all.
The problem is not of scope but of how your using the scope variables in your HTML.
By default, the directive's scope is not available in the HTML nested inside of it. You could use transclusion but often times a template is used for what you're doing in the Plunker:
.directive('myDirective', function(){
return {
scope: { local: "=" }, // comment out this line... wtf?
link: function(scope, el, attr, ctrl){
scope.local = 88;
scope.doIt = function(){
scope.local = 77;
};
},
template: '<h3>directive</h3><div>local: {{local}}</div><button ng-click="doIt()">click</button>'
}
})
Forked Plunker
You have two cases here.
When you uncomment the scope portion you get two scopes in your application. One scope is controller scope (ctrl), and second one is scope of div which uses your custom directive. They are completely separate and share no data inherently. Custom directive scope does take value from parent scope, because you set it explicitly (scope: { local: "=" }, local="parent"). When you set local to new value in child, isolated scope, it will also be copied back to original field (parent) because you specified so (local: "="). If you only want local to be set, then tell Angular not to copy value from isolated scope back to parent scope (do this by using # instead of =; scope: { local: "#" })
ngClick directive also doesn't work, because as we said, your custom directive scope is isolated. When you add doIt there, it's not available in controller scope.
When you comment scope: { local: "=" } portion, now your controller and custom directive share same scope. Now doIt function is available.

AngularJS directive transclude part binding

I'd like to use a directive, transclude content, and call directive's controller method within the transcluded part:
<mydirective>
<div ng-click='foo()'>
click me
</div>
</mydirective>
app.directive "mydirective", ->
return {
restrict: 'EACM',
transclude: true
template: "<div ng-transclude></div>"
scope: { } #required: I use two way binding on some variable, but it's not the question here
controller: [ '$scope', ($scope)->
$scope.foo = -> console.log('foo')
]
}
plunkr here.
How can I do that please?
I have a different answer, which is not a hack and I hope it will be accepted..
see my plunkr for a live demo
Here is my usage of the directive
<div custom-directive custom-name="{{name}}">
if transclude works fine you should see my name right here.. [{{customName}}]
</div>
Note I am using customName within the directive and I assign it a value as part of the directive's scope.
Here is my directive definition
angular.module('guy').directive('customDirective', function($compile, $timeout){
return {
template : '<div class="custom-template">This is custom template with [{{customName}}]. below should be appended content with binding to isolated scope using the transclude function.. wait 2 seconds to see that binding works</div>',
restrict: 'AC',
transclude: true,
scope : {
customName : '#'
},
link : function postLink( scope, element, attrs, dummy, transcludeFn ){
transcludeFn( scope, function(clone, innerScope ){
var compiled = $compile(clone)(scope);
element.append(compiled);
});
$timeout( function(){
scope.customName = 'this stuff works!!!';
}, 2000);
}
}
});
Note that I am changing the value on the scope after 2 seconds so it shows the binding works.
After reading a lot online, I understood the following:
the ng-transclude directive is the default implementation to transclusion which can be redefined per use-case by the user
redefining a transclusion means angular will use your definition on each $digest
by default - the transclusion creates a new scope which is not a child of the isolated scope, but rather a sibling (and so the hack works). If you redefine the transclusion process you can choose which scope is used while compiling the transcluded content.. -- even though a new scope is STILL created it seems
There is not enough documentation to the transclude function. I didn't even find it in the documentation. I found it in another SO answer
This is a bit tricky. The transcluded scope is not the child of the directive scope, instead they are siblings. So in order to access foo from the ng-click of the transcluded element, you have to assign foo to the correct scope, i.e. the sibling of the directive scope. Be sure to access the transcluded scope from the link function because it hasn't been created in controller function.
Demo link
var app = angular.module('plunker', []);
app.directive("mydirective", function(){
return {
transclude: true,
restrict: 'EACM',
template: "<div> {{ name }} <br/><br/> <div ng-transclude> </div></div>",
scope: { },
link: function($scope){
$scope.name = 'Should change if click below works';
$scope.$$nextSibling.foo = function(){
console.log('foo');
$scope.name = 'it works!';
}
}
}
})
Another way is assigning foo to the parent scope because both prototypally inherits from the parent scope, i.e.
$scope.$parent.foo = ...
Technically, if you remove scope: { }, then it should work since the directive will not create an isolated scope. (Btw, you need to add restrict: "E", since you use the directive as element)
I think it makes more sense to call actions defined in parent scope from directive rather than call the actions in the directive from parent scope. Directive should be something self-contained and reusable. The actions in the directive should not be accessible from outside.
If you really want to do it, you can try to emit an event by calling $scope.$broadcast(), and add a listener in the directive. Hope it helps.

dynamic directives in angularjs

The directive's attributes don't change when the scope is updated, they still keep the initial value. What am I missing here?
HTML
<ul class="nav nav-pills nav-stacked" navlist>
<navelem href="#!/notworking/{{foo}}"></navelem>
<navelem href="#!/working">works great</navelem>
</ul>
<p>works: {{foo}}</p>
Javascript
(based on angular tabs example on front-page)
angular.module('myApp.directives', []).
directive('navlist', function() {
return {
scope: {},
controller: function ($scope) {
var panes = $scope.panes = [];
this.select = function(pane) {
angular.forEach(panes, function(pane) {
pane.selected = false;
});
pane.selected = true;
}
this.addPane = function(pane) {
if (panes.length == 0)
this.select(pane);
panes.push(pane);
}
}
}
}).
directive('navelem', function() {
return {
require: '^navlist',
restrict: 'E',
replace: true,
transclude: true,
scope: { href: '#href' },
link: function(scope, element, attrs, tabsCtrl) {
tabsCtrl.addPane(scope);
scope.select = tabsCtrl.select;
},
template:
'<li ng-class="{active: selected}" ng-click="select(this)"><a href="{{href}}" ng-transclude></a></li>'
};
});
By defining scope: {} in your directive, it is creating a isolated scope.
So the parent scope is now invisible from the directive.
If you want to refer the parent scope, then you can put scope: true for shared
scope (among same directives) and omit the scope declaration for just normal scope nesting.
Or if you want to just refer $scope.foo of the parent, you can define
explicit scope variables like you've done in the child directive.
There are three types of directive scope inheritance:
No 'scope: ...' or explicit scope: false - no new scope is created. The directive uses the same scope as the parent. This is simple and convenient, but if you are building reusable components, this is not recommended, since the directive will likely only be usable if the parent scope has certain scope properties defined that the directive needs to use/access.
scope: true - creates a new scope, shared by all directives on the same element, with normal prototypical inheritance of the parent scope. Again, probably not the best choice for reusable components, since the directive probably shouldn't have access to the parent scope properties -- it could accidentally change something in the parent.
scope: { ... } - creates a new "isolated" scope -- it does not prototypically inherit from the parent scope. However, the object hash ( i.e., the { ... } ) allows us to define local directive scope properties that are derived from the parent scope -- so we can control which properties are shared, and how.
Use '=' for powerful 2-way binding between a parent scope property and a directive scope property -- changes to either scope property affect the other.
Use '#' for binding a parent's attribute value to a directive scope property. This is essentially 1-way binding. Only parent scope changes affect the directive scope.
Use '&' to bind to parent scope expressions/functions.
For your particular problem, you need to indicate in the object hash which scope properties you want to have 2-way binding.
For more about directive scopes (including pictures), please see section directives here: What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
Like Mark Rajcok said - scope: {} will create a new isolated scope that don't inherit properties from parent, however we still can get access to these properties by using $parent property.
Controller:
app.controller('indexController', function($scope) {
$scope.test="Hello world!";
});
Directive
app.directive("test", function() {
return{
restrict: "A",
scope: {},
controller: function($scope){
console.log("directiv $scope.$parent.test: " + $scope.$parent.test);
console.log("directiv $scope.test: " + $scope.test);
}
};
});
output:
directiv $scope.$parent.test: Hello world!
directiv $scope.test: undefined

Resources