What is the point of controller.$viewValue/controller.$modelValue? - javascript

I'm unclear what the relation is between scope.ngModel and controller.$viewValue/controller.$modelValue/controller.$setViewValue() is, and specifically, what the point of the latter three is. For example, see this jsfiddle:
<input type="text" ng-model="foo" my-directive>
and:
myApp.directive('myDirective', function($timeout) {
return {
require: 'ngModel',
restrict: 'A',
scope: { ngModel: '=' },
link: function (scope, element, attrs, controller) {
function log() {
console.log(scope.ngModel);
console.log(controller.$viewValue);
console.log(controller.$modelValue);
}
log();
controller.$setViewValue("boorb");
log();
scope.$watch('ngModel', function (val) {
console.log("val is now", val);
});
$timeout(function () {
log();
}, 2000);
}
}
});
With the controller being:
function MyCtrl($scope, $timeout) {
$scope.foo = 'ahha';
$timeout(function () {
$scope.foo = "good";
}, 1000);
}
The output is:
(index):45 ahha
(index):46 NaN
(index):47 NaN
(index):45 ahha
(index):46 boorb
(index):47 boorb
(index):53 val is now ahha
(index):53 val is now good
(index):45 good
(index):46 boorb
(index):47 boorb
controller.$viewValue did not start out as the value of the foo variable. Further, controller.$setViewValue("boorb") didn't influence scope.ngModel at all, nor was the update reflected in the HTML. Thus it seems there is no relation between scope.ngModel and controller.$viewValue. It seems that with anything I'd want to do, I would just use scope.ngModel, and watch those values. What is ever the point of using controller.$viewValue and controller.$modelValue or keeping them up to date with scope.ngModel?

scope: { ngModel: '=' }, creates an isolated scope for the directive, which means that changes to foo in the directive will no longer be reflected in the parent scope of MyCtrl.
Also, changes made by $setViewValue() will not get reflected in the DOM until controller.$render() is called, which tells Angular to update the DOM in the next digest cycle.
But to answer the question, NgModelController and its methods are really only necessary if you need to create some extra-special-custom-fancy data-binding directives. For normal data input and validation, you shouldn't ever need to use it. From the documentation (emphasis mine):
[NgModelController] contains services for data-binding, validation, CSS updates, and value formatting and parsing. It purposefully does not contain any logic which deals with DOM rendering or listening to DOM events. Such DOM related logic should be provided by other directives which make use of NgModelController for data-binding to control elements. Angular provides this DOM logic for most input elements.

The confusion here is coming from sticking a directive onto an existing directive, namely ngInput.
Instead, consider a fresh directive:
<my-directive ng-model="ugh">Sup</my-directive>
With:
$rootScope.ugh = 40;
And:
.directive('myDirective', function () {
return {
require: "ngModel",
// element-only directive
restrict: "E",
// template turns the directive into one input tag
// 'inner' is on the scope of the *directive*
template: "<input type='text' ng-model='inner'/>",
// the directive will have its own isolated scope
scope: { },
link: function (scope, element, attrs, ngModelCtrl) {
// formatter goes from modelValue (i.e. $rootScope.ugh) to
// view value (in this case, the string of twice the model
// value + '-'
ngModelCtrl.$formatters.push(function (modelValue) {
return ('' + (modelValue * 2)) + '-';
});
// render does what is necessary to display the view value
// in this case, sets the scope.inner so that the inner
// <input> can render it
ngModelCtrl.$render = function () {
scope.inner = ngModelCtrl.$viewValue;
};
// changes on the inner should trigger changes in the view value
scope.$watch('inner', function (newValue) {
ngModelCtrl.$setViewValue(newValue);
});
// when the view value changes, it gets parsed back into a model
// value via the parsers, which then sets the $modelValue, which
// then sets the underlying model ($rootScope.ugh)
ngModelCtrl.$parsers.push(function (viewValue) {
var sub = viewValue.substr(0, viewValue.length-1);
return parseInt(sub)/2;
});
}
};
})
Try it on Plunker.
Note that typeof ugh stays "number", even though the directive's view value is of a different type.

