Angular js directive call with controller data binding - angularjs

View
<star-rating ratingValue="ratings" readonly="true"></star-rating>
<div><strong>Rating 1:</strong>{{ratings}}</div>
Controller
app.controller('ProductCtrl', function ($scope, $http, $ionicSlideBoxDelegate, $resource, $state, $stateParams, $rootScope, $compile, $ionicPopup, $location, $sce) {
$scope.ratings = 0;
this.isReadonly = true;
this.rateFunction = function(rating) {
console.log('Rating selected: ' + rating);
};
$http.defaults.useXDomain = true;
$http.get(web_service + 'product/get', {
params: {id: $stateParams.ProductId},
headers: {}
}).success(function (response) {
$scope.product = response.product;
console.log(response.product);
$ionicSlideBoxDelegate.update();
$scope.ratings = response.product.rating;
this.rateFunction = function(rating) {
console.log('Rating selected: ' + rating);
};
})
.error(function (err) {
alert("ERROR");
});
}).directive('starRating', starRating);
Directive
function starRating() {
return {
restrict: 'EA',
template:
'<ul class="star-rating" ng-class="{readonly: readonly}">' +
' <li ng-repeat="star in stars" class="star" ng-class="{filled: star.filled}" ng-click="toggle($index)">' +
' <i class="ion-ios-star"></i>' + // or &#9733
' </li>' +
'</ul>',
scope: {
ratingValue: '=?',
max: '=?', // optional (default is 5)
onRatingSelect: '&?',
readonly: '=?'
},
link: function(scope, element, attributes) {
if (scope.max == undefined) {
scope.max = 5;
}
scope.$observe('ratingValue', function(value){
console.log(value);
//$scope.nav.selection = value
});
function updateStars() {
scope.stars = [];
for (var i = 0; i < scope.max; i++) {
scope.stars.push({
filled: i < scope.ratingValue
});
}
};
scope.toggle = function(index) {
if (scope.readonly == undefined || scope.readonly === false){
scope.ratingValue = index + 1;
scope.onRatingSelect({
rating: index + 1
});
}
};
scope.$watch('ratingValue', function(oldValue, newValue) {
if (newValue) {
updateStars();
}
});
}
};
}
When initial value of $scope.ratings is number like 1,2,3 then starts prints but the value retrieved by ajax request is not getting added to directive and in directive values shows "undefined" and no starts getting printed.
The tag below directive in view code gives retrieved value referring to this Codepen: http://codepen.io/TepigMC/pen/FIdHb
What I am missing in directive?

use ng-if so that the directive is called after you have $scope.ratings.
<star-rating ng-if="ratings" ratingValue="ratings" readonly="true"></star-rating>

Related

isolate scope communication from directive to directive?

