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.
Related
In my app I make frequent use of a checkbox. Given the repetitiveness I wanted to make a custom directive out of it, but when I call the directive in my html it does not check the checkbox. I cannot figure out what I am missing. Any suggestions would be appreciated.
HTML
<div class="col-xs-12 col-sm-9 col-md-9">
<input type="checkbox"
class="form-control"
name="{{attrs.name}}"
id="{{attrs.id}}"
data-ng-model="model">
<label for="{{attrs.id}}">
<span>{{labelText}}</span>
</label>
</div>
JS
(function () {
'use strict';
angular
.module('app.directives')
.directive('ccCheckBox', checkbox);
checkbox.$inject = [];
/* #ngInject */
function checkbox() {
return {
restrict: 'E',
scope: {
model: '=',
labelText: '#'
},
templateUrl: './src/_directives/checkbox.html',
link: CheckBox
};
function CheckBox(scope, attrs){
activate();
function activate(){
scope.attrs = attrs;
}
}
}
})();
Called in my code as
..
<cc-check-box
data-label-text="Test CheckBox Label"
data-name="checkBoxGrp"
data-id="myCheckBox"
data-for="myCheckBox"
data-ng-model="vm.model.myCheckBoxValue">
</cc-check-box>
No errors in the dev tools console.
You cannot set data-ng-model to bind with model attr.
You should use model (as you defined into the isolated scope) or data-model.
<cc-check-box
data-label-text="Test CheckBox Label"
data-name="checkBoxGrp"
data-id="myCheckBox"
data-for="myCheckBox"
data-model="vm.model.myCheckBoxValue"
></cc-check-box>
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>
I need to inject new template dynamically depending on the value or parameter of radio button.
This is my HTML
<div class="container-fluid" ng-app="rjApp">
<div class="panel-body" ng-controller="mainController">
<input name="penanggung_biaya" type="radio" ng-model="checked" ng-click="broadcast(umum)" class="ace" value="umum"> <!-- I though, umum is the parameter which I want passed through to the Controller -->
<input name="penanggung_biaya" type="radio" ng-model="checked" ng-click="broadcast(instansi)" class="ace" value="instansi">
<example-directive message="message"> </example-directive>
</div>
</div>
and, this is the JS
var rjApp = angular.module('rjApp',[]);
rjApp.config(function($interpolateProvider) {
$interpolateProvider.startSymbol('{::');
$interpolateProvider.endSymbol('::}');
})
//mainCotroller, should be work by ngClick through radio button
function mainController($scope, $rootScope) {
$scope.broadcast = function(event){
console.log(event) //I've been thought, this is the part to passing the parameter of radio button, but not gonna works.
$rootScope.$broadcast('handleBroadcast');
};
}
//the Directive, should be injected dinamically template, depends on ngClick parameter inside radion button
rjApp.directive('exampleDirective', function() {
return {
restrict: 'E',
scope: {
message: '='
},
link: function(scope, elm, attrs) {
scope.$on('handleBroadcast', function(doifq) {
templateUrl= '<?php echo url("registrasi/rawat_jalan/penanggung_biaya/") ?>'+doifq //This is the part to injected the dinamically template. And I've been guess the **doifq**, is the paramter to passing by the mainController
});
},
};
});
Please, somebody help me.
Regards.
In broadcast, you could pass the parameter,
$scope.broadcast = function(event){
console.log(event);
$rootScope.$broadcast('handleBroadcast',event);
};
This way you would be getting doifq value in directive depending on the which radio button is clicked.
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.
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.