AngularJS: All elements using directive show same value - angularjs

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

Related

How to control invoking of directive

In this plnkr :
https://plnkr.co/edit/F0XsOPZKq5HArFo9vtFs?p=preview
I'm attempting to prevent a custom directive being invoked by the use of ng-show. But if check console output when the directive is invoked 4 times : console.log('invoked') But ng-show shows/hides html elements it does not control what is rendered within the custom directive itself.
Is there a mechanism to pass the ng-show to the custom directive and if it's false then do call the directive ? I think could pass a new variable to the directive which contains same value as ng-show and then wrap the body of the directive in a conditional ?
src :
goob.html :
goob
http-hello2.html:
2. http-hello2.html
index.html :
<!doctype html>
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="FetchCtrl">
<label>Filter: <input ng-model="search"></label>
<div ng-show="false">
<div ng-repeat="sourceUrl in sourceUrls | filter:search track by $index ">
<status-viewer url="sourceUrl"> </status-viewer>
</div>
</div>
</div>
</body>
</html>
mytemplate.html :
<!--<h1>{{url}}</h1>-->
<div>
<p>{{model}}</p>
</div>
script.js :
var myapp = angular.module('app', []).controller('FetchCtrl', FetchCtrl)
myapp.directive('statusViewer', function ($http , $interval) {
return {
restrict: 'E',
templateUrl: 'mytemplate.html',
scope: {
url: '='
},
link: function (scope, elem, attrs, ctrl) {
console.log('invoked')
scope.isFinishedLoading = false;
$http.get(scope.url).success(function (data) {
scope.model = data;
});
}
};
});
function FetchCtrl($scope, $http, $q , $parse) {
$scope.sourceUrls = [
'http-hello2.html'
,'http-hello2.html'
,'test.html'
,'goob.html'];
}
test.html :
test
Instead of ng-show you should use ng-if directive to avoid directive linking before show
Forked plunker example

How to write directive to hide div clicking anywhere on page?

This is first time that I am writing directive. I am trying to write directive to hide my div.This is my html:
<div id="loggedIn" close-logged-in class="fade-show-hide" ng-show="loggedInOpened" ng-cloak>
#Html.Partial("~/Views/Shared/_LoggedInPartial.cshtml")
</div>
I find element but when I click anywhere on page I dont get anything.Can someone help me how to write directive so that when shown user click anywhere on page, it will hide that div.Any suggestion?
'use strict';
angular.module("accountModule").directive('closeLoggedIn', function () {
return {
scope: {},
restrict: 'A',
link: function (scope, element, attrs) {
var loggedIn = angular.element(document.getElementById("loggedIn"));
console.log(loggedIn);
var isClosed = false;
loggedIn.on('click', function (e) {
console.log("LOGGED IN ON CLICK ", loggedIn);
});
}
}
I dont get this message "LOGGED IN ON CLICK"
You don't need getElementById("loggedIn") The <div> is already there for you as the element argument in the link function. There should rarely be any need to reference elements by their ID's in Angular.
Is this what you are trying to achieve?
DEMO
html
<div hide-when-click-anywhere default-display='block'>Click anywhere to hide me</div>
js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $window) {
$scope.isHidden = false;
});
app
.directive('hideWhenClickAnywhere', function ($window) {
return {
// bind a local scope (i.e. on link function scope) property
// to the value of default-display attribute in our target <div>.
scope: {
defaultDisplay: '#'
},
restrict: 'A',
link: function (scope, element, attrs) {
var el = element[0];
// set default css display value. Use 'block' if
// the default-display attribute is undefined
el.style.display = scope.defaultDisplay || 'block';
angular.element($window).bind('click', function(){
// Toggle display value.
// If you just want to hide the element and
// that's it then remove this if block
if(el.style.display === 'none'){
el.style.display = scope.defaultDisplay || 'block';
return;
}
el.style.display = 'none';
});
}
};
});
Update
Ok after reading you comments I think this might be more what you're trying to achieve;
Take note of this line in the <button> element defined in template.html:
ng-click="contentHidden = !contentHidden; $event.stopPropagation();"
$event.stopPropagation() stops the event from bubbling up and triggering the 'click' listener we have defined on $window in our directive.
DEMO2
app.js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $window) {
$scope.hideOnStart = true;
});
app
.directive('panel', function ($window) {
return {
scope: {
// this creates a new 'isolate' scope
// '=' sets two-way binding between the directive
// scope and the parent scope
// read more here https://docs.angularjs.org/api/ng/service/$compile
hideOnStart: '=',
panelTitle: '#'
},
transclude: true,
templateUrl: 'template.html',
restrict: 'E',
link: function (scope, element, attrs) {
console.log(scope.panelTitle)
// div content is hidden on start
scope.contentHidden = scope.hideOnStart || false;
angular.element($window).bind('click', function(e){
// check if the content is already hidden
// if true then ignore
// if false hide the content
if(!scope.contentHidden){
scope.contentHidden = true;
// we have to manually update the scope
// because Angular does not know about this event
scope.$digest();
}
});
}
};
});
template.html
<div class="panel panel-default">
<div class="panel-heading">
<div class="panel-title">{{panelTitle}}
<button
type="button"
class="close"
ng-click="contentHidden = !contentHidden; $event.stopPropagation();"
aria-label="Close"
>
<span ng-hide="contentHidden">close</span>
<span ng-hide="!contentHidden">open</span>
</button>
</div>
</div>
<div class="panel-body" ng-hide="contentHidden" ng-transclude></div>
</div>
index.html
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<script data-require="angular.js#1.4.x" src="https://code.angularjs.org/1.4.3/angular.js" data-semver="1.4.3"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<div class="container">
<h1>Demo</h1>
<div class="row">
<div class="col-sm-6">
<panel panel-title="Title" hide-on-start="hideOnStart">
<h4>Content...</h4>
<p>foo bar baz</p>
</panel>
</div>
</div>
</div>
</body>
</html>

