ANgularjs: ng-repeat and nested custom directive - angularjs

I am trying to ng-repeat a custom directive, which has an attribute that should change over the iteration.
This is my html:
<div ng-controller="WalletsController as controller">
<bitcoin-address ng-repeat="bitcoin_label in controller.getWallets()" bitcoin-label="bitcoin_label"></bitcoin-address>
</div>
This is my controller:
(function() {
var app = angular.module('wallets', [ ]);
app.controller(
"WalletsController",
function($scope, $http) {
this.wallets = [];
var controller = this;
this.getWallets = function() {
return controller.wallets;
};
$http.get("wallet_addresses").success(
function(data) {
for (var i = 0; i < data.length; i++) {
var curWallet = data[i];
$scope[curWallet.label] = {
label: curWallet.label,
address: curWallet.address,
balance: curWallet.balance
};
controller.wallets.push(curWallet.label);
}
}
);
});
app.directive(
'bitcoinAddress',
function() {
return {
restrict: 'E',
templateUrl: '../../resources/html/bitcoin-address.html',
scope: {
bitcoinLabel: '=',
}
};
}
);
})();
And this is my template:
<div class="col-md-8 dashboardAddressCell dropdown-toggle" data-toggle="dropdown">{{bitcoinLabel.address}}</div>
What happens is that the template can not resolve the bitcoinLabel variable. I have tried specifying a constant value and the template works. My conclusion is that I am not correctly specifying the bitcoin_label attribute in the html section.I have also tried using {{bitcoin_address}}, but angularjs complains about that.
I have also tried with the following html code:
<div ng-controller="WalletsController as controller">
<!-- <tr><th>Indirizzo</th><th>Saldo</th><th></th>-->
<div ng-repeat="bitcoin_label in controller.getWallets()">
{{bitcoin_label}}
<bitcoin-address bitcoin-label="bitcoin_label"></bitcoin-address>
</div>
<bitcoin-address bitcoin-label="ciccio"></bitcoin-address>
</div>
It does not work either, but at least it shows the {{bitcoin_label}} value.

The problem seems pretty simple. Instead of
controller.wallets.push(curWallet.label);
you should push corresponding $scope[curWallet.label] object:
controller.wallets.push($scope[curWallet.label]);
Because curWallet.label is just a string so in the first case wallets ends up as array of stings. However you need wallets to be an array of objects, each having address, label, balance properties.

You have some problems with your logic. You're putting wallet labels into .wallets, then iterating over the labels, and then in your bitcoinAddress template you're trying to read .address property of the label string (not from the object where you saved it). Why not simplify the whole thing to this script:
.controller("WalletsController", function($scope, $http) {
$scope.wallets = [];
$http.get("wallet_addresses").success(function(data) {
$scope.wallets = data.slice();
});
})
.directive('bitcoinAddress', function() {
return {
restrict: 'E',
templateUrl: '...',
scope: {
wallet: '=',
}
};
})
this directive template:
<div class="..." ...>{{wallet.address}}</div>
and this body template:
<div ng-controller="WalletsController as controller">
<bitcoin-address ng-repeat="wallet in wallets" wallet="wallet"></bitcoin-address>
</div>

Both bitcoinAddress and ng-repeat directives creating scopes on the same element could cause some conflict (isolate scope in the bitcoinAddress case).
Try adjusting your html structure slightly:
<div ng-controller="WalletsController as controller">
<div ng-repeat="bitcoin_label in controller.getWallets()">
<bitcoin-address bitcoin-label="bitcoin_label"></bitcoin-address>
</div>
</div>

Why not use $scope.wallets instead of this.wallets? Also in your getWallets function. After that try
<div ng-controller="WalletsController">
<div ng-repeat="bitcoin_label in wallets">
<bitcoin-address bitcoin-label="bitcoin_label"></bitcoin-address>
</div>
</div>
But if your wallets is an array of non-object like array of string or integer, use
<div ng-controller="WalletsController">
<div ng-repeat="bitcoin_label in wallets track by $index">
<bitcoin-address bitcoin-label="wallets[$index]"></bitcoin-address>
</div>
</div>

Related

Unable to get content AngularJs dynamic popover inside ngrepeat