I am new to AngularJS and got confused with directive concept.
I am updating scope.markers in my second directive via $scope.delete function but changes are not reflecting on first directives,as I am using two way data binding isolate scope, so it should reflect. any solution will be a great help.
I have my first directive as:
app.directive('damageDiagram', function () {
return {
retrict: 'AE',
scope: {
imgsrc: '=', markers: '=', pointsrc: '=', dtype:'='
},
templateUrl: 'app/components/inspections/damage-diagram.html',
link: function (scope, element, attrs) {
}
}
});
and second directive as:
app.directive('damageMarker', function ($mdDialog,inspectionService,$timeout) {
return {
restrict: 'AE',
require: '?^damageDiagram',
scope: {
marker: '=',
pointsrc: '=',
dtype:'=',
markers: '='
},
template: '<img ng-src="{{pointsrc}}" />',
link: function (scope, elem, attr) {
elem.on("click",function(e){
showDialog();
function showDialog($event) {
var parentEl = angular.element(document.body);
$mdDialog.show ({
parent: parentEl,
targetEvent: $event,
template:
'<form name="clientForm" novalidate >'+
'<md-dialog aria-label="List dialog">' +
' <md-dialog-content>'+
'<md-input-container>'+
'<md-select ng-model="dtypeValue" class="dialog-close" placeholder="Select dtype">'+
'<md-option ng-repeat="opt in dtype">{{opt}}</md-option>'+
'</md-select>'+
'</md-input-container>'+
'<md-input-container class="md-block">'+
'<label>Comment</label>'+
'<input required name="name" ng-model="comment" class="dialog-close">'+
'<div ng-messages="clientForm.name.$error">'+
'<div ng-message="required">This is required.</div>'+
'</div>'+
'</md-input-container>'+
' </md-dialog-content>' +
' <div class="md-actions" layout="row" layout-align="end center">' +
' <md-button ng-click="closeDialog()" class="md-primary">' +
' Close' +
' </md-button>' +
'<md-button ng-disabled="clientForm.$invalid" ng-click = "save()" class="md-primary">'+
'Save'+
'</md-button>'+
'<md-button ng-disabled="clientForm.$invalid" ng-click = "delete()" class="md-primary">'+
'Delete'+
'</md-button>'+
' </div>' +
'</md-dialog>'+
'</form>',
controller: DialogController
});
function DialogController($scope, $mdDialog) {
$scope.dtypeValue = scope.dtype[scope.marker.dtype.toUpperCase()];
$scope.dtype = scope.dtype;
$scope.comment = scope.marker.comment;
$scope.marker = scope.marker;
$scope.closeDialog = function() {
$mdDialog.hide();
}
$scope.save = function(){
console.log($scope.marker.id);
console.log($scope.dtypeValue);
console.log($scope.comment);
var dataSend = {};
dataSend.id = $scope.marker.id;
dataSend.comment = $scope.comment;
for(var key in $scope.dtype) {
if($scope.dtype[key] == $scope.dtypeValue) {
dataSend.dtype = key;
}
}
inspectionService.updateDiagram(dataSend).then(function(response){
console.debug("response ; "+response);
$mdDialog.hide();
scope.marker.id = response.id;
scope.marker.comment = response.comment;
scope.marker.dtype = response.dtype;
});
}
$scope.delete = function(){
var dataSend = {};
dataSend.id = $scope.marker.id;
var param = {};
param.inspection=$scope.marker.inspection;
inspectionService.deleteDiagramMarker(dataSend).then(function(response){
inspectionService.getDiagram(param).then(function(response){
$timeout(function() {
scope.$apply(function(){
scope.markers = response.results;
})
},2000);
console.debug("response ; "+response);
$mdDialog.hide();
});
});
}
}
}
});
console.log(scope.marker.top, scope.marker.left, elem);
}
}
});
My html code for damage-diagram directive is as follows:
<damage-diagram imgsrc="imgsrc" pointsrc="pointsrc" dtype="dtype"
markers="inspection.damage_diagram">
</damage-diagram>
and my html code for damage-directive is as follow:
<div style="position:relative">
<img id="23467129" ng-src="{{imgsrc}}" style="position:relative" width="100%" />
<div ng-repeat="marker in markers"
marker="marker"
markers="markers"
dtype="dtype"
pointsrc="pointsrc"
damage-marker>
</div>
</div>
controller is as follows:
app.controller('InspectionDetailCtrl', ['$scope', 'inspectionService', '$stateParams', 'Restangular','$rootScope',
function ($scope, inspectionService, $stateParams, Restangular, $rootScope) {
$scope.updateDamageImage = {};
$scope.insp_id = $stateParams.inspId;
$scope.damageImagesShow = false;
$scope.comments = [];
$scope.types = [];
$scope.selectTypeDelete = false;
$scope.commentDelete = false;
$scope.selectTypeDeleteBefore = true;
$scope.commentDeleteBefore = true;
init($scope.insp_id);
console.log("Fetching details for scope", $scope.insp_id);
function init(insp_id)
{
inspectionService.inspections.customGET(insp_id, {type: 'full'})
.then(function (data) {
$scope.inspection = data;
}, function (err) {
$scope.inspection = null;
});
}
$scope.pointsrc="app/components/inspections/pointer.png";
$scope.dtype = {
'S': 'Scratch (minor)',
'DS': 'Deep Scratch',
'D': 'Dents',
'WD': 'Wheel Damage',
'CW': 'Cracked Window',
'FT': 'Flat Tire',
'BL': 'Broken (lights)'
};
}]);
First of all two way binding in directive doesn't works like that any change reflected in main controller can be seen in directive but not other way any change in directive won't be reflected in main controller.
But there is a solution you can create an object in main controller
var x={};
x.value='to be passed in directive'
then you use same variable in directive since only once instance of object is created so any change in any directive will be reflected everywhere.

How to call controller method from custom directive in AngularJs

