ng-show not hiding SPAN element in AngularJS 1.2 - angularjs

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>

Related

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

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.

How to show proprty from array by keys from anther array

I am trying to show data from 2 array based on same key,
the example is
firstArray
[{$id='12321',name='test'},{$id='12312',name='test'},{$id='1234',name='test'}]
second array:
[{$id='12321',value=4},{$id='12312',value=2}]
how can I display with ng-repeat the first array and if second have this id to show the value.
I tried to do
<div ng-repeat="qu in vm.qus">
<div ng-repeat="max in vm.maxs | filter :{ max.$id === qu.$id } ">
the error:
Error: [$parse:syntax] Syntax Error: Token '.' is unexpected,
expecting [}] at column 34 of the expression
You can use ng-if,
<div ng-repeat="qu in vm.qus">
<div ng-repeat="max in vm.maxs" ng-if="max.$id ===qu.$id">
{{max}}
</div>
</div>
DEMO
var app = angular.module('app', []);
app.controller('demoCtrl', ['$scope', function($scope) {
vm = this;
vm.qus = [{
$id: '12321',
value: 4
}, {
$id: '12312',
value: 2
}];
vm.maxs = [{
$id: '12321',
value: 4
}, {
$id: '12312',
value: 2
}]
}]);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body ng-app="app" ng-controller="demoCtrl as vm">
<div ng-repeat="qu in vm.qus">
<div ng-repeat="max in vm.maxs" ng-if="max.$id ===qu.$id">
{{max}}
</div>
</div>
<script type=" text/javascript " src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.6/angular.js "></script>
<script type="text/javascript " src="MainViewController.js "></script>
</body>
</html>
You had syntactical mistake, it should be
<div ng-repeat="max in vm.maxs | filter :{ max: {$id :qu.$id } }: true as filtered">
Though the better way of filtering collection is controller, as it will evaluate only once where, applying filtering on html itself got evaluated on each digest cycle(expensive in terms of performance)
vm.filterd = $filter('filter')(vm.maxs, { max: {$id :qu.$id } }, true);
But then you have to again think about to make sure you're applying above for each question.

Angularjs | how in directive change classes for siblings of clicked element?

I'm quite fresh with Angular, so sorry if my question looks trivial
I have few buttons, I create by ng-repeat. They have some bootstrap classes to make them nice, but if button is "active" gets additional class - active. The problem is only one button can be active, and I have 2 separate groups (containers with different class name) of buttons on page. They should work the same, but clicking on button on one groups should not affect the buttons in other group.
Could you, please help me write a directive, which will bind click event to button and remove 'active' class from all buttons in group and add it to clicked button only?
EDITED:
Here is a code in jsfiddle https://jsfiddle.net/996o7nk1/1/
HTML:
<!doctype html>
<html ng-app="confApp">
<head>
<title>Test</title>
<style>
.active {color: red;}
</style>
</head>
<body>
<div ng-controller="ProjectConfCtrl">
<div class="tans">
<button class="btn btn-default btn-cph" ng-repeat="tan in tans" ng-class="active: tan.active === true" data-tan-type-id="{{tan.id}}">{{tan.name}}</button>
</div>
<div class="langs">
<button class="btn btn-default btn-cph" data-ng-repeat="lang in langs">{{ lang.name }}</button>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script src="custom.js"></script>
</body>
</html>
JavaScript:
var app = angular.module("confApp", []);
app.controller('ProjectConfCtrl', [
'$scope', function($scope) {
$scope.tans = [
{
id: 1,
name: 'Auto',
active: true
}, {
id: 2,
name: 'Import',
active: false
}
];
$scope.langs = [
{
id: 1,
name: 'English',
active: true
}, {
id: 2,
name: 'Spanish',
active: false
}, {
id: 3,
name: 'German',
active: false
}
];
}
]);
For fiddle I just changed markup (removed html and head tag and added ng-app to div (it was in html)) but somehow in fiddle it throws an error.
I also added class, but it doesn't work also...
You can use ng-class together with a variable in the model, like in this fiddle:
https://jsfiddle.net/L5wm11s2/1/
Notice that I had to put the variables in the controller, not in the scope, and use the controller as to be able to access the variables from the view. That's because ng-repeat creates a child scope for each repeated element - a copy of the original scope, but separated from it. Because of that, if I did simply selectedTan = tan in ng-click, I'd end up setting the variable in the child scope. That means each button might consider itself selected, because it would have it's own selectedTan variable.

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>

