Adding directive dynamically using ng-click - angularjs

I am working on a modal for a client. I have created a directive that works great, the problem is that one modal is made ahead each time it is used like..
What I have
<div ng-repeat="item in items">
<a data-toggle="modal" data-target="{{item.id}}">Click</a>
<my-dialog element-id="item.id">
<h1>This is the body of the modal</h1>
</my-dialog>
</div>
This works great for a small amount of modals but we are using a very large number of modals. So I would like to add the directive at runtime, something closer to...
What I want...
<div id="warning"></div>
<div ng-repeat="item in items">
<a data-toggle="modal" data-target="{{item.id}}" ng-click="showModal(item)">Click</a>
</div>
...
// inside controller
$scope.showModal = function(item){
$http.get('/someUrl').success(function(data){
var result = $compile('<my-dialog element-id="'+item.id+'">'+data+'</my-dialog>').($scope);
$("#warning").append(result);
});
}
$scope.hideModal = function(){
$( "#warning" ).empty();
}
This of course isn't working yet. Also it doesn't feel like the best way. This would allow me to remove the directive once it has been closed.
Please include a plunker or equivalent for the check.

One way you could do this is to use ng-repeat with your items, then call $scope.$apply() after you push a new item to the list. The HTML could look like this ...
<div ng-repeat="item in items">
<span dialog>
<a class="dialog-anchor">{{item.name}}</a>
<div class="dialog-body">{{item.id}}</div>
</span>
</div>
... and the directive like this
.directive('dialog', [function () {
return {
scope: {
id: '#elementId',
}
, link: function (scope, el, attrs) {
var body = $(el).find('.dialog-body').detach();
$(el).find('.dialog-anchor').on('click', function () {
$('body').append(body);
});
}};
}])
... and the controller like this
.controller('app', ['$scope', function ($scope) {
$scope.items = [
{name: 'first', id: 001},
{name: 'second', id: 002}
];
setTimeout(function () {
$scope.items.push({name: 'three', id: 003});
if (!$scope.$$phase) $scope.$apply();
}, 2000);
}])
Here's the plunker... http://plnkr.co/edit/2ETbeCKGcHW3CJCfD9d7?p=preview. You can see the $scope.$apply call in the setTimeout where I push a new item to the array.

Try this:
var result = $compile('<my-dialog element-id="'+item.id+'">'+data+'</my-dialog>')($scope);

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.

ng-click inside ng-repeat doesnt work

I have html which looks like one below, I have 2x ng-click in whole code in both situation I call same function. Both functions are in same controller.
<div class="tagselect tagselect--frameless">
<div class="combobox__body combobox__body--open combobox__body--frameless" ng-show="focus">
<ul class="list-unstyled">
<li class="combobox__item" ng-repeat="pos in listCtrl.positions | filter:query as results"
ng-click="listCtrl.choosePosition(pos)">{{pos.name}}
</li>
</ul>
</div>
</div>
<div class="col-md-2 no-padding">
<button type="button" class="btn btn-success" ng-click="listCtrl.chosenPositions(789456)">Add</button>
</div>
controller looks like:
myApp.controller('ListCtrl', ['$scope', '$cookies', '$http', function ($scope, $cookies, $http) {
var listCtrl = {
candidates: [],
positions: [],
chosenPositions: [],
init: function () {
listCtrl.getCandidates();
listCtrl.getPositions();
},
getCandidates: function () {
$http.get('candidates.json').then(function (res) {
listCtrl.candidates = res.data;
});
},
getPositions: function () {
$http.get('positions.json').then(function (res) {
listCtrl.positions = res.data;
});
},
choosePosition: function (position) {
console.log(position);
}
};
listCtrl.init();
$scope.listCtrl = listCtrl;
}]);
I double check for missspells and make sure its not because of function (I create a new one with simple console log).
Problem is that button click correctly call function but ng-repeat <li ng-click=""> doesnt do anything. I read in angular documentation that ng-repeat create new scope but this should be still okey in my opinion as soon as I use reference to object listCtrlchoosePosition()
Can someone tell me what I am doing wrong?
Thanks
EDIT: Plunker example:
http://plnkr.co/edit/ooUQA2n1Vyj8RZtsQ1Pj?p=preview
ng-blur is doing something weird, so I'm going to suggest you to change the $scope.focus value from the ListCtrl instead of using the ng-blur.
html file
<!-- more html code -->
<!-- input without ng-blur directive -->
<input class="tagselect__input" placeholder="Position" ng-focus="focus=true" ng-model="query">
<!-- more html code -->
<li class="combobox__item" ng-repeat="pos in listCtrl.positions | filter:query as results" ng-click="listCtrl.choosePosition(pos)">{{pos.name}}
<!-- more html code -->
js file
// more code goes here.
choosePosition: function (position) {
//alert('Going to choosen position');
//$scope.query = position.name;
$scope.focus = false; // Hide div options from here.
// rest of your code.
},
// more code goes here.
Working in this plunkr

