AngularJS pass variable argument function calls in elements generated with ng-repeat - angularjs

Using bootstrap with AngularJS components does not work if one needs to encapsulate inner Bootstrap elements in components, because of the extra markup of the component itself added to the DOM breaks CSS rules with direct child operator >. For example implementing a DropDown one needs to create a full component with the DropDown and should generate every dropdown option inside this full component with ng-repeat reading data from a configuration array. Something like this:
<my-bootstrap-drop-down
my-label="Some label"
my-options="[ { label: 'Option1 }, {label: 'Option2'} ]" >
</my-bootstrap-drop-down>
From Dan Wahlin's "Creating Custom AngularJS Directives" to be able to pass a function with variable number of arguments to an AngularJS component you need a special syntax where you pass a function reference to an attribute of the element tag like this:
<my-component
my-action="myMethod(p1, p2)"
my-params="{p1:1, p2:25}">
</my-componenet>
And then in the component you call the function with this code:
<a ng-click="$ctrl.myAction($ctrl.myParams)"></a>
This syntax only works right when used in element attributes mapped with the & operator as bindings of a component / directive. Even when my-action="myMethod(p1, p2) seems a function call it is in fact a passing by reference. Unfortunately if you want to use ng-repeat to generate some code inside the component like explained above, there is no way to make that syntax to work, since the myThethod(p1, p2) syntax only work in an attribute.
So how can you implement a component having an array of inner elements generated with ng-repeat and those elements having function calls with variable number of arguments, since the later syntax does not work?
<my-bootstrap-drop-down
my-label="Some label"
my-options="[
{ label: 'Option1', action: myMethod(p1, p2), params: {p1:1, p2:25}},
...
]" >
</my-bootstrap-drop-down>
When trying to do this code, the myMethod(p1, p2) is executed when creating the component, since it is in fact a function call, not a pass by reference.
Note: In the same article referenced above it is suggested another syntax for invoking functions. The syntax asumed that the component knows how many arguments to pass, which is not the case. It could be used anyway pasing the arguments as an array and invoking the function with apply, but apply is not allowed in angular expressions.
I have added a Plunker to make it clear:
https://plnkr.co/edit/dkofEYhebp0T6lSf22RP?p=preview