I need to display a small popover which should open on click and goaway on clicking anywhere on the page.
I found a plunker (http://plnkr.co/edit/K7cYQSDEBS3cHvDfJNLI?p=preview) which matches this requirement however, unable to get it to work inside ng-repeat.
I saw several answers and Plunker examples but not able to get this to work.
Here is my html
<div ng-controller="TestController">
<div class="row" style="background-color: #ebebeb !Important; ">
<div style="text-align:center">
<table style="width:100% !important;">
<tr ng-repeat="member in TeamMembers" style="font-size:18px !important; height: 108px;">
<td style="display:block;margin-top:30px;text-align:left;"> {{member.FirstName}} {{member.LastName}} <i class="fa fa-info-circle" aria-hidden="true" ng-show="member.Description != null" popover-template="dynamicPopover.templateUrl" popover-placement="bottom" popover-elem descr="{{member.Description}}"></i></td>
</tr>
</table>
</div>
</div>
...
<script type="text/ng-template" id="descriptionModal.html">
<div class="adp-info-dialog">
<div class="modal-body">
<div class="row">
<div class="col-md-8 col-md-offset-1">
<div class="form-group">
<label class="fieldset-label">Test {{ dynamicPopover.descr }}</label>
</div>
</div>
</div>
</div>
</div>
</script>
Here is the JS
testApp.controller('TestController', function ($scope, $rootScope, $log, $modal, SiebelAccountTeamService, $filter, $element) {
$scope.dynamicPopover = {
templateUrl: 'descriptionModal.html',
descr: null
};
var result = TestService.GetTeamMembers();
result.then(function (data) {
$scope.TeamMembers = data.data;
}, function (e) {
console.log(e);
}).finally(function () {
$scope.CompleteLoading();
});
});
testApp.directive('popoverClose', function ($timeout) {
return {
scope: {
excludeClass: '#'
},
link: function (scope, element, attrs) {
var trigger = document.getElementsByClassName('trigger');
function closeTrigger(i) {
$timeout(function () {
angular.element(trigger[0]).triggerHandler('click').removeClass('trigger');
});
}
element.on('click', function (event) {
var etarget = angular.element(event.target);
var tlength = trigger.length;
if (!etarget.hasClass('trigger') && !etarget.hasClass(scope.excludeClass)) {
for (var i = 0; i < tlength; i++) {
closeTrigger(i)
}
}
});
}
};
});
testApp.directive('popoverElem', function () {
return {
scope: {
descr: '#'
},
link: function (scope, element, attrs) {
$scope.dynamicPopover.descr = scope.descr,
alert($scope.dynamicPopover.descr),
element.on('click', function () {
element.addClass('trigger');
});
}
};
});
Appreciate your help.
Update:
To show the data of the ng-repeat inside the popover content, we need to access the individual objects through the $index of the ng-repeat. Refer the below example.
Plunkr Demo
The problem here is that you are using ng-repeat which creates a new scope read more here.
Since replicating the issue with your code is tedious, I tried replicating the issue with the plunkr!
Solution:
Plunkr Demo
You can simply define a new controller inside the descriptionModal.html like so
HTML:
<script type="text/ng-template" id="myPopoverTemplate.html">
<div class="adp-info-dialog" ng-controller="tester">
<div class="modal-body">
<div class="row">
<div class="col-md-8 col-md-offset-1">
<div class="form-group">
<label class="fieldset-label">Test {{ $parent.$parent.dynamicPopover.content }}</label>
</div>
</div>
</div>
</div>
</div>
</script>
JS:
app.controller('tester', function ($rootScope, $scope) {
console.log($scope.$parent.$parent.dynamicPopover.title);
});
Then, we will be able to access the parent scope, using $parent, the html inside the above script uses the $parent to get the variable!
Please note: It took me two $parent to reach the required $scope to access the scope variable. In your scenario it will also require two, the way to check how many is needed is use console.log($scope), then open the console(F12), then traverse through the objects $parent property till you find the correct $scope. Then count the number of $parent traversed, that will be your required number of $parent to traverse!
P.S:
There is another method you can do this, since this method will require a significant rewrite of your code, I will provide the GIST, you can use the controller as syntax and access the correct scope.
Here is the SO Answer giving the method to do it
SO Answer
I hope this fixes you issue.

Angularjs change view template after link clicked

I am trying to change html template after link is clicked. Value is boolean, initial value is true and appropriate template is loaded, but when value changed to false new template is not loaded, I don't know the reason. When initial value of boolean is true other template is loaded successfully, but on method called not. Please, help.
Here is my code:
TaskCtrl
app.controller('TasksCtrl', ['$scope', 'TaskService', function ($scope, TaskService) {
// initialize function
var that = this;
that.newTask = true;
that.name = "My name is Nedim";
that.templates = {
new: "views/task/addTask.html",
view: "views/task/viewTask.html"
};
// load all available tasks
TaskService.loadAllTasks().then(function (data) {
that.items = data.tasks;
});
$scope.$on('newTaskAdded', function(event, data){
that.items.concat(data.data);
});
that.changeTaskView = function(){
that.newTask = false;
console.log("New task value: " + that.newTask);
};
return $scope.TasksCtrl = this;
}]);
task.html
<!-- Directive showing list of available tasks -->
<div class="container-fluid">
<div class="row">
<div class="col-sm-6">
<entity-task-list items="taskCtrl.items" openItem="taskCtrl.changeTaskView()"></entity-task-list>
</div>
<div class="col-sm-6" ng-controller="TaskDetailCtrl as taskDetailCtrl">
<!-- form for adding new task -->
<div ng-if="taskCtrl.newTask" ng-include="taskCtrl.templates.new"></div>
<!-- container for displaying existing tasks -->
<div ng-if="!taskCtrl.newTask" ng-include="taskCtrl.templates.view"></div>
</div>
</div>
entityList directive
app.directive('entityTaskList', function () {
return {
restrict: 'E',
templateUrl: 'views/task/taskList.html',
scope: {
items: '='
},
bindToController: true,
controller: 'TasksCtrl as taskCtrl',
link: function (scope, element, attrs) {
}
};
});
directive template
<ul class="list-group">
<li ng-repeat="item in taskCtrl.items" class="list-group-item">
<a ng-click="taskCtrl.changeTaskView()">
<span class="glyphicon glyphicon-list-alt" aria-hidden="true"> </span>
<span>{{item.name}}</span>
<span class="task-description">{{item.description}}</span>
</a>
</li>
{{taskCtrl.newTask}}
Without any plunker or JSFiddle I can't tell for sure, but it might be issue with ng-if. I'm thinking of two workarounds.
First that I think is better. Use only 1 ng-include and only change the template.
HTML:
<entity-task-list items="taskCtrl.items" openItem="taskCtrl.changeTaskView('view')"></entity-task-list>
...
<div ng-include="taskCtrl.currentTemplate"></div>
JS:
that.currentTemplate = that.templates.new;
...
that.changeTaskView = function(template) {
that.currentTemplate = that.templates[template];
};
Or if you don't like this solution, try with ng-show instead of ng-if. With ng-show the elements will be rendered with display: none; property when the page loads, while with ng-if they will be rendered when the passed value is true.
Hope this helps you.

How should i propertly use angular templates (ng-include)?

I made html template and want to use it with angular via ng-include.
<input class="k-textbox" ng-model="entity.Name">
i want to use it twice, so i added it into page like this
<div ng-include="'/template.html'" ng-init="entity=firstEntity"></div>
<div ng-include="'/template.html'" ng-init="entity=secondEntity"></div>
But as a result i getting 2 templates with 'secondEntity' as data, what is proper usage?
This could be a possible usage:
JS:
app.controller("myCtrl1", function($scope) {
$scope.entity = firstEntity;
}
app.controller("myCtrl2", function($scope) {
$scope.entity = secondEndity;
}
HTML:
<div ng-controller="myCtrl1">
<div ng-include="'/template.html'"></div>
</div>
<div ng-controller="myCtrl2">
<div ng-include="'/template.html'"></div>
</div>
Although, reusable templates that require passing different models or configuration are achieved using directives.
EDIT: Directive approach:
JS:
app.controller("myCtrl1", function($scope) {
$scope.entities = {entity1: {}, entity2: {}};
}
app.directive('myDirective', function() {
return {
scope: {
entity: '='
},
template: '<input class="k-textbox" ng-model="entity.Name">'
}
});
HTML:
<div ng-controller="myCtrl1">
<myDirective entity="entities.entity1"></myDirective>
<myDirective entity="entities.entity2"></myDirective>
</div>

AngularJS - directive with a dynamic array of objects in form

I'm trying to create a form that has a dynamic set of input boxes. I'm trying to get ng-models to work with array items and I am clearly doing something wrong.
My problem seems to be indexing the scope array lineItems in the template.
Here is my jsfiddle: http://jsfiddle.net/dk253/9ykuuobq/
Here is my code:
<div ng-controller="MyCtrl">
<h2>Testing Arrays</h2>
<button type="button" ng-click="addItem()" class="btn btn-primary">Howdy</button>
<div ng-repeat="item in items" item="item" my-index="{{$index}}" itemlist></div>
</div>
<script type="text/ng-template" id="template.html">
<div class = "row">
<div class = "col-xs-6"> <input name= "itemNum-{{item.itemNum}}" type = "number" class="form-control"
placeholder = "Item Number" ng-model="items[{{item.itemNum}}].itemNum" required> </div>
<div class = "col-xs-6"> <input name= "name-{{item.itemNum}}" type = "text" class="form-control"
placeholder = "Name" ng-model="items[{{item.itemNum}}].name" required> </div>
</div>
</script>
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', function ($scope) {
$scope.count = 0;
$scope.items = [];
var baseItem = {
itemNum: 0,
name: "New"
};
$scope.addItem = function () {
newItem.itemNum = $scope.count;
var newItem = angular.copy(baseItem);
newItem.itemNum = $scope.count;
$scope.items.push(newItem);
$scope.count += 1;
};
});
myApp.directive('itemlist', function ($compile) {
return {
templateUrl: 'template.html',
restrict: 'A',
replace: true,
transclude: true,
scope: { item: '=', myIndex: '#' }
};
});
Thanks for your help!
The fiddle has several mistakes:
$scope.addItem is using the newItem before defining it. Remove the first line (probably a typo).
The ng-model:
It accepts expressions without {{...}}. So ng-model="items[{{item.itemNum}}].itemNum" would become ng-model="items[item.itemNum].itemNum"
This is still wrong because the directive has isolated scope and the items variable is not visible. But item is visible, so you only need: ng-model="item.itemNum" (and ng-model="item.name").
With these changes, it seems to be working: http://jsfiddle.net/9ykuuobq/19/

What is the *Angular* way to get an elements siblings?

What is the idiomatic way to get an elements siblings when it is clicked using AngularJS?
So far I've got this:
<div ng-controller="FooCtrl">
<div ng-click="clicked()">One</div>
<div ng-click="clicked()">Two</div>
<div ng-click="clicked()">Three</div>
</div>
<script>
function FooCtrl($scope){
$scope.clicked = function()
{
console.log("Clicked", this, arguments);
};
}
</script>
here's a jQuery implementation as a concrete example:
<div id="foo">
<div>One</div>
<div>two</div>
<div>three</div>
</div>
<script>
$(function(){
$('#foo div').on('click', function(){
$(this).siblings('div').removeClass('clicked');
$(this).addClass('clicked');
});
});
</script>
Use a directive, since you want to traverse the DOM:
app.directive('sibs', function() {
return {
link: function(scope, element, attrs) {
element.bind('click', function() {
element.parent().children().removeClass('clicked');
element.addClass('clicked');
})
},
}
});
<div sibs>One</div>
<div sibs>Two</div>
<div sibs>Three</div>
Note that jQuery is not required.
fiddle
Here is an angular version of the jQuery sample that you provided:
HTML:
<div ng-controller="FooCtrl">
<div ng-click="selected.item='One'"
ng-class="{clicked:selected.item=='One'}">One</div>
<div ng-click="selected.item='Two'"
ng-class="{clicked:selected.item=='Two'}">Two</div>
<div ng-click="selected.item='Three'"
ng-class="{clicked:selected.item=='Three'}">Three</div>
</div>
JS:
function FooCtrl($scope, $rootScope) {
$scope.selected = {
item:""
}
}
NOTE: You dont strictly need to access DOM for this. However if you still want to then you can write a simple directive. Something like below:
HTML:
<div ng-controller="FooCtrl">
<div ng-click="clicked()" get-siblings>One</div>
<div ng-click="clicked()" get-siblings>Two</div>
<div ng-click="clicked()" get-siblings>Three</div>
</div>
JS:
yourApp.directive('getSiblings', function() {
return {
scope: true,
link: function(scope,element,attrs){
scope.clicked = function () {
element.siblings('div').removeClass('clicked');
element.addClass('clicked');
}
}
}
});
fiddle
Following is a directive built exclusively with Angular grammar (borrowing from jqLite):
link: function(scope, iElement, iAttributes, controllers) {
var parentChildren,
mySiblings = [];
// add a marker to this element to distinguish it from its siblings
// this could be a lot more robust
iElement.attr('rsFindMySiblings', 'anchor');
// get my parent's children, it will include me!
parentChildren = iElement.parent().children();
// remove myself
scope.siblings = [];
for (var i=0; i < parentChildren.length; i++) {
var child = angular.element(parentChildren[i]);
var attr = child.attr('rsFindMySiblings');
if (!attr) {
scope.siblings.push({name: child[0].textContent});
}
}
}
Note that it uses a controller to store the results. See this plunker for a detailed example

Resources