Passing function into ng-repeat object - javascript

so I have a problem with ng-repeat directive. In my code I have a parent controller which have data stored as array of objects.
$scope.queue = [
{
name: 'Mark',
sex: 'Male',
age: 21
},
{...}
];
$scope.changePositionInQueue = function (currIndex, targetIndex) {
// move up / down person in queue
};
What I want to do is pass parent controller's function to my directive's ('person') isolated scope and at the same time be able to use '$index', '$first', '$last' variables.
<person data-change-position="changePositionInQueue" data-person="person" ng-repeat="person in queue"></person>
Directive scope declaration:
scope: {
person: '=',
changePosition: '&'
}
The problem is that when I create isolated scope inside ng-repeat loop I lose ng-repeat properties. On the other hand when I create default scope by ng-repeat and I have access to all wanted properties I can't use parent function.

This is my solution to your challenge:
In view:
<my-directive dir-func="fnc($index)" data="data" ng-repeat="data in datas"><span>{{data.id|json}}</span><br></my-directive>
In Direct call parent function in link:
myApp.directive('myDirective', function() {
return {
//require: 'ngModle',
scope: {
data: "=",
dirFunc: "&"
},
link: function(scope, elem, attr, ngModel) {
scope.dirFunc() // parent scope.func is called here where you get the alert
}
}
See the Plunker for detail

Related

How to pass an object from a nested directive with isolated scope to parent controller scope in angular

I have a directive treeview which contains a nested directive (being the branches) of each item rendered.
In the scope of both directives I have declared two parameters that should be talking to the parent controller.
filter: '&' //binds the method filter within the nested directive (branch) to the method doSomething() in the tree directive attribute which is bound to the html directive that binds to the controller.
iobj: '=' is the two way binding paramter that should be passing the scoped object to the controller. (but currently isn't)
Directive:
app.directive('tree', function () {
return {
restrict: 'E',
replace: true,
scope: {
t: '=src',
filter: '&',
iobj: '='
},
controller: 'treeController',
template: '<ul><branch ng-repeat="c in t.children" iobj="object" src="c" filter="doSomething()"></branch></ul>'
};
});
app.directive('branch', function($compile) {
return {
restrict: 'E',
replace: true,
scope: {
b: '=src',
filter: '&',
iobj: '='
},
template: '<li><input type="checkbox" ng-click="innerCall()" ng-hide="visible" /><a>{{ b.name }}</a></li>',
link: function (scope, element, attrs) {
var has_children = angular.isArray(scope.b.children);
scope.visible = has_children;
if (has_children) {
element.append('<tree src="b"></tree>');
$compile(element.contents())(scope);
}
element.on('click', function(event) {
event.stopPropagation();
if (has_children) {
element.toggleClass('collapsed');
}
});
scope.innerCall = function () {
scope.iobj = scope.b;
console.log(scope.iobj);
scope.filter();
}
}
};
});
HTML:
<div ng-controller="treeController">
<tree src="myList" iobj="object" filter="doSomething()"></tree>
<a ng-click="clicked()"> link</a>
</div>
Controller:
app.controller("treeController", ['$scope', function($scope) {
var vm = this;
$scope.object = {};
$scope.doSomething = function () {
var item = $scope.object;
//alert('call from directive');
console.log(item);
}
$scope.clicked = function () {
alert('clicked');
}
...
Currently I can invoke the function $scope.doSomething from the directive to the controller. So I know that I have access to the controllers scope from the directive. What I cannot figure out is how to pass an object as a parameter from the directive back to the controller. When I run this code, $scope.object
is always an empty object.
I'd appreciate any help or suggestions on how to go about this.
The & directive binding supports parameter passing. Given your example
scope.filter({message: 'Hello', anotherMessage: 'Good'})
The message and anotherMessage become local variables in the expression bound to directive:
<tree src="myList" iobj="object" filter="doSomething(anotherMessage, message)"></tree>
Here's a sample plunker where the callback parameters are set inside a template.
The documentation clearly states that:
Often it's desirable to pass data from the isolated scope via an
expression to the parent scope, this can be done by passing a map of
local variable names and values into the expression wrapper fn. For
example, if the expression is increment(amount) then we can specify
the amount value by calling the localFn as localFn({amount: 22}).

AngularJS : How to update controller scope associated to directive scope's object as it changes?

Here's the explanation:
I have the current controller that creates an array of $scope.plan.steps which will be used to store every step:
.controller('PlanCtrl', function ($scope, $http) {
$scope.plan = {
steps: [{}]
};
$scope.addStep = function () {
$scope.tutorial.steps.push({});
}
}
Then I have the following directive which has an isolated scope and that is associated to the index of the $scope.plan.steps array:
.directive('planStep', function () {
return {
template: '<input type="text" ng-model="step.name" />{{step}}',
restrict: 'E',
scope: {
index: '=index'
},
transclude: true,
controller: function($scope, $element, $transclude) {
$scope.removeStep = function() {
$scope.$emit('removeStep', $scope.index);
$element.remove();
$scope.$destroy();
}
}
};
});
These two communicate, create, and delete objects inside of the controller's scope, however, how can I allow the directive to update the controller's scope array in real time?
I've tried doing a $watch on the directive's isolated scope changes, $emit the changes to the controller, and specify the $index... But no luck.
I've created a plunker to reproduce what I currently have: Link
So far I can create and delete objects inside of the array, but I cannot get a single object to update the controller's object based on the $index.
If the explanation was not clear, by all means, let me know and I will elaborate.
Thank you
WHen you do things like this inside ng-repeat you can take advantage of the child scope that ng-repeat creates and work without isolated scope.
Here's the same directive without needing any angular events
.directive('planStep', function() {
return {
template: '<button ng-click="removeStep(step)">Delete step</button><br><input type="text" ng-model="step.name" />{{step}}<br><br>',
restrict: 'E',
transclude: true,
controller: function($scope, $element, $transclude) {
var steps = $scope.plan.steps// in scope from main controller
/* can do the splicing here if we want*/
$scope.removeStep = function(step) {
var idx =steps.indexOf(step)
steps.splice(idx, 1);
}
}
};
});
Also note that removing the element with element.remove() is redundant since it will automatically be removed by angular when array gets spliced
As for the update, it will update the item in real time
DEMO
The way you set up 2-way binding for index you could set one up for step as well? And you really do not need index to remove the item, eventhough your directive is isolated it relies on the index from ng-repeat which probably is not a good idea.
<plan-step ng-repeat="step in plan.steps" index="$index" step="step"></plan-step>
and in your directive:
scope: {
index: '=index',
step:'='
},
Demo
Removing $index dependency and redundant element remove() and scope destroy (when the item is removed from the array angular will manage it by itself):
return {
template: '<button ng-click="removeStep()">Delete step</button><br><input type="text" ng-model="step.name" />{{step}}<br><br>',
restrict: 'E',
scope: {
step:'='
},
transclude: true,
controller: function($scope, $element, $transclude) {
$scope.removeStep = function() {
$scope.$emit('removeStep', $scope.step);
}
}
and in your controller:
$scope.$on('removeStep', function(event, data) {
var steps = $scope.plan.steps;
steps.splice(steps.indexOf(data), 1);
});
Demo
If you want to get rid of $emit you could even expose an api with the isolated scoped directive with function binding (&).
return {
template: '<button ng-click="onDelete({step:step})">Delete step</button><br><input type="text" ng-model="step.name" />{{step}}<br><br>',
restrict: 'E',
scope: {
step:'=',
onDelete:'&' //Set up function binding
},
transclude: true
};
and register it on the view:
<plan-step ng-repeat="step in plan.steps" step="step" on-delete="removeStep(step)"></plan-step>
Demo

Pass object context back to controller callback from AngularJS Directive

I'm essentially trying to recreate ng-change but add some delay in it (auto-save on change frequency timeout).
So far, I have the following directive:
myApp.directive('changeDelay', ['$timeout', function ($timeout) {
return {
restrict: 'A',
require: 'ngModel',
scope: {
callBack: '=changeDelay'
},
link: function (scope, elem, attrs, ngModel) {
var firstRun = true;
scope.timeoutHandle = null;
scope.$watch(function () {
return ngModel.$modelValue;
}, function (nv, ov) {
console.log(firstRun);
if (!firstRun) {
console.log(nv);
if (scope.timeoutHandle) {
$timeout.cancel($scope.timeoutHandle);
}
scope.timeoutHandle = $timeout(function () {
//How can I pass person??
scope.callBack();
}, 500);
}
firstRun = false;
});
}
};
}]);
With the following controller:
myApp.controller('MyCtrl', ['$scope', function ($scope) {
$scope.people = [{
name: "Matthew",
age: 20
}, {
name: "Mark",
age: 15
}, {
name: "Luke",
age: 30
}, {
name: "John",
age: 42
}];
$scope.updatePerson = function (person) {
//console.log("Fire off request to update:");
//How can I get person here??
//console.log(person);
};
}]);
And this markup should be able to define which controller scope method to call as well as the object that is passed to it:
<div ng-app='myApp'>
<div ng-controller="MyCtrl">
<div ng-repeat="person in people">
<input type="text" ng-model="person.name" change-delay="updatePerson(person)" />
</div>
</div>
</div>
Here's an failing fiddle: http://jsfiddle.net/Troop4Christ/fA4XJ/
As you can see, I can't figure out how to call the directive attribute parameter w/ the "person" parameter passed to it.
So like I said, at the begining.. just trying to recreate ng-change w/ some "tweaking". How is this done in ng-change? i.e.
Solution
Isolate scope binding should be declared with "&" instead of "=", thus resulting in scope.callBack() executing the updatePerson(person) given function.
Explanations
When isolating a scope, you work with "#", "=" and "&":
"#" tells angular to watch the result of attribute evaluation against the element scope
"=" tells angular to build the getter/setter with $parse
"&" tells angular to bind a function that will evaluate the attribute (and, as an option, provide an extension to the attribute definition scope as an argument to this function call).
So, when you choose this last option "&", it means that calling callBack() on the isolate directive scope will actually call updatePerson(person) againts the outside scope (not extended with any object coming from isolate scope).
Taking the scope extension capability into account, you could have replaced the person argument of the updatePerson(person) by calling scope.callBack({person: {a:1}}). Then person would have been {a:1} in the updatePerson call scope (function scope, not angular scope).

How to set scope variable in isolate scope from link function

I'm trying to set variable in isolate scope, but I can't access variable that I set in linked function in isolate scope and also I can access controller's variable.
app.directive('myDir', function($timeout) {
return {
restrict: 'A',
scope: {
'propVar': '='
},
link: function(scope, elem) {
console.log(elem)
scope.linkVar = 'It works!'
console.log(scope);
},
}
})
I created plunker to show what I mean: http://plnkr.co/edit/lUBvIkF4fKXkEgujJpuU?p=preview
It work's all as it should be.
1) If you define 'propVar' : '=' it means that your directive element has an attribute prop-var. This is not the case. If you would like to use the prop attribute you have to define your isolated scope in this way: 'propVar' : '=prop'.
2) The child elements of your directive are bound to the controllers scope not to the directive scope. If you would like the the child elements to be part of your directive you may use a template in your directive:
app.directive('myDir', function() {
return {
restrict: 'A',
template: '<div><p>linkVar: {{ linkVar }}</p><p>propVar: {{ propVar }}</p><p>foo: {{ foo }}</p><button ng-click="foo=\'directive sucks\'">press me</button></div>',
scope: {
propVar: '=prop'
},
link: function(scope, elem) {
console.log(elem)
scope.linkVar = 'It works!'
console.log(scope);
},
}
})
see the modified PLUNKR: http://plnkr.co/edit/MHXXkmPdtfAUqtre4Fbg?p=preview

isolate scope '=' not getting assigned

I have the following directive:
CorrelatorApp.directive('correlator', function ($WebApi) {
return {
restrict: 'A',
scope: {
crOptions: '=',
},
link: function (scope, element, attrs) {
var options = scope.crOptions;
}
}
});
then in my index.html I use it like this:
<form correlator cr-options="correlatorOptions" name="CorrelatorForm" ng-controller="PortalMerchantController">
and my correlatorOptions are defined in the controller:
CorrelatorApp.controller("PortalMerchantController", function
PortalMerchantController($scope, $http) {
$scope.correlatorOptions = {
dependant: {
controller: 'PortalMerchant',
model: 'portalMerchants',
nameField: 'PortalsMerchantName'
},
principal: {
controller: 'Merchant',
model: 'merchants',
nameField: 'Name'
}
};
});
when the directive links, the value of scope.crOptions is undefined. If I set crOptions to & and then call it (var options = scope.crOptions()), the code executes correctly and I get the object defined in the controller. What am I missing?
Move your ngController directive outside of the form element.
In 1.2.0 and greater, the ngController and form have sibling scopes (previously they would have shared the isolate scope). Here's the change that causes this
You want form to be a child of ngController so it can access it's scope:
<div ng-controller="PortalMerchantController">
<form correlator cr-options="correlatorOptions" name="CorrelatorForm"></form>
working fiddle

Categories

Resources