Edit:
Ok, not sure why you need this but I got it to work:
https://plnkr.co/edit/uR9s5vUJxQoviTiUD2vj?p=preview
And the same but using a Directive:
https://plnkr.co/edit/Onh2WonmarpUscnFFLGK?p=preview
End of Edit
You should pass a variable to "my-options" (let's call it 'dropDownOptions'):
<my-bootstrap-drop-down
my-label="Some label"
my-options="dropDownOptions" >
</my-bootstrap-drop-down>
And the dropDownOptions array should contain the data you need in the directive, but only the data, not a function: [{"label": "Option 1", "params": {"p1": 1, "p2": 25}}, ...]
Now inside your directive, you have access to the data and can work on the action/function part. Example:
var testApp = angular.module('testApp', []);
testApp.controller('mainCtrl', ['$scope',
function ($scope) {
$scope.test = "Hi";
$scope.dropDownOptions = [{"name": "yes", "value": 2}, {"name": "no", "value": 25}];
}]);
testApp.directive('myBootstrapDropDown', function () {
return {
restrict: 'E',
scope: {
myLabel: '#',
myOptions: '='
},
controller: function ($scope) {
$scope.myMethod = function (val) {
alert("There was a change, new value: " + val);
};
},
template: '<label>{{myLabel}}</label> <select name="myLabel" ng-model="myValue" ng-options="opt.value as opt.name for opt in myOptions" ng-change="myMethod(myValue)"><option value=""> </option></select>'
};
});
<!DOCTYPE html>
<html lang="en" ng-app="testApp">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<title>Sample</title>
<style>
.starter-template {
padding: 10px 15px;
text-align: center;
}
a {
font-size: 11px;
cursor: pointer;
}
</style>
</head>
<body>
<div ng-controller="mainCtrl">
<div class="container">
<div class="starter-template">
<h1>Example</h1>
<p class="lead">{{test}}</p>
<my-bootstrap-drop-down
my-label="Some label"
my-options="dropDownOptions" >
</my-bootstrap-drop-down>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="app.js"></script>
</body>
</html>
Notice that the label is bound with "#" and the array with "=".
So you don't need to bind the function with your directive, unless the directive needs to trigger something back in the controller, in which case you should put the function in a separate attribute (which, in this case, would be bound with the & as you mentioned).
For example:
var testApp = angular.module('testApp', []);
testApp.controller('mainCtrl', ['$scope',
function ($scope) {
$scope.test = "Hi";
$scope.dropDownOptions = [{"name": "yes", "value": 2}, {"name": "no", "value": 25}];
$scope.runThis = function (val) {
//Do Something here
alert("There was a change, new value: " + val);
};
}]);
testApp.directive('myBootstrapDropDown', function () {
return {
restrict: 'E',
scope: {
myLabel: '#',
myOptions: '=',
myFunction: "&"
},
controller: function ($scope) {
$scope.myMethod = function (val) {
$scope.myFunction()(val);
};
},
template: '<label>{{myLabel}}</label> <select name="myLabel" ng-model="myValue" ng-options="opt.value as opt.name for opt in myOptions" ng-change="myMethod(myValue)"><option value=""> </option></select>'
};
});
<!DOCTYPE html>
<html lang="en" ng-app="testApp">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<title>Sample</title>
<style>
.starter-template {
padding: 10px 15px;
text-align: center;
}
a {
font-size: 11px;
cursor: pointer;
}
</style>
</head>
<body>
<div ng-controller="mainCtrl">
<div class="container">
<div class="starter-template">
<h1>Example</h1>
<p class="lead">{{test}}</p>
<my-bootstrap-drop-down
my-label="Some label"
my-options="dropDownOptions"
my-function="runThis" >
</my-bootstrap-drop-down>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="app.js"></script>
</body>
</html>
Notice the "()(val)" int the directive's controller. If you don't need to pass any value back to the original controller, just replace that by "()()".
I hope this helps, if you're still stuck you should share more of your code (your directive's code & html for example) so we can answer better.

Related

ng-show not hiding SPAN element in AngularJS 1.2

I am learning AngularJS from the book AngularJS up and running. This book uses AngularJS version 1.2. One of the sample codes in the books shows how ng-show directive works. The explanation of usage of ng-show directive in this example is given as follows:
ng-show inspect a variable and, depending on the truthiness of its
value, show or hide elements in the UI, respectively. In this case, we
say show the assignee span if note.assignee is true. AngularJS treats
true, nonempty strings, nonzero numbers, and nonnull JS objects as
truthy. So in this case, we get to see the assignee span if the note
has an assignee.
I am pasting the code below. (My query is given after the code.)
HTML:
<!-- File: chapter2/more-directives.html -->
<html ng-app="notesApp">
<head>
<title>Notes App</title>
<style>
.done {
background-color: green;
}
.pending {
background-color: red;
}
</style>
</head>
<body ng-controller="MainCtrl as ctrl">
<div ng-repeat="note in ctrl.notes"
ng-class="ctrl.getNoteClass(note.done)">
<span class="label"> {{note.label}}</span>
<span class="assignee"
ng-show="note.assignee"
ng-bind="note.assignee">
</span>
</div>
</body>
</html>
Script
<script
src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.19/angular.js">
</script>
<script type="text/javascript">
angular.module('notesApp', []).controller('MainCtrl', [
function() {
var self = this;
self.notes = [
{label: 'First Note', done: false, assignee: 'Shyam'},
{label: 'Second Note', done: false},
{label: 'Done Note', done: true},
{label: 'Last Note', done: false, assignee: 'Brad'}
];
self.getNoteClass = function(status) {
return {
done: status,
pending: !status
};
};
}]);
</script>
In the above code, an array is declared and there are two elements (first and and the last) which have some value in the 'assignee'. When I execute this code, I get to see all the elements of the array. The ng-show directive is not hiding the 'span' with a null value in the 'assignee'. Is the code wrong or my understanding of ng-show is wrong?
It should be like this
<div ng-show="note.assignee">
<span class="label"> {{note.label}}</span>
<span class="assignee" ng-bind="note.assignee"></span>
</div>

