Angularjs directive within ng-repeat not updating outside controller data - javascript

I have a isolate scope directive that I am using inside ng-repeat, which is iterating over an array from the controller of that template. The template is as follows:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="bootstrap.min.css" />
<script src="angular.min.js"></script>
<script src="script1.js"></script>
</head>
<body ng-app="AddNewTest" ng-controller="AddNewController">
<div class="items" ng-repeat="row in rows">
<add-new-row data="row" index="$index"></add-new-row>
</div>
</body>
</html>
The directive is defined as follows:
angular.module('AddNewTest', []).
directive('addNewRow', function ($timeout) {
return {
controller: 'AddNewController',
link: function (scope, element, attribute) {
element.on('keyup', function(event){
if(scope.index + 1 == scope.rows.length) {
console.log('keyup happening');
$timeout(function () {
scope.rows.push({ value: '' });
scope.$apply();
});
}
})
},
restrict: 'E',
replace: true,
scope: {
index: '='
},
template: '<div class="add-new"><input type="text" placeholder="{{index}}" ng-model="value" /></div>'
}
}).
controller('AddNewController', function ($scope, $timeout) {
$scope.rows = [
{ value: '' }
];
});
But even after adding new row and doing a $apply() the ng-repeat is not rendering the new data added. Please help.
Plnkr Link Here

Pass array of rows to directive as follow:-
scope: {
index: '=',
rows :'='
},
<add-new-row rows="rows" index="$index"></add-new-row>
Working plunker

Each ng-repeat creates an isolated scope than you declare an isolated scope inside your directive that has the same controller as the div. You're swimming in the $scope soup :)
I would personnally make a clean and independent directive with its own controller.
angular.module('AddNewTest', []).
directive('addNewRow', function () {
return {
restrict: 'E',
controller: MyController,
controllerAs: '$ctrl',
template: '{{$ctrl.rows.length}}<div class="add-new"><pre>{{$ctrl.rows | json}}</pre><input type="text" placeholder="0" ng-model="value" /></div>'
}
}).
controller('MyController', MyController);
function MyController($scope) {
var vm = this;
this.rows = [ { value: '' } ];
$scope.$watch("value",function(value){
if(value)
vm.rows.push({ value: value });
});
}
http://plnkr.co/edit/AvjXWWKMz0RKSwvYNt6a?p=preview
Of course you can still bind some data to the directive using bindToController (instead of scope:{}) and if you need an ng-repeat, do it in the directive template directly.

Related

Angular directive scope variable undefined

I am trying to access my scope variables in link but they appear undefined
/** #ngInject */
function tablePagination() {
return {
restrict: 'E',
template: require('./tablePagination.html'),
scope: {
currentPage: '=',
totalPages: '=',
totalCount: '=',
perPage: '='
},
link: function (scope) {
console.log(scope.currentPage, scope) // scope.currentPage is undefined
}
}
}
// controller for authlogin
module.exports = tablePagination
I also tried using # rather than = and changed the binding to using {{}} but its still undefined. I could use $observe but I want to get values for multiple attributes at once to do some computation. Whats the best way to do this?
HTML Code
<table-pagination
current-page="$ctrl.tableMeta.currentPage"
total-count="$ctrl.tableMeta.totalCount"
total-pages="$ctrl.tableMeta.totalPages"
per-page="$ctrl.tableMeta.perPage"></table-pagination>
UPDATE: I wonder if its because the directive is not getting updated values from $ctrl.tableMeta which is coming from API/Async
SOLUTION!: Oh I discovered my mistake was I need to use $watch otherwise it gets the old value which is by default undefined, since its not set async by API yet
scope.$watch('currentPage', () => {
scope.start = (scope.currentPage - 1) * scope.perPage + 1
scope.end = Math.min(scope.currentPage * scope.perPage, scope.totalCount)
})
Its just an example i hope it will clear few things up.
gridPagination.html
<label>current Page:</label><span>{{ currentPage }}</span>
<br>
<label>Total Pages:</label> {{ totalPages }}
app.js
var app = angular.module("myApp", []);
mainController.js
app.controller('mainController', ['$scope', function($scope) {
$scope.title = 'My Grid';
}]);
gridDirective.js
app.directive('grid', gridPagination);
function gridPagination() {
return {
restrict: 'E',
scope: {
currentPage: '=',
totalPages: '=',
totalCount: '=',
perPage: '='
},
templateUrl: 'gridPagination.html',
link: function(scope, element, attrs) {
console.log(scope.currentPage);
console.log(scope.totalPages);
console.log(scope.totalCount);
console.log(scope.perPage);
}
};
};
index.html
<html>
<head>
<link rel="stylesheet" href="main.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular.js"></script>
</head>
<body ng-app="myApp">
<div ng-controller="mainController">
<grid current-page="3" total-pages= "30" total-count="10" per-page="2"></grid>
</div>
<script src="app.js"></script>
<script src="mainController.js"></script>
<script src="gridDirective.js"></script>
</body>
</html>
plunker

Angular isolated scope values not visible in template when using replace

