AngularJS - directive with a dynamic array of objects in form - angularjs

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/

Related

Disable and empty model value and then restore it

I have working code:
<input type="checkbox" ng-model="disabled" >
<input type="text" ng-model="text" ng-disabled="disabled">
When disabled == true the input is disabled. I need to hide actual model value (set empty) as well. And after uncheck the checkbox actual model value should appears. How to do it without changing model value?
I would do something like this:
<input type="checkbox" ng-model="disabled" ng-change="change()">
<input type="text" ng-model="text" ng-disabled="disabled">
...
$scope.change = function() {
if ($scope.disabled) {
$scope.textBackup = $scope.text;
$scope.text = '';
} else {
$scope.text = $scope.textBackup;
}
};
Note: I posted before reading #AbdulMateenMohammed comment... My answer is an implementation of his suggestion...
This is not the best option but if you want to deal only with the view:
<input type="checkbox" ng-model="disabled">
<div ng-show="disabled">
<input type="text" ng-init="text2 = ''" ng-model="text2" ng-disabled="true">
</div>
<div ng-hide="disabled">
<input type="text" ng-model="text">
</div>
Again, this is not the best option!!! MarcosS option is the recommended.
#Makarov Sergey, I think even when you have a complex view or data source the basic idea is to use a temporary variable because you need to have two values b/w which you swap around.
angular
.module('demo', [])
.controller('DefaultController', DefaultController);
function DefaultController() {
var originalText = '';
var vm = this;
vm.text = 'Hello, World!';
vm.onValueChanged = onValueChanged;
function onValueChanged(text) {
if (vm.disabled) {
originalText = text;
vm.text = '';
} else {
vm.text = originalText;
}
}
}
span {
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div>
<em>I </em><span>♥ </span><em>AngularJS</em>
</div>
<div ng-app="demo">
<div ng-controller="DefaultController as ctrl">
<input type="checkbox" ng-model="ctrl.disabled" ng-change="ctrl.onValueChanged(ctrl.text)"/>
<input type="text" ng-model="ctrl.text" ng-disabled="ctrl.disabled"/>
</div>
</div>
Tip: For the objects that aren't displayed in the view but present in the controller shouldn't be assigned on the scope because using scope would add some JavaScript events to do the data-binding which is unnecessary overhead such as the dirty check event for the two-way data-binding so that's why in the sample code snippet I used var originalText = ''; instead of vm.originalText = '';
Found the solution that works for me atm
function directive() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModelCtrl) {
let oldViewValue = '';
scope.$watch(() => attrs.hideValue, (newV) => {
if (newV == 'true') {
oldViewValue = ngModelCtrl.$viewValue;
ngModelCtrl.$viewValue = '';
} else {
ngModelCtrl.$viewValue = oldViewValue;
}
ngModelCtrl.$render();
});
}
}
}
And use:
<input type="checkbox" ng-model="disabled" >
<input type="text" ng-model="text" ng-disabled="disabled" hide-value="disabled">

Why my angular directive doesn't work?

I have a directive that is triggered when clicking on a button. The function inside the directive simply has to change the property value of the field. So what I try to do is to change from 'popover-trigger="blur"' to 'popover-trigger="none"'.
Here is my plunkr: http://plnkr.co/edit/L81fQgi7j1dEtf1QAZJ2?p=preview
or the code is here:
var app = angular.module('ui.bootstrap.demo', ['ngAnimate', 'ui.bootstrap']);
app.controller('PopoverDemoCtrl', function ($scope) {
$scope.dynamicPopover = {
content: 'Hello, World!',
templateUrl: 'myPopoverTemplate.html',
title: 'Title'
};
$scope.label = "Please click";
$scope.message = "ON FOCUS trigger a tooltip";
$scope.htmlPopover = "myPopoverTemplate.html";
});
app.directive("changeTrigger", function($compile){
return{
restrict: 'A',
link: function(scope, elm, attrs)
{
elm.bind('click', function(){
var t = document.getElementsByClassName('f')[0].setAttribute('popover-trigger', 'none');
$compile(t);
console.log("Click works");
});
}
}
});
html
<div ng-controller="PopoverDemoCtrl">
<br><br><br>
<p>{{message}}</p>
<input class="f" type="text" value="Click me!" uib-popover-template="htmlPopover" popover-trigger="focus" popover-popup-close-delay="1000" popover-placement="right" required>
<test-directive></test-directive>
<script type="text/ng-template" id="myPopoverTemplate.html">
<div>
<p>Click the button to stop triggering tooltip!</p>
<button change-trigger><b style="color: red">Stop tooltip</b></button>
<div class="label label-success">page</div>
</div>
</script>
</div>
You can't reconfigure the angular-bootstrap Popup element by changing uib-popup-* parameters; but you can bind a scope variable to popup-enable attribute to be able to switch the popup on/off. Add:
<input ... uib-popover-template="htmlPopover" popover-enable="enable" ...>
and
$scope.enable = true;
The problem here is that your button and the input box have different scopes. But you can fix this by retrieving the scope of the field:
var t = document.getElementsByClassName('f')[0];
var scope_ = angular.element(t).scope();
Of course, you need to use $scope.$apply for the scope to correctly handle two-way data binding:
scope_.$apply(function () {
scope_.enable = false;
});
Working Plunkr.