UI
<div class="row" ng-app="myModule" ng-controller="multiselect">
<div class="col-md-2">
<div id="lob" ng-dropdown-multiselect="" extra-settings="dropdownSetting" options="lobs" attrfilterprojectsonlob="getProjects"
selected-model="lobsSelected" checkboxes="true">
</div>
</div>
<div class="col-md-2">
<div id="projects" ng-dropdown-multiselect="" extra-settings="dropdownSetting" options="projects"
selected-model="projectsSelected" checkboxes="true">
</div>
</div>
</div>
Angular Controller
var myApp = angular.module('myModule', ['angularjs-dropdown-multiselect']);
myApp.controller('multiselect', ['$scope', '$http', function ($scope, $http) {
$scope.lobsSelected = [];
$scope.lobs = [];
$scope.projectsSelected = [];
$scope.projects = [];
$scope.dropdownSetting = {
scrollable: true,
scrollableHeight: '200px'
}
$http.get('Home/GetALLLOB').then(function (data) {
angular.forEach(data.data, function (value, index) {
$scope.lobs.push({ id: value.N_LevelID, label: value.T_LevelName }
);
});
})
$http.get('Home/GetAllProjects').then(function (data) {
debugger;
angular.forEach(data.data, function (value, index) {
$scope.projects.push({ id: value.N_LevelID, label: value.T_LevelName }
);
});
})
$scope.getProjects = function (selectedId) {
debugger;
var parentId = id;
$http.get('Home/GetAllProjects', { params: { "parentId": selectedId } })
.then(function (data) {
angular.forEach(data.data, function (value, index) {
debugger;
$scope.projects.push({ id: value.N_LevelID, label: value.T_LevelName }
);
});
})
.catch(function (data) {
console.error('Gists error', data.status, data.data);
})
}
}])
Directive : - Please ignore fn_template()
'use strict';
var directiveModule = angular.module('angularjs-dropdown-multiselect', []);
directiveModule.directive('ngDropdownMultiselect', ['$filter', '$document', '$compile', '$parse',
function ($filter, $document, $compile, $parse) {
return {
//restrict: 'AE',
restrict: 'AEC',
scope: {
selectedModel: '=',
options: '=',
extraSettings: '=',
events: '=',
searchFilter: '=?',
translationTexts: '=',
groupBy: '#',
fn_selectAll: '&',
selectedId :'=',
attrfilterProjectsonLob: '&'
},
template: fn_Template,
link: function ($scope, $element, $attrs) {
var $dropdownTrigger = $element.children()[0];
$scope.toggleDropdown = function () {
$scope.open = !$scope.open;
};
$scope.checkboxClick = function ($event, id) {
$scope.setSelectedItem(id);
$event.stopImmediatePropagation();
};
$scope.filterProjectsonLob = function ($event, id) {
debugger;
$scope.selectedId = id;
$scope.attrfilterProjectsonLob({value : selectedId});
}
$scope.externalEvents = {
onItemSelect: angular.noop,
onItemDeselect: angular.noop,
onSelectAll: angular.noop,
onDeselectAll: angular.noop,
onInitDone: angular.noop,
onMaxSelectionReached: angular.noop
};
$scope.settings = {
dynamicTitle: true,
scrollable: false,
scrollableHeight: '300px',
closeOnBlur: true,
displayProp: 'label',
idProp: 'id',
externalIdProp: 'id',
enableSearch: false,
selectionLimit: 0,
showCheckAll: true,
showUncheckAll: true,
closeOnSelect: false,
buttonClasses: 'btn btn-default',
closeOnDeselect: false,
groupBy: $attrs.groupBy || undefined,
groupByTextProvider: null,
smartButtonMaxItems: 0,
smartButtonTextConverter: angular.noop
};
$scope.texts = {
checkAll: 'Check All',
uncheckAll: 'Uncheck All',
selectionCount: 'checked',
selectionOf: '/',
searchPlaceholder: 'Search...',
buttonDefaultText: 'LOB',
dynamicButtonTextSuffix: 'Selected',
LOBSelected: ''
};
$scope.searchFilter = $scope.searchFilter || '';
if (angular.isDefined($scope.settings.groupBy)) {
$scope.$watch('options', function (newValue) {
if (angular.isDefined(newValue)) {
$scope.orderedItems = $filter('orderBy')(newValue, $scope.settings.groupBy);
}
});
}
angular.extend($scope.settings, $scope.extraSettings || []);
angular.extend($scope.externalEvents, $scope.events || []);
angular.extend($scope.texts, $scope.translationTexts);
$scope.singleSelection = $scope.settings.selectionLimit === 1;
function getFindObj(id) {
var findObj = {};
if ($scope.settings.externalIdProp === '') {
findObj[$scope.settings.idProp] = id;
} else {
findObj[$scope.settings.externalIdProp] = id;
}
return findObj;
}
function clearObject(object) {
for (var prop in object) {
delete object[prop];
}
}
if ($scope.singleSelection) {
if (angular.isArray($scope.selectedModel) && $scope.selectedModel.length === 0) {
clearObject($scope.selectedModel);
}
}
if ($scope.settings.closeOnBlur) {
$document.on('click', function (e) {
var target = e.target.parentElement;
var parentFound = false;
while (angular.isDefined(target) && target !== null && !parentFound) {
if (_.contains(target.className.split(' '), 'multiselect-parent') && !parentFound) {
if (target === $dropdownTrigger) {
parentFound = true;
}
}
target = target.parentElement;
}
if (!parentFound) {
$scope.$apply(function () {
$scope.open = false;
});
}
});
}
$scope.getGroupTitle = function (groupValue) {
if ($scope.settings.groupByTextProvider !== null) {
return $scope.settings.groupByTextProvider(groupValue);
}
return groupValue;
};
$scope.getButtonText = function () {
if ($scope.settings.dynamicTitle && ($scope.selectedModel.length > 0 || (angular.isObject($scope.selectedModel) && _.keys($scope.selectedModel).length > 0))) {
//if ($scope.settings.smartButtonMaxItems > 0) {
if ($scope.settings.smartButtonMaxItems >= 0) {
var itemsText = [];
var SelectedTexts = [];
angular.forEach($scope.options, function (optionItem) {
if ($scope.isChecked($scope.getPropertyForObject(optionItem, $scope.settings.idProp))) {
var displayText = $scope.getPropertyForObject(optionItem, $scope.settings.displayProp);
$scope.SelectedTexts = displayText;
var converterResponse = $scope.settings.smartButtonTextConverter(displayText, optionItem);
itemsText.push(converterResponse ? converterResponse : displayText);
}
});
//if ($scope.selectedModel.length > $scope.settings.smartButtonMaxItems) {
// itemsText = itemsText.slice(0, $scope.settings.smartButtonMaxItems);
// itemsText.push('...');
//}
if (itemsText.length <= 2) {
return itemsText.join(', ');
}
else {
var totalSelected;
if ($scope.singleSelection) {
totalSelected = ($scope.selectedModel !== null && angular.isDefined($scope.selectedModel[$scope.settings.idProp])) ? 1 : 0;
} else {
totalSelected = angular.isDefined($scope.selectedModel) ? $scope.selectedModel.length : 0;
}
if (totalSelected === 0) {
return $scope.texts.buttonDefaultText;
} else {
return totalSelected + ' ' + $scope.texts.buttonDefaultText + ' ' + $scope.texts.dynamicButtonTextSuffix;
}
}
}
} else {
return $scope.texts.buttonDefaultText;
}
};
$scope.getPropertyForObject = function (object, property) {
if (angular.isDefined(object) && object.hasOwnProperty(property)) {
return object[property];
}
return '';
};
$scope.selectAll = function () {
$scope.deselectAll(false);
$scope.externalEvents.onSelectAll();
angular.forEach($scope.options, function (value) {
$scope.setSelectedItem(value[$scope.settings.idProp], true);
});
};
$scope.deselectAll = function (sendEvent) {
sendEvent = sendEvent || true;
if (sendEvent) {
$scope.externalEvents.onDeselectAll();
}
if ($scope.singleSelection) {
clearObject($scope.selectedModel);
} else {
$scope.selectedModel.splice(0, $scope.selectedModel.length);
}
};
$scope.setSelectedItem = function (id, dontRemove) {
debugger;
var findObj = getFindObj(id);
var finalObj = null;
if ($scope.settings.externalIdProp === '') {
finalObj = _.find($scope.options, findObj);
} else {
finalObj = findObj;
}
if ($scope.singleSelection) {
clearObject($scope.selectedModel);
angular.extend($scope.selectedModel, finalObj);
$scope.externalEvents.onItemSelect(finalObj);
if ($scope.settings.closeOnSelect) $scope.open = false;
return;
}
dontRemove = dontRemove || false;
var exists = _.findIndex($scope.selectedModel, findObj) !== -1;
if (!dontRemove && exists) {
$scope.selectedModel.splice(_.findIndex($scope.selectedModel, findObj), 1);
$scope.externalEvents.onItemDeselect(findObj);
} else if (!exists && ($scope.settings.selectionLimit === 0 || $scope.selectedModel.length < $scope.settings.selectionLimit)) {
$scope.selectedModel.push(finalObj);
$scope.externalEvents.onItemSelect(finalObj);
}
if ($scope.settings.closeOnSelect) $scope.open = false;
};
$scope.isChecked = function (id) {
if ($scope.singleSelection) {
return $scope.selectedModel !== null && angular.isDefined($scope.selectedModel[$scope.settings.idProp]) && $scope.selectedModel[$scope.settings.idProp] === getFindObj(id)[$scope.settings.idProp];
}
return _.findIndex($scope.selectedModel, getFindObj(id)) !== -1;
};
$scope.externalEvents.onInitDone();
}
};
}]);
function fn_Template(element, attrs) {
var checkboxes = attrs.checkboxes ? true : false;
var groups = attrs.groupBy ? true : false;
var template = '<div class="multiselect-parent btn-group dropdown-multiselect">';
template += '<button type="button" class="dropdown-toggle" ng-class="settings.buttonClasses" ng-click="toggleDropdown()">{{getButtonText()}} <span class="caret"></span></button>';
template += '<ul class="dropdown-menu dropdown-menu-form" ng-style="{display: open ? \'block\' : \'none\', height : settings.scrollable ? settings.scrollableHeight : \'auto\' }" style="overflow: scroll" >';
template += '<li ng-hide="!settings.showCheckAll || settings.selectionLimit > 0"><a data-ng-click="selectAll()"><span class="glyphicon glyphicon-ok"></span> {{texts.checkAll}}</a>';
template += '<li ng-show="settings.showUncheckAll"><a data-ng-click="deselectAll();"><span class="glyphicon glyphicon-remove"></span> {{texts.uncheckAll}}</a></li>';
template += '<li ng-hide="(!settings.showCheckAll || settings.selectionLimit > 0) && !settings.showUncheckAll" class="divider"></li>';
template += '<li ng-show="settings.enableSearch"><div class="dropdown-header"><input type="text" class="form-control" style="width: 100%;" ng-model="searchFilter" placeholder="{{texts.searchPlaceholder}}" /></li>';
template += '<li ng-show="settings.enableSearch" class="divider"></li>';
if (groups) {
template += '<li ng-repeat-start="option in orderedItems | filter: searchFilter" ng-show="getPropertyForObject(option, settings.groupBy) !== getPropertyForObject(orderedItems[$index - 1], settings.groupBy)" role="presentation" class="dropdown-header">{{ getGroupTitle(getPropertyForObject(option, settings.groupBy)) }}</li>';
template += '<li ng-repeat-end role="presentation">';
} else {
template += '<li role="presentation" ng-repeat="option in options | filter: searchFilter">';
}
template += '<a role="menuitem" tabindex="-1" ng-click="setSelectedItem(getPropertyForObject(option,settings.idProp))">';
if (checkboxes) {
template += '<div class="checkbox"><label><input class="checkboxInput" type="checkbox" ng-click="checkboxClick($event, getPropertyForObject(option,settings.idProp)); filterProjectsonLob($event, getPropertyForObject(option,settings.idProp)) "ng-checked="isChecked(getPropertyForObject(option,settings.idProp))" /> {{getPropertyForObject(option, settings.displayProp)}}</label></div></a>';
} else {
template += '<span data-ng-class="{\'glyphicon glyphicon-ok\': isChecked(getPropertyForObject(option,settings.idProp))}"></span> {{getPropertyForObject(option, settings.displayProp)}}</a>';
}
template += '</li>';
template += '<li class="divider" ng-show="settings.selectionLimit > 1"></li>';
template += '<li role="presentation" ng-show="settings.selectionLimit > 1"><a role="menuitem">{{selectedModel.length}} {{texts.selectionOf}} {{settings.selectionLimit}} {{texts.selectionCount}}</a></li>';
template += '</ul>';
template += '</div>';
element.html(template);
}
Comments
---------- .
Please guide me what is wrong i am doing, as controllers method is not getting invoked.
I need to call $scope.getProjects method present in controller from $scope.filterProjectsonLob in directive. Please guide me as i am new to angularJs.
Your directive can have it's own controller:
angular.directive(function() { //blah blah
return {
//restrict: 'AE',
restrict: 'AEC',
scope: {
},
controller: multiselect,
controllerAs: 'vm'
}
});
Then it's a matter of vm.methodNameCall(arguments) as you would normally do.
What's happening here is that your controller is existing on the parent scope of your directive and the directive has an isolate scope so it doesn't see its parent scope's data.
The simplest solution (but not necessarily most correct) is to use $scope.$parent ($scope.$parent.getProjects) from inside your directive to access the controller's scope.
A more correct solution is to refactor your multiselect controller to be a directive. With multiselect as a directive (ex. ngMultiselect), you would be able to inject its controller into other directives. You won't need to change your controller code that much, just make a directive with it as a controller rather than use ng-controller.
Step 1: replace ng-controller with ngMultiselect directive.
directiveModule.directive('ngMultiselect', function(){
return {
restrict: 'AEC',
controller: 'multiselect'
};
});
<div class="row" ng-app="myModule" ng-multiselect> ... </div>
Step 2: update multiselect controller to expose getProjects function
// change:
$scope.getProjects = function (selectedId) {...};
// to:
this.getProjects = function (selectedId) {...};
// and if you still need getProjects on multiselect's scope, you can add $scope.getProjects = this.getProjects;
Step 3: Inject controller into ngDropdownMultiselect by requiring ngMultiselect
directiveModule.directive('ngDropdownMultiselect', ['$filter', '$document', '$compile', '$parse',
function ($filter, $document, $compile, $parse) {
return {
//restrict: 'AE',
restrict: 'AEC',
require: 'ngMultiselect',
scope: {
selectedModel: '=',
options: '=',
// etc ...
},
template: fn_Template,
link: function ($scope, $element, $attrs, MultiselectCtrl) {
$scope.getProjects = MultiselectCtrl.getProjects;
// and the rest of your stuff here...
}
};
}
]);
Learning how to set up and differentiate controllers + directive link functions is certainly one of the finer points of learning angular. I would say it is necessary for building anything beyond simple components. There are lots of tutorials about it, but it can be tricky to really grok until you come across it in your own codings.
Edit:
Just wanted to note that you will need to take a look at which modules you are defining your directives and controller in and set your module dependencies accordingly.

