AngularJS one way binding using ngModel - angularjs

How can I isolate ngModel in a directive without a two-way binding. My current code is not working:
scope: {
ngModel: "&"
},
I don't get my collection but a method instead.

depending on your angular version, you could use in your view :: to turn two-way data-binding in to one way.
for example:
<element ng-model="::object.value"></element>
this should work from angular version 1.3 and up

ngModel uses a two-way binding. So you need to eval that as an expression, and then use the result.
<element ng-model="myvar"></element>
$scope.$watch(function() {
return attr.ngModel
}, function(value) {
var oneWay = $scope.$eval(value);
});
Don't use an isolated scope, because you want access to the scope that is on the element that ngModel is on.

This is how you can do what you asking. (Having no idea if this can be used for something useful thow)
If you declare isolated scope and use ng-model, you can get value from ngModelCtrl.(require it in directive):
app.directive('test', function($timeout) {
return {
scope : {},
require: 'ngModel',
link : function(scope, elem, attrs, ctrl) {
console.log(ctrl);
ctrl.$viewChangeListeners.push(function() {
scope.test = ctrl.$modelValue;
console.log('test = ', scope.test)
})
}
};
});
And you have ngModel value in your isolated scope one-way binded.
http://plnkr.co/edit/4xGhb5OYRVg6gtzLDHlY?p=preview

Related

one way object from controller to directive

I want to pass object from controller to directive but not two way binding.
I tried with JSON.stringify and it work but in HTML it display whole data.
Is there any way around to achieve this?
In my controller:
$scope.obj= { selectedItems=[1,2,3]};
In html:
<my-dir pass-obj= "obj"><my-dir>
In directive:
scope:{passObj='#'}
It give me string as "obj" not the object.
In older versions of AngularJS one-way binding is done with $watch.
app.directive("myDir", function() {
return {
scope: {},
link: function(scope, elem, attrs) {
//one-way binding
scope.$parent.$watch("attrs.passObj", function(newValue) {
scope.passObj = newValue;
});
}
};
});
The above example is the equivalent of doing one-way binding with AngularJS 1.5:
scope: { passObj: '<' }
This should work..
controller:
$scope.obj= { selectedItems=[1,2,3]};
html:
<my-dir passObj= "::obj"><my-dir>
It's been asked before, and you can read more here; One-way binding in Angular directives

Please provide me explanation for the Slider directive

I am trying to understand the Slider directive provided here.
myApp.directive('slider', function() {
return {
restrict: 'A',
scope: {
ngModel: '='
},
link: function(scope, elem, attrs) {
console.log(scope.ngModel);
return $(elem).slider({
range: "min",
animate: true,
value: scope.ngModel,
slide: function(event, ui) {
return scope.$apply(function(){
scope.ngModel = ui.value;
});
}
});
}
};
});
Like purpose of ngmodel, range, animate, value:scope.ngModel etc. I have read some article about the same from here but this seems to be a little complicated for me.
This is a typical isolated scope directive configuration that is using a jQuery plugin .
scope.ngModel is passed in to the directive through the matching attribute ng-model in the html.
The '=' makes it a 2 way binding to it's parent.
As for the slider it is a jQuery UI slider and the options like animate and range etc are documented in their API
$apply() is used whenever events outside of angular context are used to modify scope. Angular needs to be told to run a digest to update the view

Unable to watch form $dirty (or $pristine) value in Angular 1.x

