Set angular directive attribute by calling function - angularjs-directive

I'm trying to set the value of a directive's attribute by calling a function on the containing page's controller, but it doesn't work as expected. In the code below, the "make" object does not have a "modelList" property, so I must place a separate call to the server to get it for each make.
<div ng-repeat="make in makeList">
<model-list-directive model-list="getModelList(make)" />
</div>
app.controller("myController",function($scope) {
$scope.getModelList = function(make) {
return null;
//return myService.getModelList(make);
};
})
app.directive("modelListDirective",function() {
restrict:'E',
scope: {
modelList: '='
},
template: '<ul><li ng-repeat="model in modelList">{{model.modelName}}</li></ul>',
controller: ['$scope', function ($scope) {
}]
If the getModelList() function is set to return null (not commented out in the code), no error is given, but the function is called multiple times (randomly varies between 3 and 5 usually).
The real problem comes when I invoke myService.getModelList(make) (commented out in the code). This results in an endless loop of calls to the service, which crashes the browser.
I'm guessing this is because of two-way binding, but I'm not sure.
Is there a better way to get dynamic data to the directive?

I think part of the problem is that your directive definition isn't returning an object. It should look like this:
app.directive('modelListDirective',function() {
return { // <-- need to return an object
restrict:'E',
scope: {
modelList: '='
},
template: '<ul><li ng-repeat="model in modelList">{{model.modelName}}</li></ul>',
controller: ['$scope', function ($scope) {
}]
};
});
However, you're passing a function as a 2-way binding into the directive, which you shouldn't do. See this answer to a similar issue.
What you can do instead is inject myService directly into your directive, then have your directive call myService.getModelList() in its link function.
So your markup would look like this:
<div ng-repeat="make in makeList">
<model-list-directive make="{{make}}" />
</div>
Each directive instance would just need the make.
And your directive definition would look like this:
app.directive('modelListDirective', ['myService', function(myService) {
return {
restrict:'E',
scope: {
make: '#'
},
link: function (scope, element, attrs) {
scope.modelList = myService.getModelList(scope.make);
},
template: '<ul><li ng-repeat="model in modelList">{{model.modelName}}</li></ul>',
controller: ['$scope', function ($scope) {
}]
};
}]);
setting scope.modelList in its link function.
Here's a fiddle.

Related

Passing an object to attributes of directive in angularjs

I have a directive which creates a modal.I am trying to pass an object through attributes to this directive.
<modal-dialog model="viewSummaryDialog" info="{{info}}"></modal-dialog>
And I'm retrieving it through attributes like this
return {
restrict: 'E',
scope: {
model: '=',
info:'#',
},
link: function(scope, element, attributes) {
scope.info=scope.$eval(attributes.info);
In my HTML info is an object which has ng-model of different text fields and dropdowns
My problem is that info is not being updated with whatever I enter in the text fields.I am getting only auto selected drop down values in my info object in the directive.I understand this is because link function is being called even before I enter anything in text fields.
Is there any way to make sure that my info object is passed only after I enter all the fields in the form? I am not very clear about how to pass an object to the directive.I tried using resolve function also in my directive but that didn't work.
Thanks in advance :-)
Passing object to directive
var myApp = angular.module('myApp',[]);
myApp.directive('passObject', function() {
return {
restrict: 'E',
scope: { object: '=' },
template: '<div>Hello, {{object.prop}}!</div>'
};
});
myApp.controller('MyCtrl', function ($scope) {
$scope.object = { prop: "world" };
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.5/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl">
<pass-object object="object"></pass-object>
</div>

Directive with js-call in the template - method does not get called

In my angular app, I have an alert-service, which handles the list of alerts. I then have a directive, which renders all the alerts to the page. I use the UI Bootstrap components.
However, the close button of the alert does not call the method:
.directive('someAlert', ['alertService', function (alertService){
var templateString = '<uib-alert ng-repeat="alert in vm.alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>';
return {
restrict: 'E',
template: templateString,
scope: true,
controller: function(){
var vm = this;
vm.alerts = alertService.get();
vm.closeAlert = function (index) {
console.log('closeAlert within directive controller called');
alertService.closeAlertIdx(index);
}
},
controllerAs: 'vm',
replace: true
}
}]);
Try this in the templateString
close="vm.closeAlert($index)"
I added a close method directly to the alert-instance. Then the alert.close() is working as expected. Nice solution found: alertservice and a slightly modified

access app controller scope inside custom directive in ng-repeat

I'm new to this so sorry if I'm asking something obvious. I have an app with controller holding some config variables in scope. I have a custom directive used inside ng-repeat that will have to make use of this config. For debugging purposes I need the changes to config bee reflected inside the directive. How can I achieve this.What I have so far is not working my pointsmap is undefined
angular.module('demo', [])
.directive('demoDir', function () {
var dirController = ['$scope', function ($scope) {
$scope.totalPoints = 0;
$scope.$watch('person', function(newVal, oldVal){
resetPoints(newVal);
}, true);
function resetPoints(pPerson){
$scope.totalPoints = $pointsMap['VIP'] * pPerson.points ;
}
return {
restrict: 'E',
scope: {
person: '=' ,
pointsMap : '='
},
controller: dirController,
template: '<span> {{totalPoints}}</span>'
}
})
.controller('mainAppController', function ($compile, $scope, $q ) {
/*CONFIG */
$scope.points = {
'VIP': 8.50,
'Standard': 7.50,
};
});
<demoDir person='myobject' pointsMap='points' />
Any particular reason why you want your directive to have isolated scope? If there is no restriction to use isolated scope, you can simply make the scope of the directive as false(now directive will use parent scope)
angular.module('demo', [])
.directive('demoDir', function () {
var dirController = ['$scope', function ($scope) {
$scope.totalPoints = 0;
$scope.$watch('person', function(newVal, oldVal){
resetPoints(newVal);
}, true);
function resetPoints(pPerson){
$scope.totalPoints = $pointsMap['VIP'] * pPerson.points ;
}
return {
restrict: 'E',
scope: false,
controller: dirController,
link: function(scope, element, attrs){
//scope.points should be available
}
template: '<span> {{totalPoints}}</span>'
}
})
.controller('mainAppController', function ($compile, $scope, $q ) {
/*CONFIG */
$scope.points = {
'VIP': 8.50,
'Standard': 7.50,
};
});
//assuming that your directive is housed inside the mainAppController
<div ng-controller="mainAppController">
<demoDir/>
</div>
You will also have access to the parent scope properties in the directive's link function if you want to use.

How to use a angularjs route to call a javascript function

I'm trying to use the routing of angularjs to call a javascript function if a certain url is used.
The following code is not providing the expected result:
var app = angular.module('myApp', []);
app.config(function($routeProvider) {
$routeProvider.when('/link1', {
controller: 'PageController'
})
.when('/link2', {
controller: 'PageController'
})
.otherwise({
controller: 'PageController'
});
});
app.controller('PageController', function($scope, $routeParams) {
alert('1');
});
The alert(1); is not called if one of these URLs are requested...
Maybe someone knows how to solve this ?
Controller is not called until you specify template or templateUrl option in $routeProvider configuration. If there is no template needed, you could specify one-space char (but not empty string). Like so
$routeProvider.when('/link1', {
controller: 'PageController',
template: ' '
})
There is no way to associate the routing with a specific action in the controller. The routing in the AngularJS is not like the routing in other web frameworks to route to specific action of request. Instead, the routing in the AngularJS is primarily relating to handle the page flow and the controller defines the scope of the page.
However, if you put the alert in the controller like that, it should be triggered when the page is loaded. You need to check whether the URL you used is correct or not. To test, you can simply put $location.url('/link1') in your code.
If your controller is being used on a particular route, then you can call that function inside the controller. It will get executed once the route changes and your controller is called.
In this http://plnkr.co/edit/qUZ5Q7nKCRAS8dFvjRIg when you click on link1 it displays alert.
I can't quite catch why your code doesn't work as expected, but I created a similar app setup and it works:
var app = angular.module('myApp',[]).
config(['$routeProvider',function($routeProvider) {
$routeProvider.
when('/', {
controller: 'PageController',
template: '<br><br>this is page #/<br> {{data}}',
}).
when('/link1', {
controller: 'SpecificPageController',
template: '<br><br>this is page #/link1<br> {{data}}'
}).
when('/link2', {
controller: 'PageController',
template: '<br><br>this is page #/link2<br> {{data}}'
}).
otherwise({redirectTo:'/'});
}]).
controller('PageController', function($scope, $routeParams) {
$scope.data = 'hello world';
}).
controller('SpecificPageController', function($scope, $routeParams) {
$scope.data = 'hello specific';
alert(1);
});
Whenever SpecificPageController is assigned to a route, and that route opened, the alert function gets executed.

angularjs directive call function specified in attribute and pass an argument to it

I want to create a directive that links to an attribute. The attribute specifies the function that should be called on the scope. But I also want to pass an argument to the function that is determined inside the link function.
<div my-method='theMethodToBeCalled'></div>
In the link function I bind to a jQuery event, which passes an argument I need to pass to the function:
app.directive("myMethod",function($parse) {
restrict:'A',
link:function(scope,element,attrs) {
var expressionHandler = $parse(attrs.myMethod);
$(element).on('theEvent',function( e, rowid ) {
id = // some function called to determine id based on rowid
scope.$apply(function() {expressionHandler(id);});
}
}
}
app.controller("myController",function($scope) {
$scope.theMethodToBeCalled = function(id) { alert(id); };
}
Without passing the id I can get it working, but as soon as I try to pass an argument, the function is not called anymore
Marko's solution works well.
To contrast with recommended Angular way (as shown by treeface's plunkr) is to use a callback expression which does not require defining the expressionHandler. In marko's example change:
In template
<div my-method="theMethodToBeCalled(myParam)"></div>
In directive link function
$(element).click(function( e, rowid ) {
scope.method({myParam: id});
});
This does have one disadvantage compared to marko's solution - on first load theMethodToBeCalled function will be invoked with myParam === undefined.
A working exampe can be found at #treeface Plunker
Just to add some info to the other answers - using & is a good way if you need an isolated scope.
The main downside of marko's solution is that it forces you to create an isolated scope on an element, but you can only have one of those on an element (otherwise you'll run into an angular error: Multiple directives [directive1, directive2] asking for isolated scope)
This means you :
can't use it on an element hat has an isolated scope itself
can't use two directives with this solution on the same element
Since the original question uses a directive with restrict:'A' both situations might arise quite often in bigger applications, and using an isolated scope here is not a good practice and also unnecessary. In fact rekna had a good intuition in this case, and almost had it working, the only thing he was doing wrong was calling the $parsed function wrong (see what it returns here: https://docs.angularjs.org/api/ng/service/$parse ).
TL;DR; Fixed question code
<div my-method='theMethodToBeCalled(id)'></div>
and the code
app.directive("myMethod",function($parse) {
restrict:'A',
link:function(scope,element,attrs) {
// here you can parse any attribute (so this could as well be,
// myDirectiveCallback or multiple ones if you need them )
var expressionHandler = $parse(attrs.myMethod);
$(element).on('theEvent',function( e, rowid ) {
calculatedId = // some function called to determine id based on rowid
// HERE: call the parsed function correctly (with scope AND params object)
expressionHandler(scope, {id:calculatedId});
}
}
}
app.controller("myController",function($scope) {
$scope.theMethodToBeCalled = function(id) { alert(id); };
}
Not knowing exactly what you want to do... but still here's a possible solution.
Create a scope with a '&'-property in the local scope.
It "provides a way to execute an expression in the context of the parent scope" (see the directive documentation for details).
I also noticed that you used a shorthand linking function and shoved in object attributes in there. You can't do that. It is more clear (imho) to just return the directive-definition object. See my code below.
Here's a code sample and a fiddle.
<div ng-app="myApp">
<div ng-controller="myController">
<div my-method='theMethodToBeCalled'>Click me</div>
</div>
</div>
<script>
var app = angular.module('myApp',[]);
app.directive("myMethod",function($parse) {
var directiveDefinitionObject = {
restrict: 'A',
scope: { method:'&myMethod' },
link: function(scope,element,attrs) {
var expressionHandler = scope.method();
var id = "123";
$(element).click(function( e, rowid ) {
expressionHandler(id);
});
}
};
return directiveDefinitionObject;
});
app.controller("myController",function($scope) {
$scope.theMethodToBeCalled = function(id) {
alert(id);
};
});
</script>
You can create a directive that executes a function call with params by using the attrName: "&" to reference the expression in the outer scope.
We want to replace the ng-click directive with ng-click-x:
<button ng-click-x="add(a,b)">Add</button>
If we had this scope:
$scope.a = 2;
$scope.b = 2;
$scope.add = function (a, b) {
$scope.result = parseFloat(a) + parseFloat(b);
}
We could write our directive like so:
angular.module("ng-click-x", [])
.directive('ngClickX', [function () {
return {
scope: {
// Reference the outer scope
fn: "&ngClickX",
},
restrict: "A",
link: function(scope, elem) {
function callFn () {
scope.$apply(scope.fn());
}
elem[0].addEventListener('click', callFn);
}
};
}]);
Here is a live demo:
http://plnkr.co/edit/4QOGLD?p=info
Here's what worked for me.
Html using the directive
<tr orderitemdirective remove="vm.removeOrderItem(orderItem)" order-item="orderitem"></tr>
Html of the directive: orderitem.directive.html
<md-button type="submit" ng-click="remove({orderItem:orderItem})">
(...)
</md-button>
Directive's scope:
scope: {
orderItem: '=',
remove: "&",
My solution:
on polymer raise an event (eg. complete)
define a directive linking the event to control function
Directive
/*global define */
define(['angular', './my-module'], function(angular, directives) {
'use strict';
directives.directive('polimerBinding', ['$compile', function($compile) {
return {
restrict: 'A',
scope: {
method:'&polimerBinding'
},
link : function(scope, element, attrs) {
var el = element[0];
var expressionHandler = scope.method();
var siemEvent = attrs['polimerEvent'];
if (!siemEvent) {
siemEvent = 'complete';
}
el.addEventListener(siemEvent, function (e, options) {
expressionHandler(e.detail);
})
}
};
}]);
});
Polymer component
<dom-module id="search">
<template>
<h3>Search</h3>
<div class="input-group">
<textarea placeholder="search by expression (eg. temperature>100)"
rows="10" cols="100" value="{{text::input}}"></textarea>
<p>
<button id="button" class="btn input-group__addon">Search</button>
</p>
</div>
</template>
<script>
Polymer({
is: 'search',
properties: {
text: {
type: String,
notify: true
},
},
regularSearch: function(e) {
console.log(this.range);
this.fire('complete', {'text': this.text});
},
listeners: {
'button.click': 'regularSearch',
}
});
</script>
</dom-module>
Page
<search id="search" polimer-binding="searchData"
siem-event="complete" range="{{range}}"></siem-search>
searchData is the control function
$scope.searchData = function(searchObject) {
alert('searchData '+ searchObject.text + ' ' + searchObject.range);
}
This should work.
<div my-method='theMethodToBeCalled'></div>
app.directive("myMethod",function($parse) {
restrict:'A',
scope: {theMethodToBeCalled: "="}
link:function(scope,element,attrs) {
$(element).on('theEvent',function( e, rowid ) {
id = // some function called to determine id based on rowid
scope.theMethodToBeCalled(id);
}
}
}
app.controller("myController",function($scope) {
$scope.theMethodToBeCalled = function(id) { alert(id); };
}