AngularJS custom directive two way data binding - javascript

I am new to Angular.js and I am trying to make a custom directive(which has a controller with functions in it) that is linked to a controller. When an object in the controller($scope.MyObj), changes I would like to have a similar object in my directive controller that changes the same way. In addition is it possible to invoke a function/scope method that is declared in my directive controller, from my basic controller(or invoke a function from my directive controller when an object from the basic controller has changed.) ?

Aviv Ben-Yosef writes a pretty nice post about it on http://www.codelord.net where he basically hooks into the controller from a isolated directive scope:
http://www.codelord.net/2015/09/02/controller-directive-communication-part-3-controller-to-directive/

if you are using Angular 1.4 and above you can use bindToController something like this:
.directive('mdAddress', function mdAddress() {
var directive = {
restrict: 'EA',
scope: {},
bindToController: {
address: '='
},
templateUrl: 'modules/address/address.html',
controller: AddressController,
controllerAs: 'dir'
};

Related

Pass variables to a dynamic Controller inside a Directive AngularJS

I got a directive that loads a different template depending on the variable type that is passed to it. I pass to the isolated scope of the directive the variables patient and service too.
.directive('serviceCharts', serviceCharts);
function serviceCharts() {
return {
restrict: 'E',
link: function (scope, element, attrs) {
if(attrs.type) {
scope.template = 'views/' + type + '.html';
}
attrs.$observe('type', function (type) {
if(attrs.type) {
scope.template = 'views/' + type + '.html';
}
});
},
template: '<div ng-include="template"></div>',
scope:{
patient:'=',
service:'='
}
};
}
In the template (views/myservice.html for example) I load the controller:
<div ng-controller="myCtrl as vm">
VIEW
</div>
And in the controller (myCtrl) I access to the patient and service this way:
service = $scope.$parent.$parent.service;
patient = $scope.$parent.$parent.patient;
This works fine, but I don't like this way of accessing the variables via the $parent.$parent. This is messing with my tests too.
Is there another (better) way to do this?
Thank you!
You could create a wrapper object for patient & service properties. That can be named as model & then provide that model object to your directive.
Then problem with your current approach is, ng-include create a child scope for template which it renders in it. So as your passing primitive type object binding to directive, If you are changing any of child primitive type binding in child scope. It loses a binding that's why tend to using $parent.$parent notation exactly bind to original source object.
$scope.model = {
patient:'My Patient',
service:'My Service'
};
By making above object structure will ensure you're following Dot Rule. Usage of Dot Rule will avoid $parent.$parent explicit scope annotation.
Directive scope binding will changed down to below
scope:{
model:'='
}
And directive usage will look like below
<service-charts type="{{'sometype'}}" model="model"></service-charts>
The other alternative than Dot Rule to such kind of scoping related issue is follow controllerAs pattern. But then as you are gonna use isolated scope with controllerAs you should make bindToController: true option to true for making sure all the scope are merged down to controller context.
scope:{
patient:'=',
service:'='
},
controllerAs: '$ctrl',
bindToController: true
And then use $ctrl before each directive scoped variable.
Yes, there is a better way to do this. You should use services and store variables in those services (in your case you should create a factory for storing data). Then you can inject those services and access their data.
Sidenote:
You can use { ..., controller: 'controllerName', controllerAs: 'vm' } syntax in your directive so you do not need to declare those in your html.

require ngModel syntax in AngularJS

I see the following:
// my-directive.js
return {
require: 'ngModel',
scope: {
ngModel: '=',
},
controller: controller,
link: myLink
};
// my-link.js
return function(scope, $element, attrs, modelController) {
scope.onMyClick = function(event) {
modelController.$setViewValue(getItem(event));
}
};
// foo.html
<my-directive ng-model="myModel"></my-directive>
Is the ng-model directive used to provide a two way data-binding to the myModel in the outer scope where the my-directive instance is declared?
Is this the idiomatic way to provide two-way data binding between a directive and an outer model in Angular 1.4?
Does the require: 'ngModel' in the directive definition do anything other than inject the controller instance for the ng-model directive instance into the link function my-link?
Finally, does it make the controller of ng-model available on the scope for the controller of my-directive to use?
There are 2 different things here - ng-model as directive and ngModel: '=' in directive as attribute. Lets split them:
<my-directive ng-model="myModel" whatever="myModel"></my-directive>
return {
require: 'ngModel',
scope: {
myModel: '=',
},
controller: controller,
link: myLink
};
Is the ng-model directive used to provide a two way data-binding to
the myModel in the outer scope where the my-directive instance is
declared?
No, 2-way binding is provided by definition inside directive. (whatever here)
Is this the idiomatic way to provide two-way data binding between a
directive and an outer model in Angular 1.4?
One you used scope : { whatever : '='} is usual way.
Does the require: 'ngModel' in the directive definition do anything
other than inject the controller instance for the ng-model directive
instance into the link function my-link
Not that much, if u use directive without ngModel it will throw error.
Finally, does it make the controller of ng-model available on the
scope for the controller of my-directive to use?
No, it doesnt add anything to your scope. You just can access injected controller.

AngularJS directive only recognize the 2-way binding type

I've a custom directive with an isolated scope which by itself works fine but only when I bind the directive's attributes with the '=' operator, thus when I define them as a 2 way binding. If I try to change them to a 1 way binding ('<') then I get this error. https://docs.angularjs.org/error/$compile/iscp?p0=xflWorkout&p1=create&p2=%3C&p3=isolate%20scope%20definition
Here is an example of my directive:
angular.module('directive.module', ['directive.dependency'])
.directive('directiveName', function(){
return {
restrict: 'E',
scope: {
'attr1': '=info',
'create': '<',
'attr3': '<',
'attr4': '=',
'attr5': '<'
},
templateUrl: 'template.html',
replace: true,
controller: function($scope, $element, $attrs, $transclude, ...){
//controller code`enter code here`
}
}});
Also, I'm using angular 1.4.2.
Even though my app works fine, I'd like to know why is it behaving like this so thanks for any help! :)
Angular 1.4.x does not support one-way binding.

AngularJS : pass ng-model from directive component to controller

Inspired by angular ui, I want to create front-end library as a pieces of component, all as an angularjs directive. So that the user can simply put directive with some configurations and get the desire result of component.
So this is how the directive will look like.
<date-picker
date-format="MM.DD.YYYY HH:mm"
enable-navigation
date-picker-id="datetimepicker2"
date-picker-model="myModel1">
</date-picker>
For the usage, the idea is that it could be wrapped by user-created controller and the controller can reach the directive scope like this.
<div ng-controller="myController">
<date-picker
...
date-picker-model="myModel1">
</date-picker>
</div>
(The reason I use component-name-model is because the component directive template might have more than one model) And the code in controller would be
angular.module('myApp').controller('myController',['$scope', function($scope) {
$scope.myModel1; //this scope binds to the datepicker template scope
}]);
Since I'm pretty new to angularJs, my questions are follow.
How to make the controller reach the directive scope with this syntax ? In my case, It seems that the controller didn't notice about directive scope (see my code in plunker)
Right now I'm also stuck with passing model to the template. as you can see in directive I've define date-picker-model="myModel1" and then directive will catch attrs and pass it to template like this
if('datePickerModel' in attrs){
$scope.datePickerModel = attrs.datePickerModel;
}
and when I'm using expression on templateUrl, ng-model="{{datePickerModel}}" doesn't work
The code is quite long so I would suggest you the check out my plunker
Thank you :-)
take a look at the scope parameter by creating your directive. There you can assign your 2-way binding between the controller and the directive's scope.
http://plnkr.co/edit/ngdoc:example-example85#snapshot?p=preview
<my-customer info="igor"></my-customer>
angular.module('docsIsolateScopeDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' };
$scope.igor = { name: 'Igor', address: '123 Somewhere' };
}])
.directive('myCustomer', function() {
return {
restrict: 'E',
scope: {
customerInfo: '=info'
},
templateUrl: 'my-customer-iso.html'
};
});
also documented here: http://docs.angularjs.org/guide/directive
be also aware of the beginning symbol '=' or '#'!

