How to inject service depending on directive attribute in angular? - javascript

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.

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.

JS - AngularJS - Get data of an directive from a controller

I´ll give my best to make my problem understandable:
I have a directive. This directive handles/alters also some data it displays. Now I was wondering how I can access these data from a parent scope e.g. the controller. I am currently getting access to the scope by selecting the element and ask for the corresponding scope:
element.scope()
It works fine but it occurs to me that this is kind of a - lets say - unconventional or odd way to get the scope of an directive.
I apologize for my english (I still practicing) and hope you guys can help me. Thank you in advance :)
OK, Based on the comments your directive is some kind of a form. There are two approaches for controlling the data inside a directive:
Parent controller of a directive should control the data. In that case, Data of each directive is unique and does not share between all directives.
The directive itself should control the data. Which it means the data will be shared in all directives, and if You change one, another will be changed too.
I go with the solution number 1 which is more preferable in my guess. Let's say your template of directive is something like this,We call it template.html:
<form name="directive_from" ng-submit="submit_form()">
<input type="text" ng-model="form.name" name="username" placeholder="username"><br>
<button type="submit">Submit</button>
</form>
Because of the time,I skipped validation but you can do that easily :)
And the directive:
angular.module('app')
.directive('formDirective',function(){
return {
restrict: 'E',
templateUrl: 'template.html',
scope: {
form: '=formData',
submit_form: '#formFunc'
},
controller: ['$scope', function($scope) {
//some extra logic or binding if you need
}
}
})
OK,Now our directive which we call it formDirective accepts two parameters:
formData which is an object and holds user inserted data.
formFunc which is a function for submitting the form.
Now parent controller:
angular.module('app')
.controller('MainCtrl', ['$scope', function($scope) {
$scope.form_object = {};
$scope.submit_it = function() {
//Do whatever you like with the $scope.form_object
}
}
And let's see the view:
<div ng-controller="MainCtrl">
<form-directive form="form_object" form-func="submit_it()"></form-directive>
</div>
That's it! It's so simple example that I believe you can customize it by your needs,But the main logic for controlling the data is something like that. for example you can pass a function for ng-change on the input field or add more input fields or...
#lilly: I think you you are looking for data exchange between the parent scope and isolated scope which is possible via '='
Here is the working fiddle
angular.module('App', [])
.controller('Main', ['$scope', function ($scope) {
$scope.exchange = {};
$scope.exchange.value = "initialized by controller";
}])
.directive('inputValue', function($timeout) {
return {
restrict: 'A',
scope: {exchangeValue: '='},
link: function(scope, element, attrs) {
$timeout(function(){
scope.exchangeValue="changed by directive";
},2000);
}
};
});
http://jsfiddle.net/tiru/dJty6/43/

angularjs directive: how communicate between link and controller?

I have a directive whose 'config' attribute value I need to access inside my directive controller.
Since the controller constructor get executed first,communication from controller to link is possible but not vice versa.
What should be the best way to achieve this?
I have considered the following approaches
1)Add the variable to scope-
That would in my opinion pollute the scope,making the variable accessible every where else where the scope is being shared.
2)Use $broadcast
Again the same issue as above
3) Pass a callback function on controller's this and call it from the link function with config as its argument
4)Pass the value through a service- In my case I have multiple such directives that would need to pass date through this service
Or is there some better approach that I am missing out for doing this?
module.directive('myDirective',function(){
return{
restrict:'E',
templateUrl:'path/to/html',
link:function(scope,iElement,iAttrs,controller){
var config=iAttrs.config;
//How to access this value inside the directive controller?
},
controller:function($scope){
//the directive attribute 'config' is required here for some larger computations which are not
//manipulating the DOM and hence should be seperated from the link function
})
There you can use isolated scope concept where you create isolated scope inside your controller & that would not be prototypically inherited from its parent scope. For that you need to use scope: { ... } inside your directive option. There are three options to pass scope value inside a directive through attribute
# : One way binding
= : Two way binding
& : Expression
In your case first two cases would be fine that are depends which one you need to use. If you just want to pass the value of scope variable to the directive in that case you could use 1st approach which would be # one way binding.
If you want to update the variable in both directive as well as controller from where it come i.e. nothing but two way binding, then you need to use =
I think = suits in your case so you should go for =
Markup
<my-directive config="config"></my-directive>
Directive
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
config: '='
},
templateUrl: 'path/to/abc.html',
link: function(scope, iElement, iAttrs, controller) {
//here it will be access as scope.config
console.log(scope.config);
},
controller: function($scope) {
console.log($scope.config); //here also it would be available inisde scope
//you could put a watch to detect a changes on config
}
}
});
Demo Plunkr
Update
As config value has been provide from the attribute with expression like {{}} so we could get those changes inside controller by putting [**$observe**][2] on $attrs. For that you need to inject $attrs dependency on your controller that will give you all the attributes collection which are available on directive element. And on the same $attrs object we gonna put $observe which work same as that of $watch which does dirty checking & if value gets change it fires that watch.
Directive
app.directive('myDirective', function() {
return {
restrict: 'E',
templateUrl: 'path/to/abc.html',
link: function(scope, iElement, iAttrs, controller) {
//here it will be access as scope.config
console.log(scope.config);
},
controller: function($scope,$attrs) {
//you could put a watch to detect a changes on config
$attrs.$observe('config', function(newV){
console.log(newV);
})
}
}
});
Updated Demo