How to get ng-repeat item inside transcluded template?

How I can use ngRepeat item inside transcluded template? Is it possible?
Directive template:
<ng-transclude ng-repeat="record in records | filter1 | filter2"></ng-transclude>
Directive:
app.directive('myDirective', function () {
return {
templateUrl: '/views/directives/mydirective.html',
restrict: 'A',
transclude: true,
scope: {
records: '='
}
};
});
Controller view:
<div my-directive records="myRecords">
{{ myDirective.record }}
</div>
Doesn't look like it from the way you're doing it.
But you can $compile the template in the directive to achieve this.
http://jsbin.com/mirisixodo/edit?html,js,console,output
(Realizing this is almost certainly too late for you to use...)
Looks like this was discussed in detail in this AngluarJS GitHub issue, and there is, thanks to moneytree-doug, a way to solve your issue without resorting to compile.
The deal seems to be that, at least starting at AngularJS 1.2.18, transcluding creates a child scope of its own, so your iteration variable is no longer accessible.
$parent is accessible, however, and we can use that to access the iteration variable and accomplish what you're looking to do.
Building off of Micah's jsbin...
HTML:
<!DOCTYPE html>
<html ng-app="app">
<head>
<script
src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div ng-controller="TestCtrl">
<my-directive records="myRecords">
?: {{$parent.record}}
</my-directive>
</div>
</body>
</html>
JavaScript
var app = angular.module('app', [])
.controller('TestCtrl', function($scope) {
$scope.myRecords = ['foo', 'bar', 'baz'];
});
app.directive('myDirective', function () {
return {
scope: {
records: '='
},
transclude: true,
template: 'HELLO' +
'<div id="inner-transclude"' +
' ng-transclude' +
' ng-repeat="record in records">X</div>',
controller: function ($scope) {
console.log($scope.records);
},
restrict: 'E'
};
});
Result
HELLO
?: foo
?: bar
?: baz
JsBin of reasonable success.
So, again, the crux of this is changing the line if your controller from {{ myDirective.record }} to ?: {{$parent.record}}.

what is the best way to attach events to children elements when using directives in angular

i am little bit confused about directives.I want to make a combobox and it consists of multiple elements.
angular guys say do not make any manipulation in the controller so they point link function.
when i try to attach event to children elements remove them from parent and append them to body it is really hard to do these operations without jquery.maybe there is better way to it?
here is the code :
<!doctype html>
<html ng-app="angularApp">
<head>
<meta charset="utf-8" />
<style type="text/css">
.cities{
position: relative;
display: none;
}
</style>
<script type="text/ng-template" id="angular-combo-template.html">
<div id="combo-wrapper-{{id}}" class="combo-wrapper">
<input id="combo-input-{{id}}" type="text" />
<ul id="combo-menu-{{id}}" class="combo-menu">
<li ng-repeat="item in items">{{item.name}}</li>
</ul>
</div>
</script>
<script src="angular.min.js" type="text/javascript"></script>
<script type="text/javascript">
var angularApp = angular.module('angularApp', []);
angularApp.controller('CityController', function ($scope) {
$scope.name = "test";
$scope.cities = [
{
'name': 'Istanbul',
'value': 34
},
{
'name': 'Izmir',
'value': 35
},
{
'name': 'Amasya',
'value': 3
},
{
'name': 'Balikesir',
'value': 14
},
{
name: 'Bursa',
value: '16'
}
];
});
angularApp.directive("angularCombo", function () {
return {
restrict : 'E',
controller: function ($scope) {
},
link : function ($scope, element, attributes) {
},
scope: {
items: '=',
id : '#'
},
templateUrl : 'angular-combo-template.html'
}
});
</script>
</head>
<body ng-controller="CityController">
<angular-combo id="city" items="cities"></angular-combo>
<angular-combo id="towns" items="towns"></angular-combo>
</body>
</html>
i want to attach focus/blur on input field and when i focus on input, ul must be appended to body by after removed from element, on blur it must be removed from body and append to inside element again.
You don't need events and such, that's not "the Angular way" (see how-to-think-in-angular).
Here you go (jsfiddle):
<div class="combo-wrapper">
<input type="text" ng-focus="showList = true" ng-blur="showList = false"/>
<ul ng-show="showList">
<li ng-repeat="item in items">{{item.name}}</li>
</ul>
</div>