Related

how can I pass 'scope' argument outside the directive

how can I pass 'scope' argument outside the directive?
i need use it in some other component..
my code:
(function () {
angular.module('dmv.shared.components').
directive('doImportPackage', ['Package', function (Package) {
return {
restrict: 'A',
scope: {
onStart: '<',
onFinish: '<',
onError: '<'},
link: function (scope, element, attributes) {
}
tnx !!
You can do this via a controller. Since AngularJS works in 2-way data binding principle, these variables you assigned will already be updated from where you referenced, and you can use them with other directives too. For example, I assume that you use your directive as follows:
<do-import-package
on-start="myCtrl.onStart"
on-finish="myCtrl.onFinish"
on-error="myCtrl.onError">
</do-import-package>
You have following corresponding variables in myCtrl controllor:
this.onStart = some value;
this.onFinish = some value;
this.onErrod = some value;
Under normal conditions, you can bind other directive's attributes to these values and they will be updated in 2-way. For example, if you use the following directive, both directives should be updated with the same values.
<other-directive
on-start="myCtrl.onStart"
on-finish="myCtrl.onFinish"
on-error="myCtrl.onError">
</other-directive>

passing defined object to another custom directive angularjs

so I have this custom directives that you could see below.
myApp.directive('myDirective', function (testService) {
return {
restrict:'EA',
link:function (scope, element, attr) {
//defined the object
var object = new object();
testService.setObject(object);
}
}
});
myApp.directive('mySecondDirective', function (testService) {
return {
restrict:'EA',
link:function (scope, element, attr) {
//call the variable from previous custom directive
console.log(testService.getobject()); -> always return undefined
}
}
});
and this is the html structure where I used the directives above.
<my-directive></my-directive>
<my-second-directive></my-second-directive>
there I want to retreive the object that contains new object() from previous custom directive, but it always return an undefined I wonder how could I do this without using require nor isolated scope as well.. could you guys help me ?
UPDATE
I create a service to provide the facility to set and retreive the object and apparently it returned undefined because I set the custom direcitve this way
<my-second-directive></my-second-directive>
<my-directive></my-directive>
and this is the service
define(
['services/services'],
function(services)
{
'use strict';
services.factory('testService', [function() {
var me = this;
var testObject = '';
return {
setObject: function(object) {
me.testObject = object;
},
getObject: function() {
return me.testObject;
}
}
}
]);
}
);
the thing is that I actually set the html markup like I already mentioned above which is
<my-second-directive></my-second-directive>
<my-directive></my-directive>
so could you give me some advice on how should I do this ?
note* that the passing object actually worked I prefer using services because it will easy to mantain latter. The question is how do I make the object accessible from another directive even though the initiate install of the object (set the object) in the directive that I defined at the html markup, as the last position of the html it self ?
UPDATE this is the PLUNKER that I've been made for you to understand the question it self
You could achieve this by firing a custom event and then listening for it in the second directive. Here's an updated plunker: http://plnkr.co/edit/512gi6mfepyc04JKfiep?p=info
Broadcast the event from the first directive:
app.directive('myDirective', function(testService) {
return {
restrict: 'EA',
link: function(scope, elm, attr) {
var object = {};
testService.setObject(object);
console.log('setting object');
scope.$broadcast('objectSet');
}
}
});
... and then listen for it on the second:
app.directive('mySecondDirective', function(testService) {
return {
restrict: 'EA',
link: function(scope, elm, attr) {
scope.$on('objectSet', function(){
console.log('retrieving object', testService.getObject());
})
}
}
});
You could also pass data along with the event, if you wanted to emit a specific piece of data to be picked up by the second directive.
1). Scope. Since you don't want to use controllers and isolated scope, then you can simply set this object as scope property.
myApp.directive('myDirective', function() {
return {
restrict: 'EA',
link: function(scope, element, attr) {
var object = {};
object.test = 21;
// set object as scope property
scope.object = object;
}
}
});
myApp.directive('mySecondDirective', function() {
return {
priority: 1, // priority is important here
restrict: 'EA',
link: function(scope, element, attr) {
console.log('Retrieve: ', scope.object);
}
}
});
Just make sure you are also defining priority on the second directive (only if both directive a applied to the same element) to make sure it's evaluated in proper turn (should be bigger then the one of myDirective, if omitted it's 0).
2). Service. Another simple solution is to use service. You can inject custom service into both directives and use it storage for you shared object.
Expanding from what #gmartellino said.
Anything that you wanted to do after listening to the event in second directive, can have a callBack method and use it.
app.directive('mySecondDirective', function(testService) {
return {
restrict: 'EA',
link: function(scope, elm, attr) {
// what if I created like this ?
// define the test variable
var test;
scope.$on('objectSet', function(){
//set the variable
test = testService.getObject();
console.log('retrieving object : ', testService.getObject());
//Anything that you wanted to do
//after listening to the event
//Write a callBack method and call it
codeToExecuteAsCallBack();
})
//then use this method to make a call back from the event
//and outside the event too.
var codeToExecuteAsCallBack = function(){
console.log(test);
}
codeToExecuteAsCallBack();
}
}
});
Updated plnkr link

Manually applying the ngModel directive

My directive needs to use ngModel.
I need to do this dynamically from within another directive as I want to do some funky stuff with scopes and abstract this away from the person writing the HTML.
My first thought was to use the $set function provided by the attrs argument in the link function, that works to modify the HTML but the directive itself does not get compiled. We can then combine this with the $compile provider, and it works.
attrs.$set('ngModel', someVar);
$compile(element)(scope);
The problem is that this creates infinite recursion if I do not (and I can not) replace the elements tag as the directive gets reapplied and recompiled indefinitely.
However I can fiddle with the priorities and get that to work:
module.directive('input', [
'$compile',
function($compile) {
return {
restrict: 'E',
scope: {},
priority: 100, // Set this high enough to perform other directives
terminal: true, // Make sure this is the last directive parsed
link: function(scope, element, attrs) {
var key = 'example';
attrs.$set('ngModel', key);
$compile(element, null, 100)(scope);
}
};
}
]);
This works fine, but it just feels wrong:
I now have to ensure that all other directives on the element are
capable of being recompiled as they will all get compiled twice.
I have to make sure that nobody uses a higher priority.
So this got me thinking why can't I just inject the ngModelDirective and force compile it against my element?
module.directive('input', [
'ngModelDirective',
function(ngModel) {
return {
restrict: 'E',
scope: {},
priority: 100, // Set this high enough to perform other directives
terminal: true, // Make sure this is the last directive parsed
require: '?^form',
link: function(scope, element, attrs, formCtrl) {
var key = 'example';
attrs.$set('ngModel', key);
var ngModelFactory = ngModel[0];
var ngModelLink = ngModelFactory.compile(element);
ngModelLink.call(this, scope, element, attrs, [ngModelFactory.controller, formCtrl]);
}
};
}
]);
See: https://github.com/angular/angular.js/blob/v1.2.x/src/ng/directive/input.js#L1356
No errors thrown, but nothing happens. It seems this isn't enough to hook it up, so my question is can anyone elaborate on to what I need to do link the ngModelDirective to my custom directive without forcing a recompile?
ngModel seems a bad fit for what you are trying to do. But you don't need it anyway. You can two-way-bind some variable and pass the name into the model directive scope:
app.directive("myDirective", function() {
// ...
scope: {
myModel = "=",
modelName = "myModel"
// ...
}
// ...
});
app.directive("ngModelDirective", function() {
// ...
// ...
transclude: true,
link: function(scope, element, attrs) {
var modelName = scope.modelName;
console.assert(modelName, '`modelName` must be set when using `ngModelDirective`.');
// TODO: Check if `scope[modelName]` is actually bound
doSomethingFancyWith(scope, modelName);
}
});
Template example:
<myDirective ngModelDirective my-model="..." />
Note that doSomethingFancyWith can read and write the model variable, with bindings to the outside world.
I don't think it is possible without a re-compile.
The ngModel is designed to be a kind of collaborator between other directives in the same element and also parent form diretives. For example, during complilation:
other directives (e.g. input, required or ng-change) may add its own $parser or $formatter to ngModel.
ngModel will add itself to a parent form directive if exists.
Therefore, if the ngModel is somehow added after the complication process is ended already, the above two actions will be missing.
Edit: In case the value to be assigned to ng-model attribute is known at the compile time, it is possible and will be something like this:
app.directive('myNgModel', function($compile) {
return {
restrict: 'A',
replace: false,
priority: 1000,
terminal: true, // these terminal and priority will stop all other directive from being compiled.
link: function (scope, element, attrs) {
var key = 'example';
attrs.$set('ngModel', key);
attrs.$set('myNgModel', null); // remove itself to avoid a recusion
$compile(element)(scope); // start compiling other directives
}
};
});
Here is the plunker with example: http://plnkr.co/edit/S2ZkiVIyq2bOK04vAnFO?p=preview
I've managed to do it. It's not the prettiest thing but it works and I can hook up my input directive to work using the native inputDirective so that it can use things like require or validate specific input types.
To build this against another standard directive that implements specific ngModel functionality such as ngChange just replace the injected inputDirective with the correct directive e.g., ngChangeDirective.
module.directive('input', function() {
return {
restrict: 'E',
scope: {},
require: '?ngModel',
priority: -1,
link: function(scope, element, attrs, ngModel) {
var key = 'example.property';
if (ngModel === undefined) {
attrs.$set('ngModel', key);
angular.injector(['ng']).invoke([
'inputDirective',
'ngModelDirective',
'$controller',
'$exceptionHandler',
'$parse',
'$animate',
function(inputDirective, ngModelDirective, $controller, $exceptionHandler, $parse, $animate) {
var ngModelFactory = ngModelDirective[0];
var ngModelLink = ngModelFactory.compile(scope); // Get the ngModel linkage function against this scope
ngModel = $controller(ngModelFactory.controller, {
$scope: scope,
$exceptionHandler: $exceptionHandler,
$attrs: attrs,
$element: element,
$parse: $parse,
$animate: $animate
}); // Call the ngModel controller and bootstrap it's arguments
// Call the inputDirective linkage function to set up the ngModel against this input
inputDirective[0].link(scope, element, attrs, ngModel);
element.data('$ngModelController', ngModel); // Allow additional directives to require ngModel on this element.
}
]);
}
}
};
});
NOTE: This will not work for ngOptions as it specifies terminal: true.

Directive listen to controller variable change

Just starting out in AngularJS and trying to figure out the best practice for listening to events when a variable within a controller changes. The only way I have gotten it to work is with an emit, as follows.
For example:
var app = angular.module("sampleApp", [])
app.controller("AppCtrl", function($scope){
$scope.elements = [
{
name: "test"
}
]
$scope.addElement = function() {
$scope.elements.push({
name: "test" + $scope.elements.length
})
$scope.$emit('elementsChanged', $scope.elements);
}
})
app.directive('render', function() {
var renderFunc = function() {
console.log("model updated");
}
return {
restrict: 'E',
link: function(scope, element, attrs, ngModel) {
scope.$on('elementsChanged', function(event, args) {
renderFunc();
})
}
}
})
This seems a bit wonky, and I feel like I'm working against the point of angular. I've tried to have a $watch on a model, but that doesn't seem to be working. Any help on this would be very appreciated, thanks!
I'm going to assume you're using unstable Angular, because $watchCollection is only in the unstable branch.
$watchCollection(obj, listener)
Shallow watches the properties of an object and fires whenever any of the properties change (for arrays, this implies watching the array items; for object maps, this implies watching the properties). If a change is detected, the listener callback is fired.
The 'Angular' way of doing this would be to watch an attribute in your directive.
<render collection='elements'></render>
Your directive
app.directive('render', function() {
var renderFunc = function() {
console.log("model updated");
}
return {
restrict: 'E',
link: function(scope, element, attrs) {
scope.$watchCollection(attrs.collection, function(val) {
renderFunc();
});
}
}
})
If you're doing this on stable angular, you can pass true as the last argument to scope.$watch, which will watch for equality rather than reference.
$watch(watchExpression, listener, objectEquality)
objectEquality (optional) boolean
Compare object for equality rather than for reference.
What's happening here is the collection attribute on the DOM element specifies which property on our scope we should watch. The $watchCollection function callback will be executed anytime that value changes on the scope, so we can safely fire the renderFunc().
Events in Angular should really not be used that often. You were right in thinking there was a better way. Hopefully this helps.

How to get evaluated attributes inside a custom directive

I'm trying to get an evaluated attribute from my custom directive, but I can't find the right way of doing it.
I've created this jsFiddle to elaborate.
<div ng-controller="MyCtrl">
<input my-directive value="123">
<input my-directive value="{{1+1}}">
</div>
myApp.directive('myDirective', function () {
return function (scope, element, attr) {
element.val("value = "+attr.value);
}
});
What am I missing?
Notice: I do update this answer as I find better solutions. I also keep the old answers for future reference as long as they remain related. Latest and best answer comes first.
Better answer:
Directives in angularjs are very powerful, but it takes time to comprehend which processes lie behind them.
While creating directives, angularjs allows you to create an isolated scope with some bindings to the parent scope. These bindings are specified by the attribute you attach the element in DOM and how you define scope property in the directive definition object.
There are 3 types of binding options which you can define in scope and you write those as prefixes related attribute.
angular.module("myApp", []).directive("myDirective", function () {
return {
restrict: "A",
scope: {
text: "#myText",
twoWayBind: "=myTwoWayBind",
oneWayBind: "&myOneWayBind"
}
};
}).controller("myController", function ($scope) {
$scope.foo = {name: "Umur"};
$scope.bar = "qwe";
});
HTML
<div ng-controller="myController">
<div my-directive my-text="hello {{ bar }}" my-two-way-bind="foo" my-one-way-bind="bar">
</div>
</div>
In that case, in the scope of directive (whether it's in linking function or controller), we can access these properties like this:
/* Directive scope */
in: $scope.text
out: "hello qwe"
// this would automatically update the changes of value in digest
// this is always string as dom attributes values are always strings
in: $scope.twoWayBind
out: {name:"Umur"}
// this would automatically update the changes of value in digest
// changes in this will be reflected in parent scope
// in directive's scope
in: $scope.twoWayBind.name = "John"
//in parent scope
in: $scope.foo.name
out: "John"
in: $scope.oneWayBind() // notice the function call, this binding is read only
out: "qwe"
// any changes here will not reflect in parent, as this only a getter .
"Still OK" Answer:
Since this answer got accepted, but has some issues, I'm going to update it to a better one. Apparently, $parse is a service which does not lie in properties of the current scope, which means it only takes angular expressions and cannot reach scope.
{{,}} expressions are compiled while angularjs initiating which means when we try to access them in our directives postlink method, they are already compiled. ({{1+1}} is 2 in directive already).
This is how you would want to use:
var myApp = angular.module('myApp',[]);
myApp.directive('myDirective', function ($parse) {
return function (scope, element, attr) {
element.val("value=" + $parse(attr.myDirective)(scope));
};
});
function MyCtrl($scope) {
$scope.aaa = 3432;
}​
.
<div ng-controller="MyCtrl">
<input my-directive="123">
<input my-directive="1+1">
<input my-directive="'1+1'">
<input my-directive="aaa">
</div>​​​​​​​​
One thing you should notice here is that, if you want set the value string, you should wrap it in quotes. (See 3rd input)
Here is the fiddle to play with: http://jsfiddle.net/neuTA/6/
Old Answer:
I'm not removing this for folks who can be misled like me, note that using $eval is perfectly fine the correct way to do it, but $parse has a different behavior, you probably won't need this to use in most of the cases.
The way to do it is, once again, using scope.$eval. Not only it compiles the angular expression, it has also access to the current scope's properties.
var myApp = angular.module('myApp',[]);
myApp.directive('myDirective', function () {
return function (scope, element, attr) {
element.val("value = "+ scope.$eval(attr.value));
}
});
function MyCtrl($scope) {
}​
What you are missing was $eval.
http://docs.angularjs.org/api/ng.$rootScope.Scope#$eval
Executes the expression on the current scope returning the result. Any exceptions in the expression are propagated (uncaught). This is useful when evaluating angular expressions.
For an attribute value that needs to be interpolated in a directive that is not using an isolated scope, e.g.,
<input my-directive value="{{1+1}}">
use Attributes' method $observe:
myApp.directive('myDirective', function () {
return function (scope, element, attr) {
attr.$observe('value', function(actual_value) {
element.val("value = "+ actual_value);
})
}
});
From the directive page,
observing interpolated attributes: Use $observe to observe the value changes of attributes that contain interpolation (e.g. src="{{bar}}"). Not only is this very efficient but it's also the only way to easily get the actual value because during the linking phase the interpolation hasn't been evaluated yet and so the value is at this time set to undefined.
If the attribute value is just a constant, e.g.,
<input my-directive value="123">
you can use $eval if the value is a number or boolean, and you want the correct type:
return function (scope, element, attr) {
var number = scope.$eval(attr.value);
console.log(number, number + 1);
});
If the attribute value is a string constant, or you want the value to be string type in your directive, you can access it directly:
return function (scope, element, attr) {
var str = attr.value;
console.log(str, str + " more");
});
In your case, however, since you want to support interpolated values and constants, use $observe.
The other answers here are very much correct, and valuable. But sometimes you just want simple: to get a plain old parsed value at directive instantiation, without needing updates, and without messing with isolate scope. For instance, it can be handy to provide a declarative payload into your directive as an array or hash-object in the form:
my-directive-name="['string1', 'string2']"
In that case, you can cut to the chase and just use a nice basic angular.$eval(attr.attrName).
element.val("value = "+angular.$eval(attr.value));
Working Fiddle.
For the same solution I was looking for Angularjs directive with ng-Model.
Here is the code that resolve the problem.
myApp.directive('zipcodeformatter', function () {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
link: function (scope, element, attrs, ngModel) {
scope.$watch(attrs.ngModel, function (v) {
if (v) {
console.log('value changed, new value is: ' + v + ' ' + v.length);
if (v.length > 5) {
var newzip = v.replace("-", '');
var str = newzip.substring(0, 5) + '-' + newzip.substring(5, newzip.length);
element.val(str);
} else {
element.val(v);
}
}
});
}
};
});
HTML DOM
<input maxlength="10" zipcodeformatter onkeypress="return isNumberKey(event)" placeholder="Zipcode" type="text" ng-readonly="!checked" name="zipcode" id="postal_code" class="form-control input-sm" ng-model="patient.shippingZipcode" required ng-required="true">
My Result is:
92108-2223
var myApp = angular.module('myApp',[]);
myApp .directive('myDirective', function ($timeout) {
return function (scope, element, attr) {
$timeout(function(){
element.val("value = "+attr.value);
});
}
});
function MyCtrl($scope) {
}
Use $timeout because directive call after dom load so your changes doesn`'t apply

Categories

Resources