I have a scenario in an Angular 1.x project where I need to watch a controller form within a directive, to perform a form $dirty check. As soon as the form on a page is dirty, I need to set a flag in an injected service.
Here is the general directive code:
var directiveObject = {
restrict: 'A',
require: '^form',
link: linkerFn,
scope: {
ngConfirm: '&unsavedCallback'
}
};
return directiveObject;
function linkerFn(scope, element, attrs, formCtrl) {
...
scope.$watch('formCtrl.$dirty', function(oldVal, newVal) {
console.log('form property is being watched');
}, true);
...
}
The above only enters the watch during initialization so I've tried other approaches with the same result:
watching scope.$parent[formName].$dirty (in this case I pass formName in attrs and set it to a local var formName = attrs.formName)
watching element.controller()[formName] (same result as the above)
I've looked at other SO posts regarding the issue and tried the listed solutions. It seems like it should work but somehow the form reference (form property references) are out of scope within the directive and therefore not being watched.
Any advice would be appreciated.
Thank you.
I don't know why that watch isn't working, but as an alternative to passing in the entire form, you could simply pass the $dirty flag itself to the directive. That is:
.directive('formWatcher', function() {
restrict: 'A',
scope: {
ngConfirm: '&unsavedCallback', // <-- not sure what you're doing with this
isDirty: '='
},
link: function(scope, element, attrs) {
scope.watch('isDirty', function(newValue, oldValue) {
console.log('was: ', oldValue);
console.log('is: ', newValue);
});
}
})
Using the directive:
<form name="theForm" form-watcher is-dirty="theForm.$dirty">
[...]
</form>

Should I use isolate scope in this case?

I'm implementing an custom input widget. The real code is more complex, but generally it looks like this:
app.directive('inputWidget', function () {
return {
replace:true,
restrict: 'E',
templateUrl:"inputWidget.html",
compile: function (tElement, tAttributes){
//flow the bindings from the parent.
//I can do it dynamically, this is just a demo for the idea
tElement.find("input").attr("placeholder", tAttributes.placeholder);
tElement.find("input").attr("ng-model", tElement.attr("ng-model"));
}
};
});
inputWidget.html:
<div>
<input />
<span>
</span>
</div>
To use it:
<input-widget placeholder="{{name}}" ng-model="someProperty"></input-widget>
The placeholder is displayed correctly with above code because it uses the same scope of the parent: http://plnkr.co/edit/uhUEGBUCB8BcwxqvKRI9?p=preview
I'm wondering if I should use an isolate scope, like this:
app.directive('inputWidget', function () {
return {
replace:true,
restrict: 'E',
templateUrl:"inputWidget.html",
scope : {
placeholder: "#"
//more properties for ng-model,...
}
};
});
With this, the directive does not share the same scope with the parent which could be a good design. But the problem is this isolate scope definition will quickly become messy as we're putting DOM-related properties on it (placeholder, type, required,...) and every time we need to apply a new directive (custom validation on the input-widget), we need to define a property on the isolate scope to act as middle man.
I'm wondering whether it's a good idea to always define isolate scope on directive components.
In this case, I have 3 options:
Use the same scope as the parent.
Use isolate scope as I said above.
Use isolate scope but don't bind DOM-related properties to it, somehow flow the DOM-related properties from the parent directly. I'm not sure if it's a good idea and I don't know how to do it.
Please advice, thanks.
If the input-widget configuration is complex, I would use an options attribute, and also an isolated scope to make the attribute explicit and mandatory:
<input-widget options="{ placeholder: name, max-length: 5, etc }"
ng-model="name"></input-widget>
There is no need to flow any DOM attributes if you have the options model, and the ngModel:
app.directive('inputWidget', function () {
return {
replace:true,
restrict: 'E',
templateUrl:"inputWidget.html",
scope: { options:'=', ngModel: '='}
};
});
And in your template, you can bind attributes to your $scope view model, as you normally would:
<div>
<input placeholder="{{options.placeholder}}" ng-model="ngModel"/>
<span>
{{options}}
</span>
</div>
Demo
Personally, when developing for re-use, I prefer to use attributes as a means of configuring the directive and an isolated scope to make it more modular and readable. It behaves more like a component and usually without any need for outside context.
However, there are times when I find directives with child / inherited scopes useful. In those cases, I usually 'require' a parent directive to provide the context. The pair of directives work together so that less attributes has to flow to the child directive.
This is not a very trivial problem. This is because one could have arbitrary directives on the templated element that are presumably intended for <input>, and a proper solution should ensure that: 1) these directives compile and link only once and 2) compile against the actual <input> - not <input-widget>.
For this reason, I suggest using the actual <input> element, and add inputWidget directive as an attribute - this directive will apply the template, while the actual <input> element would host the other directives (like ng-model, ng-required, custom validators, etc...) that could operate on it.
<input input-widget
ng-model="someProp" placeholder="{{placeholder}}"
ng-required="isRequired"
p1="{{name}}" p2="name">
and inputWidget will use two compilation passes (modeled after ngInclude):
app.directive("inputWidget", function($templateRequest) {
return {
priority: 400,
terminal: true,
transclude: "element",
controller: angular.noop,
link: function(scope, element, attrs, ctrl, transclude) {
$templateRequest("inputWidget.template.html").then(function(templateHtml) {
ctrl.template = templateHtml;
transclude(scope, function(clone) {
element.after(clone);
});
});
}
};
});
app.directive("inputWidget", function($compile) {
return {
priority: -400,
require: "inputWidget",
scope: {
p1: "#", // variables used by the directive itself
p2: "=?" // for example, to augment the template
},
link: function(scope, element, attrs, ctrl, transclude) {
var templateEl = angular.element(ctrl.template);
element.after(templateEl);
$compile(templateEl)(scope);
templateEl.find("placeholder").replaceWith(element);
}
};
});
The template (inputWidget.template.html) has a <placeholder> element to mark where to place the original <input> element:
<div>
<pre>p1: {{p1}}</pre>
<div>
<placeholder></placeholder>
</div>
<pre>p2: {{p2}}</pre>
</div>
Demo
(EDIT) Why 2 compilation passes:
The solution above is a "workaround" that avoids a bug in Angular that was throwing with interpolate values being set on a comment element, which is what is left when transclude: element is used. This was fixed in v1.4.0-beta.6, and with the fix, the solution could be simplified to:
app.directive("inputWidget", function($compile, $templateRequest) {
return {
priority: 50, // has to be lower than 100 to get interpolated values
transclude: "element",
scope: {
p1: "#", // variables used by the directive itself
p2: "=" // for example, to augment the template
},
link: function(scope, element, attrs, ctrl, transclude) {
var dirScope = scope,
outerScope = scope.$parent;
$templateRequest("inputWidget.template.html").then(function(templateHtml) {
transclude(outerScope, function(clone) {
var templateClone = $compile(templateHtml)(dirScope);
templateClone.find("placeholder").replaceWith(clone);
element.after(templateClone);
});
});
}
};
});
Demo 2