AngularJS: All elements using directive show same value

I have a directive which passes a reference to the scope data through an attribute like so (assuming $scope.parent.child exists):
<span status-label item='parent.child'></span>
The directive works as expected when used once. But when used more than once, and with different item attribute values, all elements using the directive show the same value.
My full code is below and a Plunker is here: http://plnkr.co/edit/9ahmua?p=preview. If you'll notice, changing the item= value on the second element using the directive (line 8) will change the value for all elements to that value.
What am I doing incorrectly? How do I make each element / directive work off of its individual item attribute? Thanks.
<!DOCTYPE html>
<html ng-app='familyApp'>
<head>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div ng-controller='BaseController'>
<p>{{ parent.child.name }}: <span status-label item='parent.child'></span></p>
<p>{{ root.name }}: <span status-label item='root'></span></p>
</div>
</body>
<script data-require="jquery#1.7.1" data-semver="1.7.1" src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="http://code.angularjs.org/1.2.12/angular.js" data-semver="1.2.12" data-require="angular.js#1.2.12"></script>
<script>
var familyApp = angular.module('familyApp', []);
angular.module('familyApp').controller('BaseController',
function ($scope) {
$scope.root = {
name: 'Jack',
age: 40,
flagged: false
};
$scope.parent = {
child: {
name: 'Jill',
age: 30,
flagged: true
}
};
});
angular.module('familyApp').directive('statusLabel',
function ($compile, $parse) {
return {
link: function (scope, element) {
scope.status = function (item) {
item = $parse(element.attr('item'))(scope);
if (item.flagged === true) {
return 'flagged';
}
return 'clean';
};
},
transclude: true,
template: '<div ng-switch on="status()"><div ng-switch-when="flagged">Flagged</div><div ng-switch-when="clean">Clean</div></div>'
};
});
</script>
</html>
You will want to give the directive it's own scope by setting scope to something.
You can learn more about that here

How/when to use ng-click to call a route?