How can I use Angular to output dynamic form fields?

I want to render a form, based on a dynamic field configuration:
$scope.fields = [
{ title: 'Label 1', type: 'text', value: 'value1'},
{ title: 'Label 2', type: 'textarea', value: 'value2'}
];
This should output something that behaves like:
<div>
<label>{{field.title}}<br />
<input type="text" ng-model="field.value"/>
</label>
</div>
<div>
<label>{{field.title}}<br />
<textarea ng-model="field.value" rows="5" cols="50"></textarea>
</label>
</div>
The simple implementation would be to use if statements to render the templates for each field type. However, as Angular doesn't support if statements, I'm lead to the direction of directives. My problem is understanding how the data binding works. The documentation for directives is a bit dense and theoretical.
I've mocked up a bare bones example of what I try to do here: http://jsfiddle.net/gunnarlium/aj8G3/4/
The problem is that the form fields aren't bound to the model, so the $scope.fields in submit() isn't updated. I suspect the content of my directive function is quite wrong ... :)
Going forward, I need to also support other field types, like radio buttons, check boxes, selects, etc.
The first problem you are running into regardless of the directive you are trying to create is using ng-repeat within a form with form elements. It can be tricky do to how ng-repeat creates a new scope.
This directive creates new scope.
I recommend also instead of using element.html that you use ngSwitch instead in a partial template.
<div class="form-row" data-ng-switch on="field.type">
<div data-ng-switch-when="text">
{{ field.title }}: <input type="text" data-ng-model="field.value" />
</div>
<div data-ng-switch-when="textarea">
{{ field.title }}: <textarea data-ng-model="field.value"></textarea>
</div>
</div>
This still leaves you with the problem of modifying form elements in child scope due to ng-repeat and for that I suggest using the ngChange method on each element to set the value when an item has changed. This is one of the few items that I don't think AngularJS handles very well at this time.
You might consider Metawidget for this. It uses JSON schema, but is otherwise very close to your use case. Complete sample:
<html ng-app="myApp">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js" type="text/javascript"></script>
<script src="http://metawidget.org/js/3.5/metawidget-core.min.js" type="text/javascript"></script>
<script src="http://metawidget.org/js/3.5/metawidget-angular.min.js" type="text/javascript"></script>
<script type="text/javascript">
angular.module( 'myApp', [ 'metawidget' ] )
.controller( 'myController', function( $scope ) {
$scope.metawidgetConfig = {
inspector: function() {
return {
properties: {
label1: {
type: 'string'
},
label2: {
type: 'string',
large: true
}
}
}
}
}
$scope.saveTo = {
label1: 'value1',
label2: 'value2'
}
$scope.save = function() {
console.log( $scope.saveTo );
}
} );
</script>
</head>
<body ng-controller="myController">
<metawidget ng-model="saveTo" config="metawidgetConfig">
</metawidget>
<button ng-click="save()">Save</button>
</body>
</html>
The type attribute can be changed when the element is out of DOM, so why not a small directive which removes it from DOM, changes it type and then add back to the same place?
The $watch is optional, as the objective can be change it dynamically once and not keep changing it.
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
$scope.rangeType = 'range';
$scope.newType = 'date'
});
app.directive('dynamicInput', function(){
return {
restrict: "A",
link: linkFunction
};
function linkFunction($scope, $element, $attrs){
if($attrs.watch){
$scope.$watch(function(){ return $attrs.dynamicInput; }, function(newValue){
changeType(newValue);
})
}
else
changeType($attrs.dynamicInput);
function changeType(type){
var prev = $element[0].previousSibling;
var parent = $element.parent();
$element.remove().attr('type', type);
if(prev)
angular.element(prev).after($element);
else
parent.append($element);
}
}
});
span {
font-size: .7em;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="MainCtrl">
<h2>Watching Type Change</h2>
Enter Type: <input ng-model="newType" /><br/>
Using Type (with siblings): <span>Before</span><input dynamic-input="{{newType}}" watch="true" /><span>After</span><Br>
Using Type (without siblings): <div><input dynamic-input="{{newType}}" watch="true" /></div>
<br/><br/><br/>
<h2>Without Watch</h3>
Checkbox: <input dynamic-input="checkbox" /><br />
Password: <input dynamic-input="{{ 'password' }}" value="password"/><br />
Radio: <input dynamic-input="radio" /><br/>
Range: <input dynamic-input="{{ rangeType }}" />
</div>
Tested in latest Chrome and IE11.

Resources