Is it ok watch the scope inside a custom directive?

I'm trying to write a directive to create a map component so I can write something like:
<map></map>
Now the directive looks like this:
angular.module('myApp')
.directive('map', function (GoogleMaps) {
return {
restrict: 'E',
link: function(scope, element, attrs) {
scope.$watch('selectedCenter', function() {
renderMap(scope.selectedCenter.location.latitude, scope.selectedCenter.location.longitude, attrs.zoom?parseInt(attrs.zoom):17);
});
function renderMap(latitude, longitude, zoom){
GoogleMaps.setCenter(latitude, longitude);
GoogleMaps.setZoom(zoom);
GoogleMaps.render(element[0]);
}
}
};
});
The problem is that 'watch' inside the directive doesn't looks very well thinking in the reusability of the component. So I guess the best thing is being able to do something like:
<map ng-model="selectedCenter.location"></map>
But I don't know if it's even a good thing using angular directives inside custom directives or how can I get the object indicated in the ng-model attribute in the custom-directive's link function.
You will need to do something like that
angular.module('myApp')
.directive('map', function (GoogleMaps) {
return {
restrict: 'E',
scope: {
ngModel: '=' // set up bi-directional binding between a local scope property and the parent scope property
},
as of now you could safely watch your scope.ngModel and when ever the relevant value will be changed outside the directive you will be notified.
Please note that adding the scope property to our directive will create a new isolated scope.
You can refer to the angular doc around directive here and especially the section "Directive Definition Object" for more details around the scope property.
Finally you could also use this tutorial where you will find all the material to achieve a directive with two way communication form your app to the directive and opposite.
Without scope declaration in directive:
html
<map ng-model="selectedCenter"></map>
directive
app.directive('map', function() {
return {
restrict: 'E',
require: 'ngModel',
link: function(scope, el, attrs) {
scope.$watch(attrs.ngModel, function(newValue) {
console.log("Changed to " + newValue);
});
}
};
});
One easy way you can achieve this would be to do something like
<map model="selectedCenter"></map>
and inside your directive change the watch to
scope.$watch(attrs.model, function() {
and you are good to go

Resources