Angular Directive How can we pass binding attr

Assuming that we have custom directive which have ng-repeat inside:
//...
<div class="item" ng-repeat="item in items" data-value="{{item.id}}">
{{item.name}}
</div>
//...
And the caller is:
<dropdown items="assetTypesData"></dropdown>
Question is how can we pass name and id of {{item.name}} and {{item.id}} to directive as:
<dropdown items="assetTypesData" text="name" value="id"></dropdown>
Using isolate scope, you can pass in the three things you need: items, text, id. Then in your template, reference the {{item[text]}} for getting the property passed in for text (name in this exmaple) and {{item[value]}} to get the value property on the item (e.g. id).
angular.module('myApp', [])
.controller('MainController', function ($scope) {
var vm = {};
$scope.vm = vm;
activate();
function activate() {
var items = [];
for (var i = 0; i <= 15; ++i) {
items.push({id: i, name: 'Item ' + i});
}
vm.items = items;
}
})
.directive('myDropdown', function () {
var template = '<div class="item" ng-repeat="item in items" data-value="{{item[value]}}">' +
'{{item[text]}}' +
'</div>';
return {
restrict: 'E',
template: template,
scope: {
items: '=',
text: '#',
value: '#'
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MainController">
<my-dropdown items="vm.items" text="name" value="id"></my-dropdown>
</div>

angular1.3.7 ng-repeat binding was not expected

I'm looking for an explanation of how angular binding works behind the curtain. I only want $scope.values to be updated on ng-click, but once its clicked once it bound to the form forever. woah. Why?
First example is NOT working as I expected it to work. The values should only be reflected in the span everytime I click on my button.
http://jsfiddle.net/webmandman/Ls4g4yLn/12/
html:
<div ng-app="app" ng-controller="myCtrl">
<div>
<span ng-repeat="value in values">{{value.text}}</span><br>
<input ng-repeat="field in fields" type="text" name="{{field.name}}" ng-model="field.text"/>
</div>
<button ng-click="display()">display</button>
</div>
controller:
var app = angular.module('app', []);
angular.module('app').controller("myCtrl", function($log,$scope, $http) {
$scope.fields = [
{name:"Name", text:null},
{name:"Phone", text:null}
];
$scope.display = function(){
$log.log("display has been called...");
$scope.values = $scope.getValues()
};
$scope.getValues = function(){
$log.log('getValues was called.');
var list = [];
angular.forEach($scope.fields, function(value, index){
//$log.log(item);
$log.log(value);
$log.log(index);
this.push(value);
//this.push(item);
},list);
return list;
};
});
This example is working like I want it too, but it is not using ng-repeat.
http://jsfiddle.net/webmandman/Ls4g4yLn/13/
html:
<div ng-app="app" ng-controller="myCtrl">
<div>
<span>{{values.join(',')}}</span><br>
<input ng-repeat="field in fields" type="text" name="{{field.name}}" ng-model="field.text"/>
</div>
<button ng-click="display()">display</button>
</div>
controller:
var app = angular.module('app',[]);
angular.module('app').controller("myCtrl", function($log,$scope, $http) {
$scope.fields = [
{name:"Name", text:null},
{name:"Phone", text:null}
];
$scope.display = function(){
$log.log("display has been called...");
$scope.values = $scope.getValues()
};
$scope.getValues = function(){
var list = [];
angular.forEach($scope.fields, function(value, index){
//$log.log(item);
$log.log(value);
$log.log(index);
this.push(value.text);
//this.push(item);
},list);
return list;
};
});
That's because in your $scope.values, you push refercnes to your $scope.fields, hence then when you edit a field text, it is rendered in both places.
Instead, you can simply push the text in your values, as String are passed by copy and not reference (that's pure Javascript not related to Angular).
Here is a working fiddle:
http://jsfiddle.net/zrwq6cmu/

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