I have 2 nested ng-repeat and boolean, which will hide the loading div, but loading div is hiding before all content is rendered. Boolean depends on a callback function which gets all data.
<tr ng-repeat="package in packages track by $index">
<td> {{ pack.Name }}</td>
<td>
<select ng-hide="package.spinStart" class="form-control" ng-model="package.selectedVersion">
<option ng-repeat="pack in package.allPackageVersions track by $index"
value="{{pack.Version}}"
ng-hide="pack.shouldHide"
ng-disabled="!expertModeOn && pack.shouldDissable"
ng-style="!expertModeOn && pack.shouldDissable && {'color':'#ddd'}">
{{pack.NuGetPackageId}} | {{pack.Version}} | {{pack.Published}}
</option>
</select>
How I call the function when all 2 nested loops are finished?
I tried with $watch statement and directive.
Thanks You!
can you use $last?
<!DOCTYPE html>
<html data-ng-app="myApp">
<head>
<link rel="stylesheet" href="style.css">
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body data-ng-controller="testController">
<div ng-repeat="package in packages">
{{package.name}}
<div ng-repeat=" pkg in package.selectedVersion" ng-init="$last && call()">
{{pkg.name}}
</div>
</div>
<script>
angular
.module('myApp', [])
.run(function($rootScope) {
$rootScope.title = 'myTest Page';
})
.controller('testController', ['$scope',
function($scope) {
$scope.packages = [{
name: 'test1',
selectedVersion: [{
name: 'test_ver1'
}, {
name: 'test_ver2'
}, {
name: 'test_ver3'
}]
}, {
name: 'test2',
selectedVersion: [{
name: 'test_ver1'
}, {
name: 'test_ver2'
}, {
name: 'test_ver3'
}]
}];
var counter = 0
$scope.call = function() {
counter++
if (counter == $scope.packages.length) {
alert("All loops finished");
}
}
}
])
</script>
</body>
</html>
You could use a directive which emits an event when the outer loop finishes.
You can reuse this directive in any loop, just resubscribe to the event.
app.directive('onRepeatFinish', function($timeout) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
// $last is a boolean set by angular inside ng-repeats
if (scope.$last) {
// Wait for view to fully render
$timeout(function() {
// Emit upwards the tree
scope.$emit('repeatFinished');
});
}
}
};
});
In your controller, subscribe to the event:
$scope.$on('repeatFinished', function() {
$scope.showLoader = false;
});
Usage in view:
<tr ng-repeat="package in packages track by $index" on-repeat-finish>
Related
Brief intro to my problem
I have a directive that dynamically shows a list of checkboxes. It has a parameter called options that should be an array like the following, in order to show the list of checkboxes correctly. For example:
var options = [
{
id: 1,
label: 'option #1'
},
{
id: 2,
label: 'option #2'
},
{
id: 3,
label: 'option #3'
}
];
So, by passing this array to my directive, a group of three checkboxes would be shown.
Also, the directive requires ngModel that will have the result of checking/unchecking the checkboxes (this object is always passed initialized). For example:
var result = {
"1": true,
"2": true,
"3": false
};
This case means that the first and second checkboxes (options with id=1 and id=2) are checked and the third (option with id=3) is unchecked.
My directive
template.html
<div ng-repeat="option in options track by $index">
<div class="checkbox">
<label>
<input type="checkbox"
ng-model="result[option.id]">
{{ ::option.label }}
</label>
</div>
</div>
directive.js
angular
.module('myApp')
.directive('myDirective', myDirective);
function myDirective() {
var directive = {
templateUrl: 'template.html',
restrict: 'E',
require: 'ngModel',
scope: {
options: '='
},
link: linkFunc
};
return directive;
function linkFunc(scope, element, attrs, ngModel) {
scope.result;
ngModel.$render = setResult;
function setResult() {
scope.result = ngModel.$viewValue;
};
};
};
What I want to achieve
Wherever I use my directive, I want to be able to trigger a function whenever the ngModel changes. Of course, I would like to achieve this using ngChange. So far I have the following:
<my-directive
name="myName"
options="ctrlVM.options"
ng-model="ctrlVM.result"
ng-change="ctrlVM.selectionChanged()">
</my-directive>
but the .selectionChanged() function is not triggered whenever the model changes. Anyone has any idea why this is not working as I am expecting it to work?
First thing first, please try to provide jsfiddle, codepen etc code snippet link so that it will be easy for others to answer your question.
The problem in your case is that you are never updating the ctrlVM.result object as you are passing the object's reference and that reference never change even if you manually update the model by calling ngModel.$setViewValue().
To solve the problem, just update the model by manually calling ngModel.$setViewValue() and pass in the new Object so that the reference changes and that will trigger the ngChange directives logic.
I've added the logic to do that and it will successfully trigger the change. Look at the code below:
angular
.module('myApp', [])
.directive('myDirective', myDirective)
.controller('MyController', function($timeout) {
var vm = this;
vm.options = [{
id: 1,
label: 'option #1'
}, {
id: 2,
label: 'option #2'
}, {
id: 3,
label: 'option #3'
}];
vm.result = {
"1": true,
"2": true,
"3": false
};
vm.selectionChanged = function() {
vm.isChanged = true;
$timeout(function() {
vm.isChanged = false;
}, 500)
}
});
function myDirective() {
var directive = {
templateUrl: 'template.html',
restrict: 'E',
require: 'ngModel',
scope: {
options: '='
},
link: linkFunc
};
return directive;
function linkFunc(scope, element, attrs, ngModel) {
scope.result;
ngModel.$render = setResult;
function setResult() {
scope.result = ngModel.$viewValue;
};
scope.updateValue = function(val) {
ngModel.$setViewValue(Object.assign({}, val))
}
};
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app="myApp">
<script type="text/ng-template" id="template.html">
<div ng-repeat="option in options track by $index">
<div class="checkbox">
<label>
<input type="checkbox"
ng-model="result[option.id]" ng-click="updateValue(result)">
{{ ::option.label }}
</label>
</div>
</div>
</script>
<div ng-controller="MyController as ctrlVM">
<my-directive name="myName" options="ctrlVM.options" ng-model="ctrlVM.result" ng-change="ctrlVM.selectionChanged()">
</my-directive>
<div> Data: {{ctrlVM.result}} </div>
<div> isChanged: {{ctrlVM.isChanged}} </div>
</div>
</div>
#Gaurav correctly identified the problem (ng-change is never called because the object reference does not change). Here is a simpler solution that doesn't require manually cloning into the controller's model:
Add a binding for the ng-change attribute:
scope: {
options: '=',
ngChange: '&' // Add this, creates binding to `ctrlVM.selectionChanged()`
}
Add an ng-change to your checkbox template:
<input type="checkbox"
ng-model="result[option.id]" ng-change="ngChange()">
Now, when any checkbox changes it will automatically call the outer ng-change function without the intermediate step of cloning into the model.
Here is my custom directive code
(function () {
var app = angular.module('CustDirMod', []);
var custdirCtrl = function ($scope) {
$scope.Person = {
Name: 'Jagan868',
address: {
street: '10 Donwstreet',
city: 'North Avenue',
state: 'Los Angeles'
},
friends: [
'Friend1',
'Friend2',
'Friend3'
]
};
};
var custDirectivewithBinding = function () {
return {
templateUrl: "Friends.html",
restrict: "E",
controller: function ($scope) {
$scope.KnightMe = function (Person) {
Person.rank = "Knight";
}
}
}
};
app.controller('CustDirCtrl', custdirCtrl);
app.directive("custDirectiveBinding", custDirectivewithBinding);
})();
and here is my template html
<div class="panel panel-primary">
<div class="panel-heading">
{{ Person.Name }}
</div>
<div class="panel-body">
<div ng-show='!!Person.address'>
<h4>Address :
</h4>
{{Person.address.street}}
<br />
{{Person.address.city}}
<br />
{{Person.address.state}}
</div>
<h4>Friends :</h4>
<br />
<ul>
<li ng-repeat='friend in Person.friends'>
{{friend}}
</li>
</ul>
<div ng-show="!!Person.rank">
Rank : {{Person.rank}}
</div>
<button ng-show="!Person.rank" class="btn btn-success" ng-click="KnightMe(Person)">Knight Me</button>
</div>
</div>
Now the following final html page where i'm using the above custom directive
<!DOCTYPE html>
<html ng-app="CustDirMod">
<head>
<title>Simple Directives - Angularjs</title>
<script src="Scripts/jquery-3.1.1.js"></script>
<link href="Content/bootstrap.css" rel="stylesheet" />
<script src="Scripts/bootstrap.js"></script>
<script src="Scripts/angular-1.5.8.js"></script>
<script src="Scripts/CustomDirective.js"></script>
</head>
<body ng-controller="CustDirCtrl" class="container" style="padding-top:30px;">
<cust-directive-binding></cust-directive-binding><br /><br />
</body>
</html>
Now i tried to add isolated scope in my custom directive as follows
var custDirectivewithBinding = function () {
return {
templateUrl : "Friends.html",
restrict: "E",
scope: {
userdata: "="
},
controller: function($scope){
$scope.KnightMe = function (Person) {
Person.rank = "Knight";
}
}
}
};
and then in the html page as follows
<body ng-controller="CustDirCtrl" class="container" style="padding-top:30px;">
<cust-directive-binding userdata="Person"></cust-directive-binding><br /><br />
</body>
After adding the isolated scope named as 'userdata' i'm not getting any data in UI. But if i remove that 'userdata' isolated scope from both js & html file its working fine. How to resolve this issue.
P.S: I don't want name the isolated scope local property name same as "Person". I just want it to be something different so that i can distinguish easily.
You don't have a scope property Person in the directive , you renamed it to userdata when you created the isolated scope.
You either need to change the template to now use userdata instead of Person or change the name of userdata to Person so the template will work
scope: {
userdata: "="
}
// in view
{{ userdata.Name}}
Or
<cust-directive-binding Person="Person">
scope: {
Person: "="
}
// in view
{{ Person.Name}}
Because now inside your directive template data will be available with isolated scope variable userdata. To fix the issue you could use userdata instead of Person every where on template. But instead of doing that I'd suggest you to use alias on isolated scope like Person: "=userdata". Where it says userdata will be attribute inside directive data will be available with Person name.
scope: {
Person: "=userdata"
},
I have a Kendo model instance (person for this example) and watching it is modified or not by using dirty property.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Kendo + Angular</title>
<link rel="stylesheet" href="http://kendo.cdn.telerik.com/2016.2.714/styles/kendo.common.min.css">
<link rel="stylesheet" href="http://kendo.cdn.telerik.com/2016.2.714/styles/kendo.rtl.min.css">
<link rel="stylesheet" href="http://kendo.cdn.telerik.com/2016.2.714/styles/kendo.default.min.css">
<link rel="stylesheet" href="http://kendo.cdn.telerik.com/2016.2.714/styles/kendo.mobile.all.min.css">
<script src="http://code.jquery.com/jquery-1.12.3.min.js"></script>
<script src="http://kendo.cdn.telerik.com/2016.2.714/js/angular.min.js"></script>
<script src="http://kendo.cdn.telerik.com/2016.2.714/js/jszip.min.js"></script>
<script src="http://kendo.cdn.telerik.com/2016.2.714/js/kendo.all.min.js"></script>
</head>
<body>
<div ng-app="app" ng-controller="MainCtrl">
<div>Person name: {{ person.name }}</div>
<input type="text" name="name" ng-model="person.name"> <!-- This input don't work -->
<button ng-click="foo()">Foo</button> <!-- This button work because I call person.set method manually -->
<div>This person is modified? {{ person.dirty }}</div>
</div>
<script>
var Person = kendo.data.Model.define({
id: "personId", // the identifier of the model
fields: {
"name": {
type: "string"
},
"age": {
type: "number"
}
}
});
angular.module("app", ["kendo.directives"])
.controller("MainCtrl", function ($scope) {
$scope.person = new Person({
name: "John Doe",
age: 42
});
$scope.foo = function () {
$scope.person.set('name', "Kendo");
}
});
</script>
</body>
</html>
But when I type to text box dirty don't change because Angular ngModel doesn't fire Kendo "change" event. My real app have dozens of models like this, so is there any way to fix this automatically???
Thanks.
You can write a directive to replace for ng-model,
<input type="text" name="name" k-bind-model="person.name">
angular.module('app')
.directive("kBindModel", ["$parse", function ($parse) {
return {
restrict: "A",
scope: false,
link: function (scope, element, attributes, controller) {
var key = null;
var strs = attributes.kBindModel.split('.');
if (strs && strs.length > 1) {
key = strs[1];
}
var model = scope[strs[0]];
element.change(function () {
scope.$apply(function () {
model.set(key, element.val());
});
});
scope.$watch(attributes.kBindModel, function (n, o) {
element.val(n);
});
}
}
}]);
I want to write treeview using angularjs. I am using ng-include for recursive call..everything is fine except from ng-click..when each node is clicked..the hierarchy call is from child to it's parents and for every node in this hierarchy the ng-click fires. how can i solve this problem??..I have this exact problem using another approach (appending element on post-link which I think is not a good way) instead of ng-include.here is my code:
index.html:
<!doctype html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-rc.0/angular.min.js"></script>
</head>
<body >
<div ng-app="app" ng-controller='AppCtrl'>
<ul>
<li ng-repeat="category in categories" ng-click='nodeSelected(category)' ng-include="'template.html'"></li>
</ul>
</div>
<script src="controller.js"></script>
</body>
</html>
template.html:
{{ category.title }}
<ul ng-if="category.categories">
<li ng-repeat="category in category.categories" ng-click='nodeSelected(category)' ng-include="'template.html'">{{ category.title }}</li>
</ul>
controller.js
var app = angular.module('app', []);
app.controller('AppCtrl', function ($scope) {
$scope.nodeSelected = function(category){
alert('This node is selected' + category.title);
}
$scope.categories = [
{
title: 'Computers',
categories: [
{
title: 'Laptops',
categories: [
{
title: 'Ultrabooks'
},
{
title: 'Macbooks',
categories:[
{
title:'Paridokht'
},
{
title:'Shahnaz',
categories:[
{
title:'Sohrab'
}
]
}
]
}
]
},
{
title: 'Desktops'
},
{
title: 'Tablets',
categories: [
{
title: 'Apple'
},
{
title: 'Android'
}
]
}
]
},
{
title: 'Printers'
}
];
});
here is the output picture:
for example when paridokht node is selected, the alert hierarchy in order is paridokht,macbooks,laptops,computers (from child to parents). please help me to solve this issue. it's killing me! :(
Try stopping event from bubble-ing up in the DOM tree.
In you ng-click:
ng-click='nodeSelected($event, category)'
In your controller:
$scope.nodeSelected = function($event, category){
$event.stopPropagation();
alert('This node is selected' + category.title);
}
I am using & (expression binding) operator in isolated scope of a directive but I am unable to trigger function on the parent controller . There should be output on the console but I am receiving none.
Here in the HTML part:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Directive &</title>
<script type="text/javascript" src="angular.min.js"></script>
<script src="isolate_scope_&.js"></script>
</head>
<body ng-app="isolate_scope">
<div ng-controller="isolateScopeController">
<b>Ctrl Data</b>
<div ng-repeat="person in persons">
<p>{{person.name}}</p>
</div>
<b>Directive Data</b>
<div ng-repeat="person in persons">
<friends frnd="person.name"></friends>
</div>
<my-button isolatedFunction="printScopeToFile()"></my-button>
</div>
</body>
</html>
Here goes the JS part :
angular.module('isolate_scope', [])
.controller('isolateScopeController', function($scope){
$scope.persons = [
{
name:"tanmay",
age:"28"
},
{
name: "James",
age:"28"
},
{
name:"Rylan",
age:"26"
},
{
name:"Aditya",
age:"23"
}
];
$scope.printScopeToFile = function(){
console.log("printng to file.....");
for(var i in $scope.persons){
console.log("Name = " + $scope.persons[i].name + " Age = " + $scope.persons[i].age);
}
};
})
.directive('friends',function(){
return {
restrict :'E',
template: '<input type="text" ng-model="name">',
scope :{
name:'=frnd'
}
};
})
.directive('myButton',function(){
return {
restrict: 'E',
template: '<button ng-click="isolatedFunction()">callParentfunction</button>',
scope : {
isolatedFunction:"&"
}
};
});
Fiddle for the same: http://jsfiddle.net/v51kob1q/
The attribute isolatedFunction in your <my-button isolatedFunction...> directive should need to be isolated-function, dash-delimited attributes