Angular Accordion Panel Color Change

I have an accordion that dynamically has html controls added to it. I am trying to figure out how to change the color of the accordion's panel to yellow when any of the child controls become dirty; has been touched.
Here is the plnkr code that I have so far. [1]: http://plnkr.co/edit/MdMWysRUEtGOyEJUfheh?p=preview
Layout below.
<html ng-app="app">
<head>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.11.0.js"></script>
<script src="script.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
<link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.2.0/css/bootstrap-theme.cs" rel="stylesheet">
<link href="style.css" rel="stylesheet">
</head>
<body ng-controller="bookcontroller">
<accordion id="accordion_{{$index+((currentPage-1)*20)+1}}" close-others="true">
<accordion-group is-open="isopen" >
<accordion-heading class="container-fluid grey">
Book Hearder
</accordion-heading>
<form name="form">
<div class="form-row" ng-repeat="record in records">
<table>
<tr ng-formfield></tr>
</table>
</div>
</form>
</accordion-group>
</accordion>
</body>
</html>
SCRIPT.JS code
var app = angular.module("app", ['ui.bootstrap']);
app.controller('bookcontroller', ['$scope', function ($scope) {
$scope.records=[
{
RecordId: 91,
Type:'Label',
Label: 'Favoritebook'
},
{
RecordId: 92,
Type: 'Dropdown',
Label: 'Favoritebook',
DDLValue: [{ 'value': '1', 'text': 'HarryPotter' },
{ 'value': '2', 'text': 'StarGate' }]
},
{
RecordId: 93,
Type:'Text',
Label: 'The TextBox'
}]
}
]);
app.directive('ngFormfield', function ($compile) {
return {
link: function (scope, element, attrs) {
if (scope.record.Type == 'Label') {
element.html('<label togglecolor type="label" ng-model="record.DDLValue.value"/>' + scope.record.Label + '</label>');
}
else if (scope.record.Type == 'Text') {
element.html('<td colspan="8">'+scope.record.Label + ': <input togglecolor type="text" name="fname"></td>');
}
else if (scope.record.Type == 'Dropdown') {
element.html('<td colspan="8"><select class="btn btn-default dropdown" togglecolor ng-model=record.DDLValue.value ng-options="obj.value as obj.text for obj in record.DDLValue"></select></td>');
}
$compile(element.contents())(scope);
}
}
});
app.directive('togglecolor', [function(){
return{
restrict: 'A',
link: function(scope, element, attrs, controller){
scope.$watch(function() {return element.attr('class'); }, function(newValue){
debugger;
if (element.hasClass('ng-dirty')) {
element.parent().addClass('toggle-yellow');
} else {
element.parent().removeClass('toggle-yellow');
}
});
}
}
}]);
Any idea how to get this togglecolor directive working?
I think the problem is in the togglecolor directive. It appears that element.parent() is not the element whose color you want to change.
I would recommend selecting the element that you want to change explicitly.
In html, load jQuery and add an id to the element whose colour you want to change:
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
...
<accordion-group id="bookHeader" is-open="isopen" >
In js, use jQuery to select the element by id and change the colour if it is dirty:
if (element.hasClass('ng-dirty')) {
$('#bookHeader').addClass('toggle-yellow');
}
You can use ngClass to accomplish this. Either by attaching an ngChange directive on your forms that affects a variable in the controller.
Attaching an additional watcher adds performance overhead, so you want to avoid that when possible.
Further, it might not make sense to use a table for this form. It looks like you're using it for formatting?

How do I use getterSetter in an angular directive with an isolated scope?

I'm having trouble getting a getterSetter to work in a ng-model directive.
Please, look at this plunker: http://plnkr.co/edit/Tx0nyvvbuKqf1ZpsRTPu?p=preview
If you use the uncommented template, the example behaves as if it didn't understand that getSet() is a function it should call.
If you uncomment the other template (and comment out the first one), the input is connected to the {{ }} as expected.
Why is the getterSetter not working?
Thank you for help!
This is the code:
javascript:
var app = angular.module('app', [])
.controller('ctrl', ['$scope', function($scope) {
$scope.something = "hahaha";
}])
/*
* attributes:
* - value - a variable to store the input value in
*
*/
.directive('inputNumber', ['$log', function( $log ) {
var linker = function( scope, element, attrs) {
$log.info('inputNumber linker called! value = "' + scope.value + '".');
scope.getSet = function( newValue ) {
if( angular.isDefined( newValue ) ) {
scope.value = newValue;
}
return scope.value;
}
}
return {
restrict : 'E',
// template: '<p>Say something! {{ value }}</p><input ng-model="value" ng-model-options="{ getterSetter: false }"></input>',
template: '<p>Say something! {{ getSet() }}</p><input ng-model="getSet" ng-model-options="{ getterSetter: true }"></input>',
link: linker,
scope: {
value: '='
}
};
}])
html:
<!DOCTYPE html>
<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.15/angular.min.js"></script>
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
</head>
<body>
<h1>Hello Plunker!</h1>
<div ng-app="app" ng-controller="ctrl">
<input-number value="something" ></input-number>
</div>
</body>
</html>
The problem here is ngModelOptions is only available in angular 1.3, but you're using angular 1.2.x. Try to modify your script tag to use 1.3.0-rc.0/angular.min.js and it should work.
Your directive's template should be set to the model (in this case 'value').
template: '<p>Say something! {{ getSet() }}</p><input ng-model="value"
ng-model-options="{ getterSetter: true }"></input>'
Demo Plunker

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>

Resources