I am creating a small app and I have the following directive with the template.
smallgrid.directive.js:
angular.module('myActions')
.directive('smallgrid', ['$rootScope', function($rootScope) {
return {
restrict: "E",
scope: {
actionable: "="
},
controller: function($scope) {
$scope.setLocation = function() {
console.log("yee");
};
}
};
}])
.directive('three', function() {
return {
replace: true,
templateUrl: '/app/my_actions/directives/templates/grid3x3.template.html'
};
})
.directive('four', function() {
return {
replace: true,
templateUrl: '/app/my_actions/directives/templates/grid4x4.template.html'
};
})
.directive('five', function() {
return {
replace: true,
templateUrl: '/app/my_actions/directives/templates/grid5x5.template.html'
};
});
grid3x3.template.html
<div class="k-edit-field" id="board">
<div class="row" ng-click="setLocation()">
{{actionable.probability}}
</div>
</div>
I use this directive as follows:
<smallgrid three actionable="currentAction.actionable" ng-if="somecondition"></smallgrid>
The UI renders properly. However it shows {{actionable.probability}} is empty and the Click event is not firing. However, if I remove the isolated scope and access the variable directly, values are available. I understand that when I am using isolated scopes, in the three directive, I can't access values of smallgrid. Is there a way to pass those values from smallgrid to the template?
Passing a directive as an attribute of a directive you're bound to have scope problems.
It will look better if you use scope inheritance for nested directives with ng-transclude.
So your starting point should be
<smallgrid actionable="currentAction.actionable" ng-if="somecondition">
<three></three>
</smallgrid>
This way <three> has access to the $parent
function smallgrid() {
return {
restrict: "E",
transclude: true,
scope: {
actionable: "="
},
template: `<div ng-transclude></div>`,
controller: function($scope) {
$scope.setLocation = function() {
console.log("yee");
};
}
};
}
function three() {
return {
template: `<div class="k-edit-field" id="board">
<div class="row" ng-click="$parent.setLocation()">
test = {{$parent.actionable.probability}}
</div>
</div>`
};
}
function myController($scope) {
$scope.currentAction = {actionable: {probability: "test"}};
$scope.somecondition = true;
}
angular.module('myApp', []);
angular
.module('myApp')
.controller('myController', myController)
.directive('smallgrid', smallgrid)
.directive('three', three);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="myController">
<smallgrid actionable="currentAction.actionable" ng-if="somecondition">
<three></three>
</smallgrid>
</div>
</div>

getting data in child directive from parent directive in Angular JS

I have a parent directive in which its controller makes a call through a service to get some data.
sharedService.getData(options).then(function(data){
$scope.data = data;
});
Now i need this data in my child controller.
What i have already tried are the ff:
1) Through $timeout i get the data after sometime but it doesn't seem a good solution impacting performance
2) watchCollection() - i watched if newValue !== oldValue
problem being the data is huge so it takes a toll of performance
Now the issue i'm getting is the child directive gets executed after parent BUT before the data comes back from the service and i'm not able to get that data in my child directive via $scope.data.
Is there any solution to get data from parent directive to child directive when i have to wait for data to come in parent?
You can include your parent directive controller in your child directive by using require.
angular.module('myApp', [])
.directive('dirParent', function() {
return {
restrict: 'E',
scope: {},
controller: ['$scope', function($scope) {
}],
};
})
.directive('dirChild', function() {
return {
require: '^dirParent', // include directive controller
restrict: 'E',
scope: {
},
link: function(scope, element, attrs, paretCtrl) {
var data = paretCtrl.getMyData();
}
};
})
It's always a best to use service for communication and and business logic. Here is an example. Please check. This might solve your problem.
// Code goes here
angular.module('app', [])
.factory('messageService', function() {
return {
message: null
}
})
.directive('parentDir', function() {
return {
scope: {}, //isolate
template: '<input type="text" ng-model="PDirInput"/><button ng-click="send()">Send</button>',
controller: function($scope, messageService) {
$scope.send = function() {
messageService.message = $scope.PDirInput;
}
}
}
})
.directive('childDir', function() {
return {
scope: {}, //isolate
template: '<code>{{CDirInput.message}}</code>',
controller: function($scope, messageService) {
$scope.CDirInput = messageService;
}
}
})
<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angular.js#*" data-semver="2.0.0-alpha.31" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<HR/>Parent Directive
<parent-dir></parent-dir>
<br/>
<HR/>Child Directive
<child-dir></child-dir>
<HR/>
</body>
</html>

How to make a variable change in one directive to get reflected in another directve

