scope variable inside directive is not updating - angularjs - angularjs

I have a simple directive for a table with pagination. The Pagination has a dropdown & a button. User can either select a page in the dropdown or click on the button to navigate.
Say, the dropdown lists page numbers 1, 2, 3 and the selected page is 1. When user clicks on 'Next', the selected value in the dropdown should become 2.
Issue:
When Next is clicked, though the scope variable SelectedPage is seen updated in the console, it is not reflecting in the view.
report.html:
<div>
<ul>
<li>
<select ng-model="$scope.state.SelectedPage" ng-change="ShowPageResult()">
<option ng-repeat="num in PageNumbers value="{{num}}">{{num}}</option>
</select>
</li>
<li ng-click="ShowNextPage();">Next</li>
</ul>
<table>
...//some html
</table>
</div>
directive
app.directive('Report', function () {
return {
restrict: 'AE',
replace: 'true',
templateUrl: '/views/report.html'
};
});
controller:
$scope.state={};
$scope.ShowPageResult = function () {
GetReport($scope.state.SelectedPage);
}
$scope.ShowNextPage = function () {
$scope.state.SelectedPage = $scope.state.SelectedPage + 1;
GetReport($scope.state.SelectedPage);
}
//get report data to bind to table
function GetReport(pageNumber) {
$scope.UserReport = [];
var promise = ReportsFactory.GetData();
promise.then(function (success) {
if (success.data != null && success.data != '') {
$scope.UserReport = success.data;
BindPageNumbers($scope.UserReport[0].TotalRows, pageNumber);
}
},
function (error) {
console.log(error);
});
}
//bind page numbers to dropdown in pagination
function BindPageNumbers(totalRows, selectedPage) {
$scope.PageNumbers = [];
$scope.LastPageNumber = Math.ceil((totalRows / 10));
for (var i = 1; i <= Math.ceil((totalRows / 10)) ; i++) {
$scope.PageNumbers.push(i);
}
$scope.state.SelectedPage = selectedPage; //can see the no. becomes 2 here.
}

My guess:
ng-change="ShowPageResult()" is declared in the directive template and because your directive declares its own scope, then it looks for the definition of ShowPageResult() in the directive's scope and not in the controller.
Therefore the change handler function is null and does nothing.

Try putting SelectedPage in an object. So in your controller
$scope.state = {
SelectedPage: 1
};
You need to initialise the SelectedPage.
Then reference SelectedPage with state.SelectedPage in your controller and in your directive template:
<select ng-model="state.SelectedPage" ng-change="ShowPageResult()">
Because of javascript prototypical inheritance, the SelectedPage in your directive will be different to the one in your controller. See Understanding Scopes
And it's not clear to me why you're binding BindPageNumbers to your directive scope. Firstly, there is no controller in your directive so it never gets called there and there is no call in the template either.
Second, BindPageNumbers is not bound to the parent scope so the directive wont get that function anyway.

Related

Dynamically adding multiple custom directives associated with single controller in angularjs 1

