using ng-model in ng-repeat - javascript

I've created angular directive with template:
<div class="directiveclass">
<div ng-repeat="i in items">
<input type="radio" ng-model="myvalue" value="{{$index}}">
</div>
</div>
The ng-model "myvalue" is part of the directive local scope:
.directive('mydirective', function()
{
return {
templateUrl: "path",
scope: {},
link : function(scope, el, attr)
{
scope.myvalue = 0;
scope.$watch('myvalue', function()
{
console.log('myvalue changed');
});
}
};
}
I'm clicking on the the radio buttons elements, but "myvalue" value never change.
Any idea?
Sample code: http://jsfiddle.net/r5jGL/

I found the solution.
Since ng-repeat create new scope, I've changed the ng-model value to $parent.myval in order to access the directive scope.

In the HTML:
<input type="radio" ng-model="$parent.myvalue" value="{{$index}}">
This allows you to access ng-model as ng-repeat has created its own scope.
The directive does not need to be changed.

Related

Binding ng-model to a Service parameter from inside a Template

I keep a data model in a service so that various controllers can use them. Inside the controller I scope it so that elements can bind to it using ng-model:
In the controller:
angular.module('hiveApp').controller('weatherController', function($scope, $rootScope, weatherModel, messageService, utilityService, $http, $timeout, $cookies, $window, $controller) {
$scope.weatherModel = weatherModel;
Then in the HTML element:
<input type="text" id="city" ng-model="weatherModel.city" />
So far so good, this works. The problem occurs when I bring a directive into the mix. I have a directive that handles a pair of radio buttons, and makes use of a template. That template attempts to use ng-model to reference that same weatherModel service parameters, however while it works from the HTML in the page itself, the directive template doesn't work:
app.directive('radiopair', function() {
return {
restrict: 'E',
template: `
<div style="color:white; float:left">
<input type="radio" id="metric" name="conversion" value="metric"
ng-model="weatherModel.conversion" selected> Metric<br>
<input type="radio" id="imperial" name="conversion" value="imperial"
ng-model="weatherModel.conversion"> Imperial<br>
</div>
`,
link: function ($scope, element, attrs) {
element.on('click', function (event) {
event.currentTarget.selected = true;
$scope.refreshTable();
});
}
}
});
When I toggle between the two buttons, the ng-model=weatherModel.conversion value never gets updated. I figure this has got to be some scoping issue but I'm hitting a wall as to how to fix it.
Instead of using a click handler to invoke the refreshTable function, use the ng-change directive:
app.directive('radiopair', function() {
return {
restrict: 'E',
template: `
<div style="color:white; float:left">
<input type="radio" id="metric" name="conversion" value="metric"
ng-change="refreshTable()"
ng-model="weatherModel.conversion" selected> Metric<br>
<input type="radio" id="imperial" name="conversion" value="imperial"
ng-change="refreshTable()"
ng-model="weatherModel.conversion"> Imperial<br>
</div>
`,
//link: function ($scope, element, attrs) {
// element.on('click', function (event) {
// event.currentTarget.selected = true;
// $scope.refreshTable();
// });
//}
}
});
Avoid manipulating the select attribute of <input> elements. That should be done by the ng-model directive and the ngModelController.

Detect who change the model (user input vs controller) angularjs

I have an input which connected to model. Also, the input has directive which $watch the model.
There are 2 ways that the model will change.
The user will type in the textbox.
The code will change it (no matter what is the reason)
My question is
Is there a way to find out who change the model, the user interaction or the code, in the directive?
Example:
angular.module('app', [])
.controller('ctrl', function($scope) {
})
.directive('dir', function($rootScope){
return {
require: 'ngModel',
link: function(scope, element, attrs) {
$rootScope.logs = [];
scope.$watch(attrs.ngModel, function() {
// here I need to check if the change was from the UI or from the controller
$rootScope.logs.push('change');
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div data-ng-app="app" data-ng-controller="ctrl">
<input type="text" data-ng-model="model" data-dir="" />
<button data-ng-click="model = 'asd'">Set "model" to defferent value</button>
{{model}}
<hr />
<h3>console <button data-ng-click="$root.logs = []">clear console</button></h3>
<ul>
<li data-ng-repeat="log in $root.logs track by $index" data-ng-bind="log"></li>
</ul>
</div>
http://jsbin.com/vufawur/edit?html,js,output
Update
Example2:
angular.module('app', [])
.controller('ctrl', function($scope, $timeout) {
$timeout(function() {
$scope.model = 'asd';
}, 3000);
})
.directive('dir', function($rootScope){
return {
require: 'ngModel',
link: function(scope, element, attrs) {
$rootScope.logs = [];
scope.$watch(attrs.ngModel, function() {
// here I need to check if the change was from the UI or from the controller
$rootScope.logs.push('change');
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div data-ng-app="app" data-ng-controller="ctrl">
...wait until data "return from the server"<br />
<input type="text" data-ng-model="model" data-dir="" />
{{model}}
<hr />
<h3>console <button data-ng-click="$root.logs = []">clear console</button></h3>
<ul>
<li data-ng-repeat="log in $root.logs track by $index" data-ng-bind="log"></li>
</ul>
</div>
ext-change External Change Directive for ng-model
Use a $viewChangeListener to save the last user input and have the watch handler compare that to discriminate external changes to the model from user input changes to the model.
.directive('extChange', function(){
return {
require: 'ngModel',
link: function(scope, element, attrs, modelCtrl) {
var lastUserInput = modelCtrl.$viewValue;
modelCtrl.$viewChangeListeners.push(function() {
lastUserInput = modelCtrl.$viewValue;
});
scope.$watch(attrs.ngModel, function watchHandler (value) {
if (value!==lastUserInput) {
scope.$eval(attrs.extChange, {$value:value});
}
});
}
}
});
The example directive saves that last user input. When the watch handler gets a value that is different, it invokes the Angular expression defined by the ext-change attribute. The value of the change is exposed as $value.
<input ng-model="someInput"
ng-change="userInput=someInput"
ext-change="extInput=$value">
The ext-change directive works with the ng-model directive and complements the ng-change directive.
In this example, the ext-change directive only updates the extInput variable on external changes to the model. The ng-change directive only updates the userInput variable for user changes.
The DEMO on JSFiddle
The directive can also be used to invoke functions.
<input ng-model="someInput"
ng-change="userEvent(someInput)"
ext-change="externalEvent($value)">
Do not use $watch. You should not use it, you have to not use it, you are going to have trouble if you use $watch, you are already in trouble, don't use it.
Angular JS - you probably shouldn't use $watch in your controllers.
Is it an antipattern to use angular's $watch in a controller?
Use control flow and events. It is possible that you already have a lot of watcher and scope soup, it is not too late, refactor as soon as possible, it is for your best.
angular.module('app', [])
.controller('ctrl', function($scope) {
})
.directive('dir', function($rootScope) {
return {
require: 'ngModel',
link: function($scope, element, attrs) {
$rootScope.logs = [];
$scope.modelChange = function(reason) {
$rootScope.logs.push(reason);
};
$scope.modelChangedFromInput = function(model) {
$scope.modelChange('From input');
};
$scope.buttonClick = function() {
$scope.model = 'asd';
$scope.modelChange('From button');
};
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div data-ng-app="app" data-ng-controller="ctrl">
<input type="text" data-ng-model="model" data-dir="" data-ng-change="modelChangedFromInput()" />
<button data-ng-click="buttonClick()">Set "model" to different value</button>
{{model}}
<hr />
<h3>console <button data-ng-click="$root.logs = []">clear console</button>
</h3>
<ul>
<li data-ng-repeat="log in $root.logs track by $index" data-ng-bind="log"></li>
</ul>
</div>

Angularjs Pass all attributes on a directive to the view

I'm building a tiny angular directive <my-input> on top of a normal HTML <input>.
And because this is going to be available in a framework, I need to allow people to pass whichever attribute they might use from the directive to the input element. For example:
<my-directive disabled="disabled" type="email">
would render
<input disabled="disabled" type="email">
I know that if I have a static list of attributes, I can manually do it.. but the problem is I can't predict what attributes will be added.. so I'm looking for a solution that passes all the attributes from the directive to the input element.
Thanks
If you want to pass multiple attributes to the view, you can perform it into the link function.
Here is your directive :
Directive
(function(){
function myInput($compile) {
return{
restrict: 'E',
templateUrl: 'template.html',
link: function(scope, elm, attrs){
//Convert camelCase to dash
function toDash(str) {
return str.replace(/\W+/g, '-')
.replace(/([a-z\d])([A-Z])/g, '$1-$2');
}
//Retrieve input into the template
var input = angular.element(document.querySelector('#myInput'));
//Loop on attrs
for (var key in attrs) {
if (key[0] !== '$'){
//Remove all attribute to the top level element
elm.removeAttr(toDash(key));
//Add our attribute to the input
input.attr(toDash(key), attrs[key]);
//Compile and link it to the scope
$compile(input)(scope);
}
}
}
};
}
angular
.module('app')
.directive('myInput', myInput);
})();
With the template :
template.html
<input type="text" id="myInput">
For example, in a controller you can set some variable :
Controller
(function(){
function Controller($scope) {
$scope.show = true;
$scope.toto = 'Hello !!'
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
And call your directive :
<body ng-app="app" ng-controller="ctrl">
<my-input disabled="disabled" ng-model="toto" ng-show="show"></my-input>
</body>
So it will remove all attributes to the my-input element, and set it into your template.

Use Directive two times in one controller AngularJS

I want to use same directives multiple times in one controller in AngularJS. I want to create a list widget that can be used multiple times. I can display two widgets at the same time under the same controller. But, I am unable to bind teamA and teamB data to ng-repeat in my directive. In addition to that, the code fails during addTeamMember() because datasource is undefined. I was hoping that datasource will be updated with teamA and teamB respectively.
Here is the HTML code.
<div ng-controller="myCtrl"><div class="container">
<my-directive datasource="model.teamA"></my-directive>
<my-directive datasource="model.teamB"></my-directive>
</div></div>
Controller.js:
angular.module('app',[])
.controller('myCtrl', [ '$scope', function($scope){
$scope.teamA = {};
$scope.teamB = {};
} ] );
Directive.js:
angular.module('app', [] )
.directive('myDirective', function(){
return{
restrict: 'AE',
templateUrl: 'directive_html.html',
scope: {
datasource: "=TeamMembers"
},
transclude: true,
link: function(scope, elem, attrs){
scope.addTeamMember = function(){
scope.datasource.push({});
};
scope.removeTeamMember = function(item){
scope.datasource.splice(item, 1);
};
}
};
}) ;
directive_html.html:
<div><div class="container">
<div class="form-group" ng-repeat="member in TeamMembers">
<textarea ng-model="member.text" rows="2"></textarea>
Remove
<div>
<button type="button" ng-click="addTeamMember()">Add</button>
</div></div>
Could anyone please help me out here? I want to create custom Widgets that can be used multiple places either in same controllers or in different controllers.
Thanks
As #Neozaru pointed out in comments. You are expecting the directive attribute to be called team-members here:
<div ng-controller="myCtrl"><div class="container">
<my-directive team-members="model.teamA"></my-directive>
<my-directive team-members="model.teamB"></my-directive>
</div></div>
You do this when you define the isolated scope as:
scope: {
datasource: "=TeamMembers"
}
The above line is saying, "team-members is what the outside attribute will be named, but internally I'll refer to the referenced object as scope.datasource".

AngularJS The scope for dynamic content through $compile isn't attached to the controller scope

When I generate a new element through a string that has a directive (that's why I need to compile) and that directive generates an association with a variable in the controller scope through "=", the variable in my controller isn't associated to the one in the directive.
I created a jsfiddle to show the example where the "door" ng-model value should be associated to all the directives model values.
See this fiddle: http://jsfiddle.net/aVJqU/2/
Another thing I notice is that the directive that run from elements present in the html show the correct association through the variables (controller and directive).
The html (there is the directive that binds <door>):
<body ng-app="animateApp">
<div ng-controller="tst">
<h2> Controller with its model </h2>
<input ng-model="doorval" type="text"> </input>
{{doorval}}
<h2> Directive render directly from the html </h2>
<door doorvalue="doorval"></door> <key></key>
<h2> Directives that are compiled </h2>
<list-actions actions="actions"></list-actions>
</div>
</body>
This is the directive:
animateAppModule.directive('door', function () {
return {
restrict: "E",
scope: {
doorvalue:"="
},
template: '<span>Open the door <input type="text" ng-model="doorvalue"> </input> {{doorvalue}}</span>',
replace: true
}
})
This is the controller:
var animateAppModule = angular.module('animateApp', [])
animateAppModule.controller('tst', function ($scope, tmplService) {
$scope.doorval = "open"
$scope.actions = tmplService;
})
animateAppModule.service('tmplService', function () {
return [{
form_layout: '<door doorvalue="doorval"></door> <key></key>'
}, {
form_layout: '<door doorvalue="doorval"></door> with this <key></key>'
}]
})
And finally this is the directive that compiles the string that has the directive that doesn't bind:
animateAppModule.directive('listActions', function ($compile) {
return {
restrict: "E",
replace: true,
template: '<ul></ul>',
scope: {
actions: '='
},
link: function (scope, iElement, iAttrs) {
scope.$watch('actions', function (neww, old,scope) {
var _actions = scope.actions;
for (var i = 0; i < _actions.length; i++) {
//iElement.append('<li>'+ _actions[i].form_layout + '</li>');
//$compile(iElement.contents())(scope)
iElement.append($compile('<li>' + _actions[i].form_layout + '</li>')(scope))
}
})
}
}
})
What can I do to bind all the "door" ng-model values together?
Where is the compiled directive binding to?
You just have to pass the doorval reference down through all directives without skip any one. The problem was the listActions directive didn't had access to doorval in its scope.
Check this out: http://jsfiddle.net/aVJqU/5/
#Danypype is basically correct as the problem occurs due to scope isolation, as explained in the documentation.
An alternative solution is to simply eliminate the scope isolation by removing the scope block from within the directive definition.

Categories

Resources