AngularJS directive parameter "ctrls"

I see a link function in a directive in Angular like so:
link: function (scope, element, attrs, ctrls) {
var ngModelCtrl = ctrls[0],
invalidInputController = ctrls[1];
// ...
}
Can someone help me understand where the controllers in the ctrl parameter are coming from?
Edit: the directive has an angular require property specifying two other directives by name like so:
require: ['ngModel', '?numberFormatterPreventInvalidInput'],
I suspect they come from there.
Typically they come from the require part of a directive, which is either a string or an array of controllers.
['^something', '^another']
Within link they are accessed by ctrl[0] and ctrl[1].
Also from the documentation:
The basic difference (between controller and link) is that controller can expose an API, and link
functions can interact with controllers using require.
Best Practice: use controller when you want to expose an API to other
directives. Otherwise use link.
if in your directive is written
require:["ngModel","^directiveTwo"]
than ngModel , directiveTwo are names of directives and directiveTwo has to be a parent directive
link: function (scope, element, attrs, ctrls) {
var ngModelCtrl = ctrls[0],
controllerOfDirectiveTwo = ctrls[1];
// ...
}
ctrls the last parameter isthe array of the controller of the directives defined by require so for ngModel this :https://docs.angularjs.org/api/ng/type/ngModel.NgModelController for your directives defined by the controller: in your directive

ngBind equivalent of NgModelController (or best practice)

http://plnkr.co/edit/C4mFd5MOLBD2wfm8bMhJ?p=preview
Let's take a simple example and say you want to display the value of a cookie regardless of what it is, but this could be a customer name or whatever you want. There seem to be so many options available: directives, services, directives with services, controllers - and no matter how many docs I review or blog posts I read, I still have some fundamental questions about the appropriate way to access data and then update the scope accordingly.
What's clouding my thought right now is the fact that there doesn't seem to be the equivalent of NgModelController for non ngModel capable DOM elements like span or div or anything besides user input. Basically, seeing how ngModelCtrl is utilized in the link function of a directive seems to make a lot of sense, it doesn't allow you to drown in scope soup and it nicely organizes your thoughts, but how do we achieve this decoupling with ngBind elements?
I think the answer is just 'use services', but perhaps maybe not in all cases is the thing that's gnawing at me. Suppose you want to display a very specific cookie (or a customer name) and you don't know where you want to display it, you could continually inject your custom CookieService where ever you go, but what about a specific directive that cleans things up: <specific-cookie></specific-cookie> Would we just inject our CookieService into that directive, or just access it via $cookies like we've done elsewhere.
Does the answer simply lie in whether or not you'll be accessing more than one cookie in the future? That is, if you only need one <specific-cookie></specific-cookie>, then just use $cookies in you're directive and move on with your life, or it is always appropriate to abstract away this type of call into a service, or am I just being super pedantic about understanding this.
Directive
angular-myapp.js
var app = angular.module('myApp', ['ngCookies']);
app.directive('cookie', ['$cookies', function($cookies) {
return {
scope: true,
controller: function($scope, $element, $attrs) {
$scope.cookie = $cookies[$attrs.cookie];
}
}
}]);
index.html
<div cookie="__utma">Cookie: {{cookie}}</div>
Controller
angular-myapp.js
app.controller('CookieCtrl', function($attrs, $cookies) {
this.value = $cookies[$attrs['getcookie']];
});
index.html
<a ng-controller="CookieCtrl as cookie" getCookie="__utma" href="/{{cookie.value}}">{{cookie.value}}</a>
Service
angular-myapp.js
app.controller('SomeCtrl', function($scope, CookieService) {
$scope.cookie = CookieService.getCookie('__utma');
});
app.service('CookieService', function($cookies) {
var getCookie = function(cookie) {
return $cookies[cookie];
};
return ({ getCookie: getCookie });
});
index.html
<div ng-controller="SomeCtrl">Cookie: {{cookie}}</div>
Directive with service
angular-myapp.js
app.directive('specificCookie', function(CookieService) {
return {
scope: true,
template: 'Cookie: <span ng-bind="cookie"></span>',
controller: function($scope, $element, $attrs) {
$scope.cookie = CookieService.getCookie('__utma');
}
}
});
index.html
<specific-cookie></specific-cookie>
Unless I'm misunderstanding some of your scenarios, the simplest (and proper) way to do this is to create a reusable directive that displays a cookie based on a name passed to it via its attribute.
app.directive('cookie', ['$cookies', function($cookies) {
return {
scope: {},
template: "<span>{{cookie}}</span>",
restrict: "E",
link: function(scope, element, attrs) {
attrs.$observe("name", function(newVal){
scope.cookie = $cookies[newVal];
});
}
};
}]);
The usage would be trivial (and page controller-independent):
<cookie name="__utma"></cookie>
<input ng-model="cookieName" type="text">
<cookie name="{{cookieName}}"></cookie>
the resulting DOM would be:
<span class="ng-binding">137862001.838693016.141754...</span>
<span class="ng-binding">GA1.2.838693016.1417544553</span>

Categories

Resources