I have an html template attached to a controller and three directives. There are three buttons. On clicking a button a directive is added to the page(using ng-click) like this(the following is in my controller not in directive):
$scope.addFilterDimension = function() {
console.log('CLICKED!!!!!!!')
var divElement = angular.element(document.querySelector('#filterDimension'));
var appendHtml = $compile('<filter-directive></filter-directive>')($scope);
divElement.append(appendHtml);
}
Similarly for other buttons, other directives are added. Now, I can keep adding as many of these directives as I like, which is the use case here.
These directives are basically like forms containing either dropdowns, input boxes or both. The values user selects from the dropdowns or enters in input boxes have to be sent back to the backend to be stored in the DB.
This is one of the directives(others are very similar):
anomalyApp.directive('filterDirective', function() {
return {
restrict: "E",
scope: {},
templateUrl:'filter-dimension.html',
controller: function($rootScope, $scope, $element) {
$scope.dimensionsOld = $rootScope.dimensionsOld;
$scope.dimensions = $rootScope.dimensions;
$scope.selectedDimensionName = $rootScope.selectedDimensionName;
$scope.selectedDimensionValue = $rootScope.selectedDimensionValue;
$scope.extend = $rootScope.extend;
$scope.selectedExtend = $rootScope.selectedExtend;
$scope.isDateField = $rootScope.isDateField;
console.log($scope.dimensions);
$scope.Delete = function(e) {
//remove element and also destoy the scope that element
$element.remove();
$scope.$destroy();
}
}
}
});
Now, in my controller I assign $rootscope to my values which have to be used in the directives and thus catch them in the directive. Example:
$rootScope.dimensions = temp.map(d=>d.dimName);
$rootScope.selectedDimensionName = '';
$rootScope.selectedDimensionValue = '';
And this is how I retrieve my values from added directives:
var retriveValue = function() {
var filtersData = [];
var constraintsData = [];
var variablesData = [];
var ChildHeads = [$scope.$$childHead];
var currentScope;
while (ChildHeads.length) {
currentScope = ChildHeads.shift();
while (currentScope) {
if (currentScope.dimensions !== undefined){
filtersData.push({
filterDimensionName: currentScope.selectedDimensionName,
filterDimensionValue: currentScope.selectedDimensionValue,
filterDimensionExtend: currentScope.selectedExtend,
filterDimensionIsDateFiled: currentScope.isDateField
});
}
if (currentScope.constraintDimensions !== undefined){
filtersData.push({
constraintDimensionName: currentScope.selectedConstraintName,
constraintDimensionValue: currentScope.selectedConstraintValue,
constraintDimensionExtend: currentScope.selectedConstraintExtend,
constraintDimensionVariable: currentScope.selectedConstraintVariable,
constraintDimensionOperator: currentScope.selectedOperator,
constraintDimensionVariableValue: currentScope.constraintVariableValue,
constraintDimensionIsDateField: currentScope.isDateFieldConstraint
});
}
if (currentScope.variableNames !== undefined){
console.log('currentScope.selectedVariableVariable',currentScope.selectedVariableVariable);
filtersData.push({
variableName: currentScope.selectedVariableVariable,
variableOperator: currentScope.selectedVariableOperator,
variableValue: currentScope.variableVariableValue,
variableExtend: currentScope.selectedVariableExtend
});
}
currentScope = currentScope.$$nextSibling;
}
}
return filtersData;
}
This is one of the directive's template:
<div >
<div>
<label>Dimension</label>
<select class = "custom-select custom-select-lg mb-6" ng-model="selectedDimensionName" ng-options="dimension for dimension in dimensions">
<!-- <option ng-repeat="table in tables track by $index">{{table}}</option> -->
</select>
</div>
<div>
<label>Date Field</label>
<input type="checkbox" ng-model="isDateField">
</div>
<div>
<label>Value</label>
<select multiple class = "custom-select custom-select-lg mb-6" ng-model="selectedDimensionValue" ng-options="val for val in ((dimensionsOld | filter:{'dimName':selectedDimensionName})[0].dimValues)"></select>
</span>
</div>
<div>
<label>Extend</label>
<select class = "custom-select custom-select-lg mb-6" ng-model="selectedExtend" ng-options="val for val in extend"></select>
</span>
</div>
<button type="button" class="btn btn-danger btn-lg" ng-click="Delete($event)">Delete</button>
This is in the main html to add the directive:
<div id="filterDimension"> </div>
I know this is not a good way, but please suggest a better one.
Secondly, a new change has to be made where inside one of the directives there will be a button, clicking on which 2 dropdowns(or simply one more directive) will be added and can be added as many times as needed(just like the other directive).
The issue here is this one is a directive inside another directive and I am facing unusual behavior like:
When I add the parent directive it is fine, I add the child directives its fine, but when I add a second parent and try to add its child, they get appended inside the first directive.
Even if I somehow manage to get out of the above point I do not know how to retrieve values from such directives.
PS: I am new in AngularJS or front-end for that matter, the retriveValue() and using rootscope I got from somewhere online.

Passing a parameter in ng-click function