How to inject service depending on directive attribute in angular?

I have the following directive
directiveModule.directive('typeahead', function() {
return {
restrict: 'E',
transclude: true,
scope: 'isolate',
templateUrl: 'assets/partials/common/typeahead.html' ,
controller: ["$scope","$element","$attrs","$transclude", "Event",
function ($scope,$element,$attrs,$transclude, Event){
console.log(eval($attrs.service + ".query();"));
$scope.search = function(){ console.log("Searching for:" + $scope.selectedValue) };
$scope.selectedValue = "";
$scope.providedItems = [];
}],
};
});
With the following template:
<div>
<input ng-change="search()" ng-model="selectedValue" ng-click="log()" autocomplete="off" type="text"/>
<ul class=".typeahead">
<li ng-repeat="item in providedItems">{{eval(item + "." + descriptor)}}</li>
</ul>
and the following call inside my view:
I would like to let the service attribute be evaluated at runtime when injecting to the controller in the directive. So that where it now says
controller: ["$scope","$element","$attrs","$transclude", "Event",
function ($scope,$element,$attrs,$transclude, Event){
It would say something similar to:
controller: ["$scope","$element","$attrs","$transclude", eval($attrs.service),
function ($scope,$element,$attrs,$transclude, eval($attrs.service)){
However I can't access the $attrs from the scope inside the directiveModule call.
If there is any way to access a service declared in the view that would suffice. Please help!
One solution for this, would be creating and binding the controller yourself. All you need is to inject both $injector (in order to resolve the service dynamically) and $controller (in order to resolve the controller dynamically). And in your linking function you create the controller yourself:
link: function(scope, elm, attr) {
$controller(function YourController($scope, dynamnicService) {
dynamicService.query();
}, {
$scope: scope,
dynamicService: $injector.get($attr.service)
}
)
}
There is one thing important here. I'm considering, in this example, that your service is a string in the element attribute. If it's a string inside the scope, referred by the attribute, then you have to change the approach. You should $attr.observe the attribute, and on change, you should grab the service $injector.get(...) and pass it to the controller. You ould either create a this.setService method on the controller itself, or $scope.setService method, your call. I'd rather the controller, as this is related to accessing a service. Using this second approach, you don't need to create the controller by hand, you can simple expose this method and set the data from outside.
One more info, you should NEVER TOUCH THE DOM FROM YOUR CONTROLLER. So passing $element, $attr and $transculde to the controller is probably a bad idea, no matter what.
Take a look at the docs about $controller, $injector and directive.

Categories

Resources