AngularJS $watch newValue is undefined

I am trying to make a alert service with a directive. It is in the directive part I have some trouble. My have a directive that looks like this:
angular.module('alertModule').directive('ffAlert', function() {
return {
templateUrl: 'components/alert/ff-alert-directive.html',
controller: ['$scope','alertService',function($scope,alertService) {
$scope.alerts = alertService;
}],
link: function (scope, elem, attrs, ctrl) {
scope.$watch(scope.alerts, function (newValue, oldValue) {
console.log("alerts is now:",scope.alerts,oldValue, newValue);
for(var i = oldValue.list.length; i < newValue.list.length; i++) {
scope.alerts.list[i].isVisible = true;
if (scope.alerts.list[i].timeout > 0) {
$timeout(function (){
scope.alerts.list[i].isVisible = false;
}, scope.alerts.list[i].timeout);
}
}
}, true);
}
}
});
The reason for the for-loop is to attach a timeout for the alerts that has this specified.
I will also include the directive-template:
<div class="row">
<div class="col-sm-1"></div>
<div class="col-sm-10">
<div alert ng-repeat="alert in alerts.list" type="{{alert.type}}" ng-show="alert.isVisible" close="alerts.close(alert.id)">{{alert.msg}}</div>
</div>
<div class="col-sm-1"></div>
</div>
When I run this, I get this error in the console:
TypeError: Cannot read property 'list' of undefined
at Object.fn (http://localhost:9000/components/alert/ff-alert-directive.js:10:29)
10:29 is the dot in "oldValue.list" in the for-loop. Any idea what I am doing wrong?
Edit: I am adding the alertService-code (it is a service I use to keep track of all the alerts in my app):
angular.module('alertModule').factory('alertService', function() {
var alerts = {};
var id = 1;
alerts.list = [];
alerts.add = function(alert) {
alert.id = id;
alerts.list.push(alert);
alert.id += 1;
console.log("alertService.add: ",alert);
return alert.id;
};
alerts.add({type: "info", msg:"Dette er til info...", timeout: 1000});
alerts.addServerError = function(error) {
var id = alerts.add({type: "warning", msg: "Errormessage from server: " + error.description});
// console.log("alertService: Server Error: ", error);
return id;
};
alerts.close = function(id) {
for(var index = 0; index<alerts.list.length; index += 1) {
console.log("alert:",index,alerts.list[index].id);
if (alerts.list[index].id == id) {
console.log("Heey");
alerts.list.splice(index, 1);
}
}
};
alerts.closeAll = function() {
alerts.list = [];
};
return alerts;
});
try like this , angular fires your watcher at the first time when your directive initialized
angular.module('alertModule').directive('ffAlert', function() {
return {
templateUrl: 'components/alert/ff-alert-directive.html',
controller: ['$scope','alertService',function($scope,alertService) {
$scope.alerts = alertService;
}],
link: function (scope, elem, attrs, ctrl) {
scope.$watch(scope.alerts, function (newValue, oldValue) {
if(newValue === oldValue) return;
console.log("alerts is now:",scope.alerts,oldValue, newValue);
for(var i = oldValue.list.length; i < newValue.list.length; i++) {
scope.alerts.list[i].isVisible = true;
if (scope.alerts.list[i].timeout > 0) {
$timeout(function (){
scope.alerts.list[i].isVisible = false;
}, scope.alerts.list[i].timeout);
}
}
}, true);
}
}
});
if you have jQuery available, try this
if(jQuery.isEmptyObject(newValue)){
return;
}
inside scope.$watch

scope.$on() invokes several times for one broadcast

In Angular.js, scope.$on("UPDATE", function(event, account, vipLabel, nodeName) is firing about 20 times when it should only invokes once for each $rootScope.$broadcast("UPDATE", this.account,this.vipLabel, this.nodeName). I know that scope.$on() contains an event object, but I haven't been able to find a use for it to help my issue.
I am stuck on this and I need the scope.$on() to fire only once for each broadcast.
angular.module('main.vips')
.factory("StatusTrackerService", function($timeout, $rootScope, Restangular) {
var CHECK_ITERATIONS = 1;
var TIME_ITERATION = 1000;
var eventCollection = {};
eventCollection.vipLabel;
function urlBuilder(account, eventId) {
var account = Restangular.one("account", account);
var eventId = account.one("event", eventId);
return eventId;
}
function NewStatusEvent(account, eventId, url, vipLabel, nodeName) {
this.iteration = 0;
this.account = account;
this.eventId = eventId;
this.url = url;
this.vipLabel = vipLabel;
this.nodeName = nodeName;
}
NewStatusEvent.prototype.runCheckLoop = function() {
this.url.get()
.then(function(data) {
if (data.Automation.Status != "SUCCESS") {
console.log("yes");
console.log(this.iteration);
console.log(CHECK_ITERATIONS);
if (this.iteration < CHECK_ITERATIONS) {
this.iteration++;
$rootScope.$broadcast("UPDATE", this.account,
this.vipLabel, this.nodeName);
console.log('-------------------');
console.log('checking ' + JSON.stringify(this.url));
console.log('iteration ' + this.iteration);
console.log('-------------------');
$timeout(function (){
this.runCheckLoop();
}.bind(this), TIME_ITERATION);
}
}
}.bind(this));
}
function runEventCheck(account, eventId, nodeName) {
url = urlBuilder(account, eventId);
eventCollection[eventCollection.vipLabel] = new NewStatusEvent(account, eventId,
url, eventCollection.vipLabel, nodeName);
eventCollection[eventCollection.vipLabel].runCheckLoop();
}
return { runEventCheck: runEventCheck,
eventCollection: eventCollection
};
});
Directive:
return {
templateUrl: "vips/directives/action_button.html",
restrict: "AE",
replace: true,
transclude: false,
scope: {
label: "#?",
icon: "#?",
type: "#?",
actions: "=?"
},
link: function(scope, element, attrs) {
element.on("click", function(event) {
var vipLabel = event.currentTarget.id;
StatusTrackerService.eventCollection.vipLabel = vipLabel;
});
scope.$on("UPDATE", function(event, account,
vipLabel, nodeName) {
console.log("Scope.on has fired this many times");
var nodeSelector = "#node-"+vipLabel+"-"+nodeName;
var nodeElement = angular.element(document.querySelector(nodeSelector));
if (!nodeElement.length) {
var vipElement = angular.element(document.querySelector('#vipLabel-'+vipLabel));
var elementGlobe = '<div id="'+vipLabel+nodeName+'">Element modified</div>';
vipElement.append(elementGlobe);
}
});
return scope.actions = services[attrs.type];
}
}
});
Logged output:
21 -Scope.on has fired this many times actionButton.js:52
------------------- status-checker.js:35
checking {"id":"0fd6afd9-2367-4a0-a5c9-ff0b3e60cdcf","route":"event","reqParams":null,"$fromServer":false,"parentResource":{"route":"account","parentResource":null,"id":"99006"},"restangularCollection":false} status-checker.js:36
iteration 1 status-checker.js:37
-------------------
Html:
<div action-button type="loadbalancer" label="Actions" icon="glyphicon glyphicon-cog"
actions="actions.loadbalancer"></div>
</div>
<div id="{{vip.label}}" action-button type="vip" icon="glyphicon glyphicon-pencil" actions="actions.vips"
class="pull-right"></div>
<div action-button type="node" icon="glyphicon glyphicon-pencil" actions="actions.nodes"
class="pull-right"></div>
UPDATE:
I added if (scope.type !== "vip") and it worked...now the event fires only once. It doesn't make sense to me because it is the <div id="{{vip.label}}" action-button type="vip" that I am using. It seems like it should be if (scope.type == "vip") instead...but it's the opposite.
scope.$on("UPDATE", function(event, account,
vipLabel, nodeName) {
if (scope.type !== "vip") {
console.log(scope);
var nodeSelector = "#node-"+vipLabel+"-"+nodeName;
var nodeElement = angular.element(document.querySelector(nodeSelector));
console.log(nodeElement.length);
if (nodeElement.length == 0) {
console.log("create node");
var vipElement = angular.element(document.querySelector('#vipLabel-'+vipLabel));
var elementGlobe = '<div id="'+vipLabel+nodeName+'">Element modified</div>';
vipElement.append(elementGlobe);
}
}
});