I am trying to pass data to an API for the user e-mail subscription status as "Y"/"N".
In the controller code console.log(a.checked) is undefined. But in regular javascript a onclick event for the input element type="checkbox" has the this.checked to respond correspondingly as true or false. Why is it not happening here in AngularJS?
My html code with angular directive ng-click:
<label for="mail_sub">E-mail subscription</label>
<input id="mail_sub" type="checkbox" name=checkbox ng-click="mailsubscription(this)">
Code of the controller:
.controller("registerCtrl", function($scope, $state, userProcess) {
$scope.mailsubscription = function(a) {
console.log(a);
console.log(a.checked); // console output is: "undefined"
signupinfo = {};
if (a.checked) {
signupinfo.u_mail_subscription = 'Y';
} else {
signupinfo.u_mail_subscription = 'N';
}
console.log(signupinfo);
};
/*$scope.registerProcess = function(signupinfo){
console.log(signupinfo);
userProcess.signup(signupinfo).sucess(function(response){
if(response.status){
}
})
};*/
});
There is no checked defined in your scope. You have do to something like this:
$scope.checked = false
$scope.mailsubscription = function () {
$scope.checked = !$scope.checked;
console.log($scope.checked)
};
Or you can use ngModel directive in your template
<input id="mail_sub" type="checkbox" name=checkbox ng-click="mailsubscription(this)" ngModel="checked">
If you go this way you dont need to toggle the variable checked by your self.

ng-repeat is not refreshed when ng-click method called from directive

I am working on creating reusable directive which will be showing composite hierarchical data .
On first page load, Categories like "Server" / "Software"/ "Motherboard" (items array bound to ng-repeat) would be displayed . If user clicks on "Server" then it would show available servers like "Ser1"/"Ser2"/"Ser3".
html :
<div ng-app="myApp" ng-controller="myCtrl" ng-init="init()">
<ul>
<li ng-repeat="item in items">
<div my-dir paramitem="item"></div>
</li>
</ul>
</div>
Now first time Items are loading, but clicking on any item is not refreshing ng-repeat. I have checked ng-click, "subItemClick" in below controller, method and it is being fired. However the items collection is not getting refreshed.
http://plnkr.co/edit/rZk9cbEJU90oupVgcSQt
Controller:
var myApp = angular.module('myApp', []);
myApp.controller('myCtrl', ['$scope', function($scope) {
$scope.init = function() {
$scope.items = [{iname: 'server',subItems: ['ser1', 'ser2','ser3']}
];
};
$scope.subItemClick = function(sb) {
if (sb.subItems.length > 0) {
var zdupitems = [];
for (var i = 0; i < sb.subItems.length; i++) {
zdupitems.push({
iname: sb.subItems[i],
subItems: []
});
}
$scope.items = zdupitems;
}
};
}])
.directive('myDir', function() {
return {
controller: 'myCtrl',
template: "<div><a href=# ng-click='subItemClick(paramitem)'>{{paramitem.iname}}</a></div>",
scope: {
paramitem: '='
}
}
});
I am expecting items like ser1/ser2 to be bound to ng-repeat on clicking "Server" but it is not happening .
Any help?
I think that onClick is screwing up the method's definition of $scope. In that context, the $scope that renders the ngRepeat is actually $scope.$parent (do not use $scope.$parent), and you're creating a new items array on the wrong $scope.
I realize the jsfiddle is probably a dumbed down example of what you're dealing with, but it's the wrong approach either way. If you need to use a global value, you should be getting it from an injected Service so that if one component resets a value that new value is reflected everywhere. Or you could just not put that onClick element in a separate Directive. What's the value in that?

Use ngAnimate on template in directive

I have this function,
app.directive('movieDetails', MovieDetailsDirectiveFn)
.controller('MovieRowCtrl', ['$scope', '$rootScope', MovieRowCtrlFn]);
function MovieDetailsDirectiveFn() {
return {
restrict: 'E',
scope: {
movie: '='
},
templateUrl: '/tpl.html',
// template: '<div class="movie" style="background-image:url(https://image.tmdb.org/t/p/w1280{{movie.backdrop}})">{{movie.title}}</div>'
}
}
function MovieRowCtrlFn($scope, $rootScope) {
$scope.selectedMovie;
$scope.rowActive = false;
$scope.$on('movieRowActivated', ($event, dataObject) => {
if ($scope.$id != dataObject.targetScopeId) {
$scope.rowActive = false;
}
});
$scope.updateSelectedMovie = function updateVisibleMovieIndexFn(movie) {
$scope.selectedMovie = movie;
$rootScope.$broadcast('movieRowActivated', {targetScopeId: $scope.$id});
$scope.rowActive = true;
console.log ('hello')
}
}
And this html,
<div class="content" ng-repeat="movie in movieGroup" ng-init='$movieIndex = $index'>
<a href='' ng-click='updateSelectedMovie(movie)'>{{ movie.title }}</a>
</div>
<div ng-if='rowActive'>
<movie-details movie='selectedMovie'></movie-details>
</div>
When a user clicks on a button the updateSelectedMovie function triggers and the directive element movie-details is updated with new information.
Check the plunker here http://plnkr.co/edit/Ea1OtRUc1wvC4UNw1sNi?p=preview
What I want to do is animate a fadeOut/fadeIn transition when the movie-details directive element changes content. I've used ngAnimate on ui-router elements, since they get the ng-enter ng-animate classes etc. but that doesn't work with a directive.
For anyone having the same problemn. I "fixed" this by putting a ui-view inside the template that's being loaded by the directive. And fixed a state with it. So now the directive creates a template and goes to another state. The state then places the template inside the freshly created ui-view (from the directive) and now I can animate that element.

