Passing values from custom angular directive to other page - angularjs

I've a custom directive that has values stored in an object i want these values from custom directive input and dropdown to be passed as an object to the page where i am referring this directive. this directive values will be passed to the main page when i click apply button which is on my main page, i've tried following but can't get the values from the custom directive in my page where i use this directive. Please suggest how can i pass values from directive to a different page. I need the values from query object in request variable i've declared in a function in my main page controller defined at the last
The custom directive template file metrics-dashboard-configuration.html
<div>
<span>Service Level (SL)</span>
<select ng-model="selection.serviceLevelOption" ng-options="serviceLevelObject.value as serviceLevelObject.text for serviceLevelObject in selection.serviceLevels.values" ng-init="selection.serviceLevelOption='30'"></select>
<br>
<input type="text" ng-model="selection.inputText" />
</div>
Custom directive declaration and controller
function metricsDashboardConfiguration() {
return {
restrict: 'E',
scope: {
query: '='
},
templateUrl: 'metrics-dashboard-configuration.html',
controller: metricsDashboardConfigurationCtrl
};
}
function metricsDashboardConfigurationCtrl($scope) {
$scope.query = {};
$scope.selection = {
serviceLevels: {
values: [
{value : "15" , text : "15"},
{value : "30" , text : "30"},
{value : "45" , text : "45"},
{value : "60" , text : "60"},
{value : "120" , text : "120"}
]
},
inputText: "test"
};
$scope.updateRequest = function() {
$scope.query.inputText = $scope.selection.inputText;
$scope.query.serviceLevels= $scope.selection.serviceLevels;
};
$scope.$watch('selection.inputText', $scope.updateRequest, true);
$scope.$watch('selection.serviceLevels', $scope.updateRequest, true);
The html page where i'm using the directive
<metrics-dashboard-configuration query="queryData" update-Queues-Dashboard="updateQueuesDashboard()"></metrics-dashboard-configuration>
The controller of the page where i need values of the custom directive
$scope.queryData = {
inputText : "",
trailingWindows: []
};
$scope.updateQueuesDashboard = function () {
var request = angular.copy($scope.queryData);
};

The model you have used in your metrics-dashboard-configuration.html file is ng-model="selection.serviceLevelOption" and ng-model="selection.myInput", but in your directive you are watching selection.inputText and selection.trailingWindows
Check this working Plunk and verify what is going wrong in your code.

If you want to use the model bound to the UI elements of the directive in the view where it is being used then, you should make property in isolated scope same way as you have done by passing query in isolated scope.
Something like this:
function metricsDashboardConfiguration() {
return {
restrict: 'E',
scope: {
query: '=',
serviceLevelOption: '=',
inputText: '='
},
templateUrl: 'metrics-dashboard-configuration.html',
controller: metricsDashboardConfigurationCtrl
};
}
HTML
<metrics-dashboard-configuration service-level-option="option" input-text="inText" query="queryData" update-Queues-Dashboard="updateQueuesDashboard()"></metrics-dashboard-configuration>
and then what ever value is being updated from the ui or directive controller it will be reflected in the option and inText variables.

Related

Angular - Bind directive value to controller object

I'm trying to pass an array from a controller to a directive and for some (probably obvious to you lot!) reason when the array values are updated in the controller it does not reflect in the directive. The controller obtains data from a service into an array and I want to pass that array to the directive to create a bar graph. I've put the key parts of the code below.
Here is my top level HTML
<div dash-progress
graph-data="{{dashCtrl.myProgress}}">
</div>
<div>
Other Stuff
</div>
My template HTML for the directive:
<div class="boxcontent" ng-show="dashCtrl.showProgress">
<div class="chart-holder-lg">
<canvas tc-chartjs-bar
chart-data="progress"
chart-options="options"
height="200"
auto-legend>
</canvas>
</div>
</div>
Controller:
angular
.module('myApp')
.controller('dashCtrl',['mySvc',
function(mySvc) {
var self = this;
this.myProgress = [];
this.getProgress = function() {
//logic must be in the service !
mySvc.getProgress().then(function(success) {
self.myProgress = mySvc.progress;
});
};
}]);
and the directive:
angular
.module('myApp')
.directive('dashProgress', [function() {
return {
restrict: 'AE',
templateUrl: 'components/dashboard/progress.html',
scope: {
graphData: '#'
},
link: function(scope,el,attrs) {
scope.progress = {
labels: ['Duration','Percent'],
datasets: [
{
label: 'Duration',
data: [scope.graphData.duration]
},
{
label: 'Percent',
data: [scope.graphData.percent]
}
]
};
scope.options = { };
}
}
}]);
If I set an initial values of the myProgress object in the controller then these do get reflected in the directive, but I don't get the real values that I need when they are returned to the controller from the service.
In your directive's scope, instead of this:
scope: {
graphData: '#'
}
try using this:
scope: {
graphData: '='
}
Don't use {{ }} when passing array to the directive with =. It will render the array in the view instead of passing a reference to directive's scope.
As far as I know, # is not only one-way binding, but also one-time binding and should be used mostly for string values (e.g. setting an html attribute while initializing directive). If you'd like to use #, you should firstly convert data to JSON, then pass it to directive with {{ }}, then parse it again in directive and after any change - manually recompile the directive. But it would be a little overkill, wouldn't it?
Conclusion
Just remove the curly brackets from the view and use = to bind value to directive's scope.
View
<div dash-progress
graph-data="dashCtrl.myProgress">
</div>
Directive
scope: {
graphData: '='
},
Update
Try one more thing. In dashCtrl, wrap myProgress with an object (you can change names to be more self-explaining - this is just an example):
this.graphData = {
myProgress: []
}
this.getProgress = function() {
mySvc.getProgress().then(function(success) {
self.graphData.myProgress = mySvc.progress;
});
}
Then, pass graphData to directive:
<div dash-progress
graph-data="dashCtrl.graphData">
</div>
Finally, substitute every scope.graphData with scope.graphData.myProgress. This way you make sure that scope.graphData.myProgress always refers to the same data because it's a property of an object.
If this still doesn't work, you will probably have to use a watcher and update properties of scope.progress manually.

Angularjs - Pass argument to directive

Im wondering if there is a way to pass an argument to a directive?
What I want to do is append a directive from the controller like this:
$scope.title = "title";
$scope.title2 = "title2";
angular.element(document.getElementById('wrapper')).append('<directive_name></directive_name>');
Is it possible to pass an argument at the same time so the content of my directive template could be linked to one scope or another?
here is the directive:
app.directive("directive_name", function(){
return {
restrict:'E',
transclude:true,
template:'<div class="title"><h2>{{title}}</h3></div>',
replace:true
};
})
What if I want to use the same directive but with $scope.title2?
You can pass arguments to your custom directive as you do with the builtin Angular-directives - by specifying an attribute on the directive-element:
angular.element(document.getElementById('wrapper'))
.append('<directive-name title="title2"></directive-name>');
What you need to do is define the scope (including the argument(s)/parameter(s)) in the factory function of your directive. In below example the directive takes a title-parameter. You can then use it, for example in the template, using the regular Angular-way: {{title}}
app.directive('directiveName', function(){
return {
restrict:'E',
scope: {
title: '#'
},
template:'<div class="title"><h2>{{title}}</h2></div>'
};
});
Depending on how/what you want to bind, you have different options:
= is two-way binding
# simply reads the value (one-way binding)
& is used to bind functions
In some cases you may want use an "external" name which differs from the "internal" name. With external I mean the attribute name on the directive-element and with internal I mean the name of the variable which is used within the directive's scope.
For example if we look at above directive, you might not want to specify another, additional attribute for the title, even though you internally want to work with a title-property. Instead you want to use your directive as follows:
<directive-name="title2"></directive-name>
This can be achieved by specifying a name behind the above mentioned option in the scope definition:
scope: {
title: '#directiveName'
}
Please also note following things:
The HTML5-specification says that custom attributes (this is basically what is all over the place in Angular applications) should be prefixed with data-. Angular supports this by stripping the data--prefix from any attributes. So in above example you could specify the attribute on the element (data-title="title2") and internally everything would be the same.
Attributes on elements are always in the form of <div data-my-attribute="..." /> while in code (e.g. properties on scope object) they are in the form of myAttribute. I lost lots of time before I realized this.
For another approach to exchanging/sharing data between different Angular components (controllers, directives), you might want to have a look at services or directive controllers.
You can find more information on the Angular homepage (directives)
Here is how I solved my problem:
Directive
app.directive("directive_name", function(){
return {
restrict: 'E',
transclude: true,
template: function(elem, attr){
return '<div><h2>{{'+attr.scope+'}}</h2></div>';
},
replace: true
};
})
Controller
$scope.building = function(data){
var chart = angular.element(document.createElement('directive_name'));
chart.attr('scope', data);
$compile(chart)($scope);
angular.element(document.getElementById('wrapper')).append(chart);
}
I now can use different scopes through the same directive and append them dynamically.
You can try like below:
app.directive("directive_name", function(){
return {
restrict:'E',
transclude:true,
template:'<div class="title"><h2>{{title}}</h3></div>',
scope:{
accept:"="
},
replace:true
};
})
it sets up a two-way binding between the value of the 'accept' attribute and the parent scope.
And also you can set two way data binding with property: '='
For example, if you want both key and value bound to the local scope you would do:
scope:{
key:'=',
value:'='
},
For more info,
https://docs.angularjs.org/guide/directive
So, if you want to pass an argument from controller to directive, then refer this below fiddle
http://jsfiddle.net/jaimem/y85Ft/7/
Hope it helps..
Controller code
myApp.controller('mainController', ['$scope', '$log', function($scope, $log) {
$scope.person = {
name:"sangeetha PH",
address:"first Block"
}
}]);
Directive Code
myApp.directive('searchResult',function(){
return{
restrict:'AECM',
templateUrl:'directives/search.html',
replace: true,
scope:{
personName:"#",
personAddress:"#"
}
}
});
USAGE
File :directives/search.html
content:
<h1>{{personName}} </h1>
<h2>{{personAddress}}</h2>
the File where we use directive
<search-result person-name="{{person.name}}" person-address="{{person.address}}"></search-result>
<button my-directive="push">Push to Go</button>
app.directive("myDirective", function() {
return {
restrict : "A",
link: function(scope, elm, attrs) {
elm.bind('click', function(event) {
alert("You pressed button: " + event.target.getAttribute('my-directive'));
});
}
};
});
here is what I did
I'm using directive as html attribute and I passed parameter as following in my HTML file. my-directive="push" And from the directive I retrieved it from the Mouse-click event object. event.target.getAttribute('my-directive').
Insert the var msg in the click event with scope.$apply to make the changes to the confirm, based on your controller changes to the variables shown in ng-confirm-click therein.
<button type="button" class="btn" ng-confirm-click="You are about to send {{quantity}} of {{thing}} selected? Confirm with OK" confirmed-click="youraction(id)" aria-describedby="passwordHelpBlock">Send</button>
app.directive('ngConfirmClick', [
function() {
return {
link: function(scope, element, attr) {
var clickAction = attr.confirmedClick;
element.on('click', function(event) {
var msg = attr.ngConfirmClick || "Are you sure? Click OK to confirm.";
if (window.confirm(msg)) {
scope.$apply(clickAction)
}
});
}
};
}
])

Scope variables are not binded in directive for jQuery plugin

I'm trying to bind 2 values from an input field to a scope variable. First is an input's value (color, written as text), and the second one is an attribute value (opacity value). I want them to change, and, as well, I want so their value be outputted.
myApp.directive( 'watchOpacity', function() {
return {
restrict: 'A',
link: function( scope, element, attributes ) {
scope.$watch( attributes.opacity, function(value) {
console.log( 'opacity changed to', value );
});
}
};
})
Plunker demo
The problem is that neither the input's value, nor the attribute's value is displayed/binded.
Use the change callback jQuery MiniColors provides. It will have hex and opacity passed in as arguments, which you can use to set your scope.data properties.
You need to wrap the setting of those properties in a scope.$apply callback to ensure a digest cycle is run afterwards, so that your view is updated:
.directive( 'watchOpacity', function($timeout) {
return {
restrict: 'A',
require: 'ngModel',
scope: {
watchOpacity: '='
},
link: function( scope, element, attributes, ngModel ) {
$timeout(function(){
element.attr('data-opacity', scope.watchOpacity);
$(element).minicolors({
opacity: true,
defaultValue: ngModel.$modelValue || '',
change: function(hex, opacity) {
ngModel.$setViewValue(hex);
scope.$apply(function() {
scope.watchOpacity = opacity;
})
}
});
});
}
}
})
Using this directive, your view would look like this (ng-init is optional depending upon whether or not you require default values or if you've placed them in the controller):
<input type="text" watch-opacity="data2.opacity" ng-model="data2.color"
ng-init="data2.color = '#0000FF'; data2.opacity = 0.5;" />
Working fork of your demo
Firstly : You've defined data-opacity in your element , while you want to use it as attributes.opacity , which is not even logically correct
Secondly : in your directive , if you want to read the value of your current directive , I mean this :
watch-opacity="data.opacity"
You need to say something like this in your directive :
link:function(scope,element,attributes){
var data_opacity = attributes.watchOpacity// you have written attribute.opacity!
}
I dont know what you are going to do :(

Angular - condition, transclude

I've written a sample directive with a conditional content (component.html):
<div class="panel panel-default">
<div class="panel-heading">{{title}}</div>
<div class="panel-body">
<p ng-show="loadingVisible()" class="text-center">Loading...</p>
<div ng-show="!loadingVisible()" ng-transclude></div>
</div>
Directive code (component.js):
app.directive('component', function () {
return {
restrict : 'A',
transclude : true,
replace : true,
templateUrl : 'component.html',
require : '^title',
scope : {
title : '#',
loadingVisible : '&'
},
controller : [ '$scope', function ($scope) {
if (!$scope.loadingVisible) {
$scope.loadingVisible = function () {
return false;
};
}
} ]
};
});
The main use of this directive is something like this (sample.html):
<div component title="Title">
<div id="t"></div>
</div>
And the controller code (sample.js):
app.directive('sample', function () {
return {
restrict: 'A',
templateUrl: 'sample.html',
controller: [ '$scope', function ($scope) {
$('#id');
} ]
};
});
0
The problem is that the div aquired by using jQuery selector isn't visible. I guess it's because the loadingVisible method (conditional content) hides that div in the construction phase. So when the sample directive tries to get it it fails. Am I doing something wrong? What's the coorect resolution of this problem? Or maybe my knowledge of directives is wrong?
I'll appreciate any help :)
if you're trying to interact with the DOM (or the directive element itself), you'll want to define a link function. The link function gets fired after angular compiles your directive and gives you access to the directives scope, the element itself, and any attributes on the directive. so, something like:
link: function (scope, elem, attrs) {
/* interact with elem and/or scope here */
}
I'm still a little unclear about what your directive is trying to accomplish though, so it's tough to provide much more help. Any additional details?
so if you want to ensure that a title is specified, you can just check for the presence of the title scope var when the directive gets linked, then throw an error if it's not there.
also, is there any reason loadingVisible needs to be an expression? (using '&' syntax). If you just need to show/hide content based on this value, you could just do a normal one-way '#' binding. so overall, something like:
app.directive('component', function () {
return {
restrict : 'A',
transclude : true,
replace : true,
templateUrl : 'component.html',
scope : {
title : '#',
loadingVisible : '#'
},
link : function (scope, elem, attrs) {
if (!scope.title) {
throw 'must specify a title!';
}
if (!attrs.loadingVisible) {
scope.loadingVisible = false;
}
}
};
});
If you need to get access to any of your transcluded content, you can use the elem value that gets injected into your link function, like so:
elem.find('#a');
an (updated) working plnkr: http://embed.plnkr.co/JVZjQAG8gGhcV2tz1ImK/preview
The problem is that directive structure is like this:
<div component>
<div id="a"></div>
</div>
The directive is used somewhere like this:
asd
The test directive uses a "a" element in its controller (or link) function. The problem is that the test controller code is run before the div is transcluded and it cannot see the content :/ A simple workaround is that the component should be before the test directive. Do you have any other solutions to this problem?

Triggering a function with ngClick within ngTransclude

I have an unordered list loaded with four items from an array while using ngRepeat. The anchor tag in the list item has a function in the ngClick attribute that fires up a message. The function call works well when used like this:
<ul>
<li ng-repeat="n in supsNames">
<a ng-click="myAlert(n.name)">{{n.name}}</a>
</li>
</ul>
I created a simple directive for inserting unordered lists with list items. The list is loaded just fine but the same functioned I previously mentioned does not fire up. The code is as follows:
<div list items="supsNames">
<a ng-click="myAlert({{item.name}})">{{item.name}}</a>
</div>
Here is my javascript and angularjs code:
var app = angular.module('myapp', []);
app.controller('myCtrl', function($scope) {
$scope.title = 'ngClick within ngTransclude';
$scope.supsNames = [
{"name" : "Superman"},
{"name" : "Batman"},
{"name" : "Aquaman"},
{"name" : "Flash"}
];
$scope.myAlert = function(name) {
alert('Hello ' + name + '!');
};
});
app.directive('list', function() {
return {
restrict: 'A',
scope: {
items: '='
},
templateUrl: 'list.html',
transclude: true,
link: function(scope, element, attrs, controller) {
console.log(scope);
}
};
});
I also have a plnkr in case you want to see what I tried to do:
http://plnkr.co/edit/ycaAUMggKZEsWaYjeSO9?p=preview
Thanks for any help.
I got the plunkr working. I'm not sure if its exactly what you're looking for. I copied the main code changes below.
Here's the plunkr:
http://plnkr.co/edit/GEiGBIMywkjWAaDMKFNq?p=preview
The modified directive looks like this now:
app.directive('list', function() {
return {
restrict: 'A',
scope: {
items: '=',
ctrlFn: '&' //this function is defined on controller
},
templateUrl: 'list.html',
transclude: true,
link: function(scope, element, attrs, controller) {
//directive fn that calls controller defined function
scope.dirFn = function(param) {
if(scope.ctrlFn && typeof scope.ctrlFn == 'function') { //make sure its a defined function
scope.ctrlFn( {'name': param} ); //not sure why param has to be passed this way
}
}
}
};
});
And here's how it's called in the html file that's bound to your controller:
<div list items="supsNames" ctrl-fn="myAlert(name)">
<a ng-click="dirFn(item.name)">{{item.name}}</a>
</div>
I think what was happening before is that you were trying to use a function defined in your controller within the isolated scope of the directive, so it wasn't working--that function was undefined in the directive. So what I did was added another parameter to the directive that accepts method binding (I think that's what its called) with the '&'.
So basically you pass your controller method to the directive, and that method gets invoked however you want by the directive defined method I creatively named "dirFn". I don't know if this is the best way per se, but I've used it in an existing project with good results ;)
you need to pass the function to the directive
scope: {
items: '=', 'myAlert': '='
},
The ng-repeat inside the template of the directive insert a new scope and it require to call transclude funcion manually to work. I suggest remove ng-repeat and make the transclusion manually passing a copy of the controller scope and setting the item on each copy:
for(var i=0,len=scope.items.length;i<len;i++){
var item=scope.items[i];
var itemScope=scope.$parent.$new();
$transcludeFn(itemScope, function (clone,scope) {
// be sure elements are inserted
// into html before linking
scope.item=item;
element.after(clone);
});
};
I edit the pluker and I hope that could be helpfull: http://plnkr.co/edit/97ueb8SFj3Ljyvx1a8U1?p=preview
For more info about transclusion see: Transclusion: $transcludeFn

Resources