Controller function not called from directive template in AngularJS

Directive template (items.html)
<li ng-repeat="item in itemCart">
{{item.title}} <br>
{{item.category}} &nbsp
{{ formatCurrencyFunction({cost: item.price}) }}
</li>
This custom directive is used in Second.html
<h1>
This is Second.
{{header}}
</h1>
<hr>
<ul>
<items-list item-cart="items" format-currency-function="formatPrice(cost)"></items-list>
</ul>
The code for Controller is:
myApp.directive("itemsList", function(){
return{
templateUrl:"contents/Views/Directives/Items.html",
replace: true,
scope:{
itemCart: "=",
formatCurrencyFunction: "&"
},
restrict:"EACM" // E-Element A-Attribute C-Class M-Comments
}
})
myApp.controller('secondController', ['$scope', '$log', '$http','$routeParams', function($scope, $log, $http, $routeParams) {
$scope.formatPrice = function(price){
return "₹ "+parseFloat(price).toFixed(2);
};
$scope.header = 'Second ' + ($routeParams.num || "");
$scope.testSecond = "Second";
$http.get("/items")
.success(function(data){
$scope.items = data;
});
}]);
The function formatPrice is not called from the directive Items.html
What has to be corrected in my code to get it working?
Inside the directive's link function, you'll want to call the method like so:
scope.someFn({arg: someValue});
myApp.directive("itemsList", function(){
return{
templateUrl:"contents/Views/Directives/Items.html",
replace: true,
scope:{
itemCart: "=",
formatCurrencyFunction: "&formatCurrencyFunction"
},
link:function(scope, element, attrs){
scope.formatPrice({arg: 35}); //scope.someFn({arg: 35});
},
restrict:"EACM" // E-Element A-Attribute C-Class M-Comments
}
})
Got this working by using ng-repeat in the Second.html page instead of the directive.
Directive template (items.html)
<li>
{{item.title}} <br>
{{item.category}} &nbsp
{{ formatCurrencyFunction({cost: item.price}) }}
</li>
This custom directive is used in Second.html
<h1>
This is Second.
{{header}}
</h1>
<hr>
<ul>
<items-list item="item" format-currency-function="formatPrice(cost)" ng-repeat="item in items"></items-list>
</ul>
Code in JS file
myApp.directive("itemsList", function(){
return{
templateUrl:"contents/Views/Directives/Items.html",
replace: true,
scope:{
item: "=",
formatCurrencyFunction: "&"
},
restrict:"EACM" // E-Element A-Attribute C-Class M-Comments
}
})
Inside the directive's link function, you need to call the method by applying some event to that attribute
Controller function 'formatCurrencyFunction()' can't be called by just placing it in an expression.
To use the function you need to call it based on an even, in your case ng-init will work.
<li ng-repeat="item in itemCart" ng-init="formatCurrencyFunction({cost: item.price})">
{{item.title}} <br>
{{item.category}}
</li>

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.

ANgularjs: ng-repeat and nested custom directive

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>

Resources