How do i change the value of bar from directive2 so that it is reflected in directive1
If i make the scope:false it is happening.Is there any other way, to make this happen.(because in the code i am writting , i cannot make scope:false).
My basic requirement is to make one directive to talk to another.
Here you can try the plunkr version of the below code
HTML snippet
<body ng-controller="MainCtrl">
this is directive1: <div directive1></div>.<br/>
this is directive2: <div directive2></div>.
</body>
JS snippet
var app = angular.module('myApp', []);
app.directive('directive1', function() {
return {
restrict: 'A',
replace: true,
template: '<span>{{bar}}</span>'
}
});
app.directive('directive2', function() {
return {
restrict: 'A',
scope:{
},
replace: true,
link:function(s,e,a){
s.bar = "Changed value";
},
template: '<b>{{bar}}</b>'
}
});
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.bar ="original value";
});
You could simply use bar inside your isolate scope, that will do two way binding with your variable which is assigned to bar attribute, That means change inside a directive on bar variable will reflect the changes on controller scope variable.
Markup
<body ng-controller="MainCtrl">
this is directive1: <div directive1></div>.
<br />
this is directive2: <div directive2 bar="bar"></div>.
</body>
Directive
app.directive('directive2', function() {
return {
restrict: 'A',
scope:{
'bar': '=' //<-- Change here
},
replace: true,
link:function(s,e,a){
s.bar = "Changed value";
},
template: '<b>{{bar}}</b>'
}
});
Working Plunkr
I have modified your code in here
Share the variable in both directives by passing it as '=' in the scope, changing it in one directive will change it in the the other.
<body ng-controller="MainCtrl">
this is directive1: <div directive1 bar="bar"></div>.
<br />
this is directive2: <div directive2 bar="bar"></div>.
</body>
var app = angular.module('myApp', []);
app.directive('directive1', function() {
return {
restrict: 'A',
scope:{bar:'='},
replace: true,
template: '<span>{{bar}}</span>'
}
});
app.directive('directive2', function() {
return {
restrict: 'A',
scope:{bar:'='},
replace: true,
link:function(s,e,a){
s.bar = "Changed value";
},
template: '<b>{{bar}}</b>'
}
});
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.bar ="original value";
});
If they don't share the same $scope, the only way is using angularjs events.
In first directive you put:
$rootScope.$on('myEvent', function (event, data) {
$scope.bar = data.bar;
});
In the second one when bar change:
$scope.$emit('myEvent', {bar: bar});
Taking into account that are directives completely unrelated.

Using & bindings in nested AngularJS directives

I'm trying to bind to ng-change on an element created by a directive up through two other directives that wrap it to a method on the controller using & bindings in an isolate scope, but I can't figure out how to get arguments to pass all the way through. Here's a plunk that demonstrates the problem.
In short, I have an HTML structure like this:
<body ng-app="ExampleApp">
<div ng-controller="Controller">
<button ng-click="doSomething('Called directly')">Call Function Directly</button>
<br />
<outer on-outer-model-changed="doSomething('Called from Outer in HTML')"></outer>
</div>
</body>
The controller:
var app = angular.module('ExampleApp', []);
app.controller('Controller', ['$scope',
function($scope) {
$scope.doSomething = function(one, two, three) {
console.log(arguments);
};
}
]);
The outer directive:
app.directive('outer', function($compile) {
return {
restrict: 'E',
scope: {
outerModelChanged: '&onOuterModelChanged'
},
link: function(scope, element, attrs) {
var innerElement = angular.element('<inner></inner>');
innerElement.attr('on-inner-model-changed', 'outerModelChanged(\'Called from Outer\')');
element.after(innerElement);
$compile(innerElement)(scope);
console.log(arguments);
}
}
});
And the inner directive that the outer directive creates:
app.directive('inner', function() {
return {
scope: {
innerModelChanged: '&onInnerModelChanged'
},
restrict: 'E',
template: '<button ng-click="innerModelChanged(\'Called from Inner\')">Call from Inner</button>'
}
});
I understand that I'm getting the output ["Called from Outer in HTML"] because this is hardcoded into the <outer> tag. What I don't understand is how to pass arguments all the way up from the inner directive.
I'm not sure I 100% get what you want to accomplish but this is how you would make the ["Called from Inner"] message appear.
Change the html so the on-outer-model-changed expression does not use a hardcoded string.
<body ng-app="ExampleApp">
<div ng-controller="Controller">
<button ng-click="doSomething('Called directly')">Call Function Directly</button>
<br />
<outer on-outer-model-changed="doSomething(outerParam)"></outer>
</div>
</body>
Then change the outer directive to call outerModelChanged with a parameter. And set the outerParam to the innerParam.
app.directive('outer', function($compile) {
return {
restrict: 'E',
scope: {
outerModelChanged: '&onOuterModelChanged'
},
link: function(scope, element, attrs, controller) {
var innerElement = angular.element('<inner></inner>');
innerElement.attr('on-inner-model-changed', 'outerModelChanged({outerParam:innerParam})');
element.after(innerElement);
$compile(innerElement)(scope);
console.log(arguments);
}
}
});
Finally call the innerModelChanged from the inner directive with the innerParam set to your message.
app.directive('inner', function() {
return {
scope: {
innerModelChanged: '&onInnerModelChanged'
},
restrict: 'E',
template: '<button ng-click="innerModelChanged({innerParam:\'Called from Inner\'})">Call from Inner</button>'
}
});
Here is a plunk to the above code.

Categories