Angular Directive needs access to variable in ng-repeat - javascript

<div ng-repeat="campaign in campaigns">
<div progressbar="value"></div>
</div>
Each campaign has it's own unique property called "percent" that I want the directive to be able to access.
myApp.directive('progressbar', function(){
return {
restrict: 'A',
scope: {
'progress': '=progressbar'
},
controller: function($scope, $element, $attrs) {
$element.progressbar({
value: *** I want this to refer to the correct campaign.percent ***
})
}
}
})
I have a feeling I am severely overthinking this one. Any help would be greatly appreciated. Thanks!

You should change HTML in order to pass current campaign object to directive:
<div ng-repeat="campaign in campaigns">
<div progressbar="campaign"></div>
</div>
and then in directive you will be able to access campaign object as $scope.progress:
controller: function($scope, $element, $attrs) {
$element.progressbar({
value: $scope.progress.percent
});
}
Of course you can pass in percent value directly as <div progressbar="campaign.percent"></div> but I think that it makes sense to provide complete campaign object in case you will need some other properties other then percent.

Related

Passing in $scope.$on name parameter as an attribute of an AngularJS directive

I'm trying to create a directive which allows me to pass in an attribute string which I then use as the "name" parameter when subscribing to events using $scope.$on. Essentially, the series of events is this:
An object is broadcasted using $rootScope.$broadcast called 'validationResultMessage', in another controller for example.
I have a directive which has an attribute called "subscription" to which I pass the string 'validationResultMessage'.
That directive passes the value of the "subscription" attribute to its scope and subscribes to it with "$scope.$on".
The problem is, it looks like the value of the attribute is "undefined" at the time everything is evaluated, and so when I try to subscribe using $scope.$on, it actually subscribes me to "undefined" rather than "validationResultMessage"
Here is my directive:
app.directive('detailPane', function () {
return {
restrict: 'E',
scope: {
selectedItem: '=',
subscription: '#',
},
templateUrl: 'app/templates/DetailPane.html', //I'm also worried that this is causing my controller to get instantiated twice
controller: 'DetailPaneController'
};
});
which I then use like this:
<td class="sidebar" ng-controller="DetailPaneController" ng-style="{ 'display': sidebarDisplay }">
<detail-pane
selected-item='validationResult'
subscription='validationResultMessage'/>
</td>
And the controller that I'm trying to pass this attribute into:
app.controller('DetailPaneController', ['$scope', '$http', 'dataService', 'toastr', '$uibModal', '$rootScope', '$attrs', function ($scope, $http, dataService, toastr, $uibModal, $rootScope, $attrs) {
$scope.fetching = [];
$scope.validationResult = null;
$scope.sidebarDisplay = 'block';
console.log('subscription is ', $scope.subscription);
var thisSubscription = $scope.subscription;
//if I hardcode the param as 'validationResultMessage', this works
$scope.$on($scope.subscription, function (event, arg) {
$scope.validationResult = arg;
});
}]);
So another way that I managed to solve this particular issue is to only use the internal DetailPaneController as defined in the directive body. Part of my problem was that I was causing the controller to be instantiated twice by having it as both the parent controller using ng-controller= in my html as well as being defined in the directive body. This way I can just use the straightforward "#" binding and everything gets resolved in the right order. I can even have another directive within my template that I can pass my validationResult into.
The new setup looks like this:
DetailPaneController:
app.controller('DetailPaneController', ['$scope', '$http', function ($scope, $http) {
$scope.$on($scope.subscription, function (event, arg) {
$scope.validationResult = arg;
$scope.exception = JSON.parse(arg.Exception);
});
}]);
DetailPane Directive:
app.directive('detailPane', function () {
return {
restrict: 'E',
scope: {
subscription: '#' //notice I am no longer binding to validationResult
},
templateUrl: 'app/templates/DetailPane.html',
controller: 'DetailPaneController'
};
});
Directive as used in HTML:
<div class="sidebar" ng-style="{ 'display': sidebarDisplay }">
<detail-pane subscription='validationResultMessage' />
</div>
Directive Template (for good measure):
<div class="well sidebar-container">
<h3>Details</h3>
<div ng-show="validationResult == null" style="padding: 15px 0 0 15px;">
<h5 class=""><i class="fa fa-exclamation-triangle" aria-hidden="true" /> Select a break to view</h5>
</div>
<div ng-show="validationResult != null">
<table class="table table-striped">
<tr ng-repeat="(key, value) in validationResult">
<td class="sidebar-labels">{{key | someFilter}}</td>
<td >{{value | someOtherFilter : key}}</td>
</tr>
</table>
<another-directive selected-item="validationResult" endpoint="endpoint" />
</div>
I'm going to post my answer 1st, given that it's a bit of code, please let me know if this is the required outcome, so I can provide comments. You should be able to run the provided code snippet.
var app = angular.module('myApp', []);
app.directive('detailPane', function() {
return {
restrict: 'E',
transclude: false,
scope: {
selectedItem: '=',
subscription: '#'
},
link: function(scope, elem, attr) {
scope.$on(scope.subscription, function(e, data) {
scope.selectedItem = data.result;
elem.text(data.message);
});
},
};
});
app.controller('DetailPaneController', function($scope) {
$scope.validationResult1 = "";
$scope.validationResult2 = "";
});
app.controller('SecondController', function($rootScope, $scope, $timeout) {
$timeout(function() {
$rootScope.$broadcast('validationResultMessage1', {
message: 'You fail!',
result: 'Result from 1st fail'
})
}, 2000);
$timeout(function() {
$rootScope.$broadcast('validationResultMessage2', {
message: 'You also fail 2!',
result: 'Result from 2nd fail'
})
}, 4000);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app='myApp'>
<div ng-controller="DetailPaneController">
<detail-pane class='hello' selected-item='validationResult1' subscription='validationResultMessage1'></detail-pane>
<br/>
<detail-pane class='hello' selected-item='validationResult2' subscription='validationResultMessage2'></detail-pane>
<hr/>
<span>{{validationResult1}}</span>
<br/>
<span>{{validationResult2}}</span>
</div>
<div ng-controller="SecondController">
</div>
</body>
I think you should set watcher on $scope.subscription and checking if new value is set and then start subscribing passed event.
$scope.$watch('subscription', function(nv, ov){
//this makes sure it won't trigger at initialization
if(nv!==ov){
$scope.$on($scope.subscription, function (event, arg) {
$scope.validationResult = arg;
});
}
});

AngularJs-share object array between directives

I am trying to send an array of objects from parent directive to child directive but i am getting console.log($scope.$eval(attrs.model)) as undefined .
angular.module('tester').directive('gChart',gChart);
function gChart() {
var template = getTemplate;
return {
restrict: 'E',
require: "^Logs",
replace:true,
template: template,
scope:{
model: "="
},
link: function($scope, $element, attrs, LogsCtrl) {
console.log($scope.$eval(attrs.model));
LogsCtrl.show($scope.$eval(attrs.model));
}
};
function getTemplate() {
return'<div id="chart" style="width: 600px; height: 500px;margin: 0px auto;"></div>';
}
}
In this situation I would use factory and inject that factory in both directive. Sharing as per coding standard in angular should be done via factory. It will make ur life easy. If u need any help with that let me know but use factory to share data between controller and directive make application more testable also
You need to post all of your code for us to understand. Besides, your array of data should be in $scope.model, and not in the attrs.model. This is because you define the scope of your directive as
scope:{
model: "="
},
So, in your html, you should have something like:
<tester model="YOUR_DATA" ></tester>

Angular: custom directive not updating controller

I started to learn Angular not so long time ago and I'm trying to understand scope, binding and etc.
I have an order details controller:
orderApp.controller('OrderDetailsController', ['$http','$routeParams','$scope','config', function($http, $routeParams, $scope, config){
var orderCtrl = this;
orderCtrl.orderId = $routeParams.orderId;
orderCtrl.order = {};
orderCtrl.editingView = false;
...
}]);
On order details page I want to output all information about selected order. Also we need to give user ability to edit order. Information about editing mode is stored in orderCtrl.editingView.
I decided to create custom directive. If edit mode is off - display text, otherwise display input.
orderApp.directive('editableText', function(){
return {
restrict: 'E',
scope: {
property: '=property',
editMode: '=editMode'
},
controller: 'OrderDetailsController',
controllerAs: 'orderCtrl',
templateUrl: '/pages/editable-text.html'
}
});
This is template:
<div class="col-xs-8" ng-if="!editMode">{{property}}</div>
<div class="col-xs-8" ng-if="editMode"><input type="text" class="form-control" ng-model="property"></div>
And this is how I use directive in html files:
<editable-text property="orderCtrl.order.coid" edit-mode="orderCtrl.editingView"></editable-text>
Text and input are switching when edit mode is on/off. Problem is that orderCtrl.order.coid property is not updated when I change it in input.
Before edit property looks like:
Turn on edit mode and change value:
Turn off edit mode and we see old value:
Do I need to synchronise controller values and directive scope? I thought that with 2-ways binding it should happen automatically. Probably there is any other way to write this functionality? Will appreciate any help.
UPD
Directive code:
orderApp.directive('editableText', function(){
return {
restrict: 'E',
bindToController: {
property: '=property',
editMode: '=editMode'
},
controller: 'OrderDetailsController',
controllerAs: 'orderCtrl',
templateUrl: '/pages/editable-text.html'
}
});
Directive template:
<div class="col-xs-8" ng-if="!orderCtrl.editMode">{{orderCtrl.property}}</div>
<div class="col-xs-8" ng-if="orderCtrl.editMode"><input type="text" class="form-control" ng-model="orderCtrl.property"/></div>
Directive usage:
<editable-text property="orderCtrl.order.coid" edit-mode="orderCtrl.editingView"></editable-text>
I'm not sure that we really need to pass edit-mode attribute.
You should use bindToController: { ..scope properties.. } option here inside your directive to make sure that isolated scope properties should get bounded to controller this context.
Directive
orderApp.directive('editableText', function(){
return {
restrict: 'E',
bindToController: {
property: '=property',
editMode: '=editMode'
},
controller: 'OrderDetailsController',
controllerAs: 'orderCtrl',
templateUrl: '/pages/editable-text.html'
}
});
Template
<div class="col-xs-8" ng-if="!orderCtrl.editMode">
{{orderCtrl.property}}
</div>
<div class="col-xs-8" ng-if="orderCtrl.editMode">
<input type="text" class="form-control" ng-model="orderCtrl.property"/>
</div>
Note:- this above bindToController: { ..scope properties.. } option available for angular 1.4+ versions.
For Angular 1.3 > version & 1.4 > version you should use former way of doing it by having bindingToController: true to bind scope variable to controller context & do keep the varaibles inside scope: { ...props... }
scope: {
property: '=property',
editMode: '=editMode'
},
bindToController: true

How can i change array in directive, and then reflect that change in my controller?

I made directive with isolated scope with "=" method, in that directive i pass empty array, then i push data on that array.... How that change can be reflected on original array in my controller?
Here is the example:
angular.module('myModule').controller('MyController', ['$scope', function($scope) {
$scope.test = [];
}]);
angular.module('myModule').directive('mydirective', function() {
return {
scope: {
test: "=",
bread: "="
},
restrict: 'E',
link: function(scope, element, attribute) {
scope.test.push('one more')
},
replace: true,
templateUrl: 'some template'
};
});
HTML
<div ng-controller='MyController'>
<mydirective test='test'></mydirective>
<div ng-bind='test'> </div>
</div>
When i push something on array i dont have a reflection of that in my controller.
How can i fix that?
Here's how to do what you are trying to achieve.
HTML
<!-- myCtrl contains the original array, which we push here to the scope as 'ctrl' -->
<div ng-controller='myCtrl as ctrl'>
<!-- we pass your directive 'ctrl.test', which tells angular to two-way bind to the
test property on the 'ctrl' object on the current scope -->
<mydirective test='ctrl.test'>
<!-- we're inside the isolate scope: test here refers to mydirective's idea of test,
which is the two-way bound array -->
<div ng-bind='test'></div>
</mydirective>
</div>
JS
angular.module('app', [])
.directive('mydirective', function() {
scope: {
test: '='
},
link: function($scope) {
$scope.test.push('one more');
}
})
.controller('myCtrl', function() {
this.test = [];
});
Any alterations to the array will now be reflected in the ng-bind. Please note that it is bad practice to place primitives on $scope without being part of an object (due to the mechanics of prototypical inheritance) so you'd want to change $scope.test to something else.

ng-repeat inside custom directive

So I'm struggling with passing the scope to my custom directives.
Here's my html
<li ng-repeat="post in posts | filter:search | orderBy:sortField:reverse" class="archives-listing" ng-class="{'last-border':$last}">
<archive-notes></archive-notes>
</li>
Here's my directives
app.directive('archiveNotes', function(){
return {
restrict: 'E',
transclude: true,
scope: {
notes: '#',
paths: '#'
},
controller: function($scope) {
},
templateUrl: '/wp-content/themes/twentythirteen/js/angular/templates/notes.html'
}
})
app.directive('archiveFolders', function(){
return {
require: '^archiveNotes',
restrict: 'E',
transclude: true,
scope: {
path: '#'
},
link: function(scope, element, attrs) {
},
templateUrl: '/wp-content/themes/twentythirteen/js/angular/templates/folders.html'
}
});
here are my templates.
notes.html
<div ng-class="{'found' : find(post.paths[$index], search)}" class="arch-notes">
<div ng-bind-html="is_NotesEmpty(post.notes)">{{post.notes}}</div>
<archive-folders></archive-folders>
</div>
folders.html
<div ng-repeat="path in post.paths | filter:search track by $index" ng-transclude>
<span class="arch-paths">{{path}}</span>
</div>
I left several things blank i.e controller and link because at this point i was just trying to figure how to make everything show up first before i start manipulating the DOM
I followed the example in the angularjs documentation, and it got me this far. I guess cant seem to figure out how to access the scope?
Any help is appreciated.
Based on your templates, it seems that your archiveNotes directive definition should actually look like this:
app.directive('archiveNotes', function(){
return {
...
scope: {
post: '='
},
...
}
})
To get the post variable passed in from ng-repeat's scope, you also need to set the post attribute on the directive's element:
<archive-notes post="post"></archive-notes>
Similarly, you need to set it on the child directive:
app.directive('archiveFolders', function(){
return {
...
scope: {
post: '='
},
...
}
});
... and change your notes.html template:
<archive-folders post="post"></archive-folders>
Isolate scope is sort of like a firewall, where you can set exceptions, in this case on specific scope variables. All we're doing here is setting those exceptions in the directive definitions, then passing them through using the attribute on the elements.
These videos by John Lindquist really shed some light on isolate scope for me:
Understanding Isolate Scope
Isolate Scope '#'
Isolate Scope '='
Isolate Scope '&'

Categories