angularjs directive link function is never invoked

i had created this directive, its mean reason is customize ngGrid module, but it doesn't call the link method if i used inside an element with a ng-controller
<div ng-controller="TrainerEditionController">
<div>Header<br/>
<div id="trainer-grid"
class="grid-style"
data-keyfield="Id"
data-msgrid="gridOptions"
data-geturl="#Url.Action("List", "Trainer")"
data-deleteurl="#Url.Action("Delete","Trainer")">
<div data-item-template>
<div data-field="Name" data-editable>
</div>
<div data-field="$action">
<div class="btn" ng-click="edit()">Edit</div> <div class="btn" ng-click="delete()">Delete</div>
</div>
</div>
</div>
</div> </div>
here is my directive definition:
(function(window, $) {
angular.module('msGrid', ['ngGrid'])
.directive('msgrid', function() {
return {
priority: 3000,
restrict: 'A',
link: function($scope, $http, $element) {
$scope.__onEditEventHandler = null;
$scope.getPageData = function(pageUrl) {
$http.post(pageUrl)
.success(function(data, status) {
if (data.Success === true) {
$scope.myData = data.Result.Data;
$scope.gridOptions.pagingOptions.pages = data.Result.Pages;
$scope.gridOptions.pagingOptions.currentPage = data.Result.CurrentPage;
if (!$scope.$$phase) {
$scope.$apply();
}
} else {
//TODO: customize error notification
alert('Error on request, server application said: ' + data.Error + ' status: ' + status);
}
})
.error(function(data, status) {
//TODO: customize error notification
alert('Error on request, ignoring result status:' + status);
});
};
//set options
$scope.gridOptions = {
data: 'myData',
columnDefs: $element.data('ColumnDefs'),
rowHeight: 50,
headerRowHeight: 39,
enableRowSelection: false,
showFooter: true,
enablePaging: true,
pagingOptions: {
pages: [],
currentPage: 0
}
};
//load data from server
$scope.getPageData($element.data('geturl'));
$scope.onEditEventHandler = function(eventHandler) {
if (eventHandler === undefined) return;
$scope.__onEditEventHandler = eventHandler;
};
$scope.refresh = function(pageIndex) {
var pagingOptions = $scope.gridOptions.pagingOptions;
if (pageIndex === undefined)
pageIndex = 0;
if (pageIndex > pagingOptions.pages.length)
pageIndex = pagingOptions.pages.length;
$scope.getPageData(pagingOptions.pages[pageIndex]);
};
$scope.edit = function(row) {
var msColumnKeyField = $element.data('keyfield');
if (msColumnKeyField == null) return;
//get the key value of row
var keyValue = row.getProperty(msColumnKeyField);
if ($scope.__onEditEventHandler != null)
$scope.__onEditEventHandler(keyValue);
else {
var editUrl = $element.data('editurl');
if (editUrl == null) return;
window.location.replace(editUrl + '/' + keyValue);
}
};
$scope.delete = function(row) {
var msColumnKeyField = $element.data('keyfield');
if (msColumnKeyField == null) return;
//get the key value of row
var keyValue = row.getProperty(msColumnKeyField);
//send post to delete
var postData = {};
postData[msColumnKeyField] = keyValue;
$http.post($element.data('deleteurl'), postData)
.success(function(data, status) {
if (data.Success === true) {
//refresh grid page for new data
var pagingOptions = $scope.gridOptions.pagingOptions;
$scope.getPageData(pagingOptions.pages[pagingOptions.currentPage]);
} else {
//TODO: customize error notification
alert(data.Error);
}
})
.error(function(data, status) {
//TODO: customize error notification
alert('Error on delete, request ignored');
});
};
},
compile: function(tElement, tAttrs) {
//load row definitions
var requiredTemplate = tElement.find('*[data-item-template]');
var myColumnDefs = [];
//load columns templates
requiredTemplate.children('*[data-field]').each(function() {
var self = $(this);
var displayName = self.attr('data-displayName');
var fieldName = self.attr('data-field');
fieldName = (fieldName.indexOf('$') == 0) ? '' : fieldName;
var item = {
aggLabelFilter: '',
cellClass: self.attr('class'),
cellFilter: '',
enableCellEdit: self.attr('data-editable') === undefined,
field: fieldName,
displayName: displayName === undefined ? fieldName : displayName,
sortable: self.attr('data-sortable') != undefined
};
var cellTemplate = self.html().trim();
if (cellTemplate != '') {
cellTemplate = cellTemplate.replace('$value$', '{{row.getProperty(col.field)}}');
cellTemplate = cellTemplate.replace('ng-click="delete()"', 'ng-click="delete(row)"');
cellTemplate = cellTemplate.replace('ng-click="edit()"', 'ng-click="edit(row)"');
item['cellTemplate'] = '<div class="ngCellText" ng-class="col.colIndex()"><span ng-cell-text>' + cellTemplate + '</span></div>';
}
myColumnDefs.push(item);
});
tElement.html('<div ng-grid="gridOptions" class="' + tAttrs.class + '"></div>');
tElement.data('ColumnDefs', myColumnDefs);
},
deleteUrl: ''
};
})
.run([
'$templateCache', function($templateCache) {
$templateCache.put('footerTemplate.html',
'<div ng-show="showFooter" class="ngFooterPanel" ng-class="{\'ui-widget-content\': jqueryuitheme, \'ui-corner-bottom\':jqueryuitheme}" ng-style="footerStyle()">' +
'<div style="text-align: right"><ul class="pagination">' +
'<li ng-repeat="page in pagingOptions.pages"><div class="btn" ng-class="pagingOptions.currentPage==$index ? \'disabled\' :\'\'" ng-click="getPageData(page)">{{$index+1}}</div></li>' +
'</ul></div>' +
'</div>');
}
]);
}(window, jQuery));
if the directive is used in an element without a ng-controller it works fine
Your directive function is not valid.
When you have a compile function your linking function is actually postLink.
Also check the arguments of link function.
From the AngularJS $compile API
var myModule = angular.module(...);
myModule.directive('directiveName', function factory(injectables) {
var directiveDefinitionObject = {
priority: 0,
template: '<div></div>', // or // function(tElement, tAttrs) { ... },
// or
// templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
transclude: false,
restrict: 'A',
scope: false,
controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
controllerAs: 'stringAlias',
require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink(scope, iElement, iAttrs, controller) { ... },
post: function postLink(scope, iElement, iAttrs, controller) { ... }
}
// or
// return function postLink( ... ) { ... }
},
// or
// link: {
// pre: function preLink(scope, iElement, iAttrs, controller) { ... },
// post: function postLink(scope, iElement, iAttrs, controller) { ... }
// }
// or
// link: function postLink( ... ) { ... }
};
return directiveDefinitionObject;
});

Resources