Suppose you are using routes:
// bootstrap
myApp.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
$routeProvider.when('/home', {
templateUrl: 'partials/home.html',
controller: 'HomeCtrl'
});
$routeProvider.when('/about', {
templateUrl: 'partials/about.html',
controller: 'AboutCtrl'
});
...
And in your html, you want to navigate to the about page when a button is clicked. One way would be
<a href="#/about">
... but it seems ng-click would be useful here too.
Is that assumption correct? That ng-click be used instead of anchor?
If so, how would that work? IE:
<div ng-click="/about">
Routes monitor the $location service and respond to changes in URL (typically through the hash). To "activate" a route, you simply change the URL. The easiest way to do that is with anchor tags.
Go Home
Go to About
Nothing more complicated is needed. If, however, you must do this from code, the proper way is by using the $location service:
$scope.go = function ( path ) {
$location.path( path );
};
Which, for example, a button could trigger:
<button ng-click="go('/home')"></button>
Here's a great tip that nobody mentioned. In the controller that the function is within, you need to include the location provider:
app.controller('SlideController', ['$scope', '$location',function($scope, $location){
$scope.goNext = function (hash) {
$location.path(hash);
}
;]);
<!--the code to call it from within the partial:---> <div ng-click='goNext("/page2")'>next page</div>
Using a custom attribute (implemented with a directive) is perhaps the cleanest way. Here's my version, based on #Josh and #sean's suggestions.
angular.module('mymodule', [])
// Click to navigate
// similar to <a href="#/partial"> but hash is not required,
// e.g. <div click-link="/partial">
.directive('clickLink', ['$location', function($location) {
return {
link: function(scope, element, attrs) {
element.on('click', function() {
scope.$apply(function() {
$location.path(attrs.clickLink);
});
});
}
}
}]);
It has some useful features, but I'm new to Angular so there's probably room for improvement.
Remember that if you use ng-click for routing you will not be able to right-click the element and choose 'open in new tab' or ctrl clicking the link. I try to use ng-href when in comes to navigation. ng-click is better to use on buttons for operations or visual effects like collapse.
But
About
I would not recommend. If you change the route you might need to change in a lot of placed in the application. Have a method returning the link. ex:
About. This method you place in a utility
I used ng-click directive to call a function, while requesting route templateUrl, to decide which <div> has to be show or hide inside route templateUrl page or for different scenarios.
AngularJS 1.6.9
Lets see an example, when in routing page, I need either the add <div> or the edit <div>, which I control using the parent controller models $scope.addProduct and $scope.editProduct boolean.
RoutingTesting.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Testing</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-route.min.js"></script>
<script>
var app = angular.module("MyApp", ["ngRoute"]);
app.config(function($routeProvider){
$routeProvider
.when("/TestingPage", {
templateUrl: "TestingPage.html"
});
});
app.controller("HomeController", function($scope, $location){
$scope.init = function(){
$scope.addProduct = false;
$scope.editProduct = false;
}
$scope.productOperation = function(operationType, productId){
$scope.addProduct = false;
$scope.editProduct = false;
if(operationType === "add"){
$scope.addProduct = true;
console.log("Add productOperation requested...");
}else if(operationType === "edit"){
$scope.editProduct = true;
console.log("Edit productOperation requested : " + productId);
}
//*************** VERY IMPORTANT NOTE ***************
//comment this $location.path("..."); line, when using <a> anchor tags,
//only useful when <a> below given are commented, and using <input> controls
$location.path("TestingPage");
};
});
</script>
</head>
<body ng-app="MyApp" ng-controller="HomeController">
<div ng-init="init()">
<!-- Either use <a>anchor tag or input type=button -->
<!--Add Product-->
<!--<br><br>-->
<!--Edit Product-->
<input type="button" ng-click="productOperation('add', -1)" value="Add Product"/>
<br><br>
<input type="button" ng-click="productOperation('edit', 10)" value="Edit Product"/>
<pre>addProduct : {{addProduct}}</pre>
<pre>editProduct : {{editProduct}}</pre>
<ng-view></ng-view>
</div>
</body>
</html>
TestingPage.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.productOperation{
position:fixed;
top: 50%;
left: 50%;
width:30em;
height:18em;
margin-left: -15em; /*set to a negative number 1/2 of your width*/
margin-top: -9em; /*set to a negative number 1/2 of your height*/
border: 1px solid #ccc;
background: yellow;
}
</style>
</head>
<body>
<div class="productOperation" >
<div ng-show="addProduct">
<h2 >Add Product enabled</h2>
</div>
<div ng-show="editProduct">
<h2>Edit Product enabled</h2>
</div>
</div>
</body>
</html>
both pages -
RoutingTesting.html(parent), TestingPage.html(routing page) are in the same directory,
Hope this will help someone.
Another solution but without using ng-click which still works even for other tags than <a>:
<tr [routerLink]="['/about']">
This way you can also pass parameters to your route: https://stackoverflow.com/a/40045556/838494
(This is my first day with angular. Gentle feedback is welcome)
You can use:
<a ng-href="#/about">About</a>
If you want some dynamic variable inside href you can do like this way:
<a ng-href="{{link + 123}}">Link to 123</a>
Where link is Angular scope variable.
just do it as follows
in your html write:
<button ng-click="going()">goto</button>
And in your controller, add $state as follows:
.controller('homeCTRL', function($scope, **$state**) {
$scope.going = function(){
$state.go('your route');
}
})

Resources