AngularJS : How to add a new object into array while using factory and directives

Trying to simulate a hotel cart.
Newbie here
Questions
1. How to add an item to orders when clicked on corresponding Add button
2. Is it correct to use a factory for serving both menuitems for menu directive and orderItems for cart directive
3. On click of add button, where should the to be called add function be written, in the factory or in the directive's controller
4. Is there any way to better this code and its logic?
For those who wish to see the plunkr demo can view the same here
HTML snippet
<menu></menu>
JS snippet
angular.module('myApp',[])
.factory('menuItems',function(){
return {
list:function(){
var items = [{'name':'kabab'},
{'name':'chicken'},
{'name':'egg'},
{'name':'noodles'}];
return items
}
};
})
.factory('cartItems',function(){
var orders = [];
return orders;
})
.directive('menu',function(){
return {
restrict:'E',
template:"<ul><li ng-repeat='item in menuItems'>"+
'{{item.name}}' +
"</li></ul>",
scope:{},
controllerAs:'menuCtrl',
controller:function($scope, menuItems){
console.log("I am in menuDirective and outputting menuItems")
console.log(menuItems);
$scope.menuItems = menuItems.list();
},
link:function(){
}
}
})
.directive('cart',function(){
return{
restrict:'E',
template:"<ul><li ng-repeat='order in cartItems'>"+
'{{order.name}}' +
"</li></ul>",
scope:{},
controller:function($scope,cartItems){
$scope.cartItems = cartItems.list();
},
link:function(){
}}
})
Plunker Demo
I think this is a fine way to do it. Since you need to access the same dataset from multiple directives, it makes sense to me to put the methods and data into a factory.
HTML:
<body ng-controller="MainCtrl">
<menu></menu>
<br />Cart:<br />
<cart></cart>
</body>
JS:
angular.module('plunker',[])
.controller('MainCtrl', function() {
})
.factory('menuItems',function(){
return {
list:function(){
var items = [{'name':'kabab'},
{'name':'chicken'},
{'name':'egg'},
{'name':'noodles'}];
return items
}
};
})
.factory('cartItems',function(){
return {
orders: [],
add: function(item) {
this.orders.push(item)
}
}
//return this.orders;
})
.directive('menu',function(cartItems){
return {
restrict:'E',
template:"<ul><li ng-repeat='item in menuItems'>"+
'{{item.name}}' +
"<button ng-click='addItem(item.name)'>Add</button></li></ul>",
scope:{},
controllerAs:'menuCtrl',
controller:function($scope, menuItems){
console.log("I am in menuDirective and outputting menuItems")
console.log(menuItems);
$scope.menuItems = menuItems.list();
$scope.addItem = function(item) {
cartItems.add(item);
console.log(cartItems.orders)
}
},
link:function(){
}
}
})
.directive('cart',function(){
return{
restrict:'E',
template:"<ul><li ng-repeat='order in cartItems track by $index'>"+
'{{order}}' +
"</li></ul>",
scope:{},
controller:function($scope,cartItems){
$scope.cartItems = cartItems.orders;
},
link:function(){
}}
})
Any methods relating to the orders array should be put in the factory. Your factory should encapsulate all logic related to that particular 'thing'. Keeping the logic out of your directives gives you good abstraction, so if you need to change something, you know where all the logic is kept, rather than spread out in a bunch of directives. So if you want to add an 'empty cart' method, or a 'delete from cart' method, I would put those into the factory, operate directly on the orders array, and return the updated array to whatever is calling the methods.

Resources