I am trying to call (or use) few custom directives in ionic framework, dynamic is like <mydir-{{type}} where {{type}} will come from services and scope variable, having values radio, checkbox, select etc, and created my directives as mydirRadio, MydirCheckbox, mydirSelect, But its not working.
Is their any good approach to get the dynamic html as per {{type}} in scope?
Long story short; no you can't load directives dynamically in that way.
There are a few options for what you can do. You can, as other answers have mentioned, pass your context as an attribute (mydir type="checkbox"). You could make a directive that dynamically loads another directive, as also mentioned by others. Neither of these options are imo every good.
The first option only works if you write the directive yourself, not when using something like ionic. It also requires you to write multiple directives as one, which can get very messy very quickly. This mega directive will become hard to test and easy to mess up when maintaining it in the future. Note that this is the correct way to pass data to a directive from the view, it's just not good for this specific use case.
The second option is problematic because obfuscates things a bit too much. If someone reads your html and sees a directive called dynamic that is given dynamic data... they have no idea what is going to happen. If they see a directive called dropdown that is given a list they have a fair idea of what the result will be. Readability is important, don't skimp on it.
So I would suggest something simpler that requires much less work from you. Just use a switch:
<div ng-switch="type">
<mydir-select ng-switch-when="select"></mydir-select>
<mydir-checkbox ng-switch-when="checkbox"></mydir-checkbox>
</div>
I dont understand why do you need dynamic directives.
Simple use single directive and change the template accordingly.
For example -
angular.module('testApp')
.directive('dynamicDirective', function($compile,$templateCache,$http) {
return {
restrict: 'C',
link: function($scope,el) {
//get template
if(radio){
$http.get('radio.html', {cache: $templateCache}).success(function(html){
//do the things
el.replaceWith($compile(html)($scope));
});
} else if(checkbox){
//load checkbox template
} //vice-versa
}
};
});
You can inject service variable in directive also.
a bit more code would help. I don't know, if its possible to do dynamic directives like the ones in a tag
<{dyntag}></{dyntag}>
but you also can use an expression like
<your-tag dynamic_element="{type}">...</your-tag>
which should have exactly the same functionality. In your case it would be like:
Your JSObject ($scope.dynamics):
{"radio", "checkbox", "select"}
and your HTML:
<div ng-repeat="dyn in dynamics">
<your-tag dynamic_element="{dyn}"></your-tag>
</div>
Yes, that's not a problem. You can interpolate your data using {{}} and in your directive compile a new element using that data:
myApp.directive('dynamic', function($compile, $timeout) {
return {
restrict: "E",
scope: {
data: "#var" // say data is `my-directive`
},
template: '<div></div>',
link: function (scope, element, attr) {
var dynamicDirective = '<' + scope.data + ' var="this works!"><' + scope.data + '>';
var el = $compile(dynamicDirective)(scope);
element.parent().append( el );
}
}
});
HTML:
<div ng-controller="MyCtrl">
<dynamic var="{{test}}"></dynamic>
</div>
Fiddle
Related
I have a bunch of forms in my project and all of them have a set of specific validation expressions. On top of that I have also a loading element attached to it so it makes pretty hard to reuse that button throughout the project. With that in mind I figured I could use a directive instead. The problem is that when I pass in my expressions through attributes it does not update/validate as user inputs data. Basically it just sits with the attribute like if it was a string and not an expression.
In a nutshell what I've been trying to achieve is something like this:
<submit-button label="My Label" validate="!myForm.$valid"></submit-button>
Which will return something like that:
<button ng-disabled="!myForm.$valid">My Label</button>
Here's a basic isolated template on jsfiddle of what I have so far: https://jsfiddle.net/lucasbittar/8m992bet/3/
Thank you so much!
The easiest way to achieve this in that version of Angular is to utilize a two-way attribute binding on your directive. Bind the validate attribute/scope property to your expression, and reference that in the ngDisabled directive.
An important thing to note is that ngDisabled (and other ng-prefixed directives, IIRC) operate on expressions directly, so you shouldn't use double-brackets to interpolate them.
Directive code becomes:
function submitButton () {
var submit = {
restrict: 'E',
require: ['^form'],
template: '<button ng-disabled="validate">{{ label }}</button>',
scope: {
validate: '='
},
link: link
}
return submit;
function link (scope, el, attr, formCtrl) {
scope.label = attr.label;
}
}
Check out the working fiddle here: https://jsfiddle.net/8m992bet/6/
Note that if you wanted, you could also mimic the above using an inherited scope on your directive combined with scope.$watch, but using a two-way binding is easier.
Also note that on Angular 1.5 you can utilize a one-way binding on a directive using < instead of =.
I'm a bit confused with the use of $scope in controllers and of scope in directives. Please verify if my understanding is correct (and also provide some alternative ways how to do this).
Let's say I have an html:
<div ng-controller="app1_Ctrl">
.
.
.
<input type="text" ng-model="value"/>
<input type="checkbox" />
<button ng-click="submit()"></button>
</div>
And my main.js
(function() {
angular.module('mainApp', ['app1']);
})();
And my app1 looks like this (based on official AngularJS documentation here)
(function() {
var app = angular.module('app1', []);
app.controller('app1_Ctrl', ["$scope", function($scope) {
.
.
.
}]);
app.directive('app1_Dir1', [function() {
function link(scope, element, attr) {
scope.$watch(attr.someAttrOfCheckBox, function() {
// some logic here
});
function submit() {
// some logic here
}
}
return link;
}]);
})();
How does $scope.value passed in scope in directive so that I can do some manipulations there? Will ng-click fire the function submit() in the directive link? Is it correct to use scope.$watch to listen for an action (ticked or unticked of course) in checkbox element?
Many thanks to those who can explain.
By default, directive scope is controller $scope; but it means the directive is directly dependent on your controller and you need a different controller for each instance of the directive you want to use. It is usually considered a best practice to isolate your directive scope and specifically define the variables you wish to pass it from your controller.
For this, you will need to add a scope statement to your directive :
scope {
label :'#',
context : '=',
function : '&'
}
and update your view :
<my-directive label="labelFromController" context="ctxtFromController" function="myFunction()" ></my-directive>
The symbols denote the kind of thing you wish to pass through : # is for one-way binding (as a string in your directive), = is for two-way binding of an object (which enables the directive to update something in your controller), and & is for passing a function.
There are a lot of additional options and subtleties that are best explained by the Angular doc https://docs.angularjs.org/guide/directive. There are also some nice tutorials out there (e.g. http://www.sitepoint.com/practical-guide-angularjs-directives/)
Your submit() function is not attached to anything, so you won't be able to call if from your viewer. You need to define it as scope.submit = function() ... in your link function if you wish to access it.
You can use $watch for this kind of thing, but there are usually other more elegant ways to achieve this by leveraging the fact that angular already "watches" the variables it is aware of and monitors any changes he can (this can be an issue when some external service changes data for exemple, because angular cannot listen to events it is not made aware of). Here, you can probably simply associate the ng-model directive to your input checkbox to store its true/fale (checked/unchecked) value, and the ng-change or ng-click directives to act on it. The optimal solution will mostly depend on the exact nature of your business logic.
Some additional thoughts :
The HTML insides of your directive should be packaged in an inline template field, or in a separate HTML file referenced by the templateUrl field in your directive.
In your HTML code above, your directive is not referenced anywhere. It should be an element, attribute or class (and your directive definition should reflect the way it can be called, with the restrict field). Maybe you have omitted the line containing the directive HTML, but as it stands, your directive doesn't do anything.
To my knowledge, you don't need to return link. Think of it as the "body" of your directive, where you define the variables and functions you will call in the HTML.
Your directive doesn't actually need HTML code and the above thoughts might be irrelevant if you are going in a different direction, but encapsulating some kind of view behaviour that you want to reuse is probably the most common use of directives.
I'm wondering if there is a template parsing hook in Angular, that you can use globally or in specific controllers.
What I want to do is to implement a language and device specific (multidimensional) theme loader, that will dynamically grab any ressource link (img-tags, inline-styles) and redirect to the specific resource.
For example:
Someone implemented a template that shows some image:
<img src="images/my-super-image.jpg">
Now I want to grab the template and change the ressource to it's language specific correspondant:
<img src="theme/en_us/lowres/my-super-image.jpg">
The following things are important for me:
The one who generates the template doesn't need to take care of the themes, just uses the ressource as given in the first example
I don't want to use a directive, I want a global (App specific) solution --> best would be to have it in the run()-function of the app
I don't want to use look-up tables for the ressources, just want it to be highly dynamical
At the moment I'm not quite sure where to place such a parse-hook-function, nor how to get access to the current templates used on a page, to manipulate them before Angular provides them to the DOM.
I used some dirty hack, but I'm unhappy with it, because it will only be applied, when templates are already rendered and provided:
$(document).bind('DOMNodeInserted', function(event) {
if(angular.isDefined(event.originalEvent.originalTarget.innerHTML)) {
event.originalEvent.originalTarget.innerHTML = String(event.originalEvent.originalTarget.innerHTML).replace('src="images','src="' + imgPath);
}
});
Do you have any idea of how to do it? Thank you, guys!
Btw. I'm pretty new to Angular, so if you'd please be very descriptive, that would be kind. Thanks again.
You can use compile, Since angular only allow directives to modify DOM you need to create a directive
Here is an example
app.directive('myApp', function() {
return {
restrict: 'A',
replace: true,
link: function(scope, element, attrs) {
},
compile: function(tElement, tAttrs, transclude) {
tElement.find('img')[0]['src'] = "theme/en_us/lowres/" + tElement.find('img')[0] ['src'].split('/')[tElement.find('img')[0]['src'].split('/').length - 1];
}
};
});
Plunker
I'm in a situation where I would like to have many directives with different
tag names, but which are nearly identical in their behaviour. My first idea
was to just capture the directive as a function like this:
function standardDirective(template,extract) {
return function() {
return {
restrict: 'E',
... }
}
}
and then use this function to 'stamp out' the required directives.
angular.module('MCQ', [])
.directive('mcq'
, standardDirective("MCQTemplate.html"
, function(scope){return scope.userSelection;}
)
);
After writing this, I'm pretty sure that there is some common, and possibly better
idiom, for doing this. For example, in my 'solution' I probably can't make angular load the standardDirective when it is needed.
(I know that I could make a single directive and use attributes to differentiate behaviour, but for now, let's assume that I really need different directives..)
You could use the require option on the directive.
This essentially ensures that your directive "inherits" another directive, like a base class.
Your 'base' directive can contain all of your common logic and then then other directive that is requiring it has it's own spin on whatever it needs to do.
you can read more about it in the Angular Docs - see the Creating Directives that Communicate section
I might get some concept terribly wrong, but I don't get this to work I expected:
http://plnkr.co/edit/Qe2IzMMMR5BJZJpwkx9e?p=preview
What I'm trying to do is to define a directive that gets attached to a top-level <nav> element, and then modifies the contained DOM elements in its link function (such as adding css classes to <li> etc.).
However, the link function seems to only get the original directive template (<nav><ul><ng-transclude/></ul></nav), and not the transcluded/expanded DOM elements.
If this is "by design", how should I do this?
It find it pretty useless to define a transcluding "root" directive, if it does not have access to the transcluded DOM tree....
Please read some of my answers about transclusion in angular:
What is the main use of transclusion in angularjs
Why I can't access the right scope?
As to your question:
First, it's not useless , even if not fit to your use case.
Yes, it's by design - but it's just the default behavior of ng-transclude.
If it was the opposite then everyone would be yelling about scope leaking.
You can do anything you want with $transclude, just be careful.
There are probably better solutions like creating isolated scope with bindings.
This is what you wanted (plunker):
angular.module('app').directive ('myNav', ['$timeout', function($timeout) {
return {
replace: false,
transclude: true,
template: '<nav><ul></ul></nav>',
link: function (scope, element, attrs,ctrl,$translcude){
$transclude(scope,function(clone){
element.find('ul').html(clone)
});
var items = element.find('li'); //element.find('ng-transclude') === 1 !
window.console.log (items.length);
}
};
(Correct answers see above from Ilan and others)
I finally got my (simple) use case working without transclude at all with the old dirty $timeout hack: http://plnkr.co/edit/FEEDYJLK9qRt0F4DNzRr?p=preview
link: function(scope, element) {
// add to end of event queue
$timeout(function() {
var items = element.children('ul:first').children('li');
window.console.log(items.length);
}, 0);
}
I know this is a bad thing to do, and not totally sure if this will work always, but at least seems to work for my simple case...
I think the issue is that you have a ng-repeat within the directive so the "element" is not able to access the child nodes until the ng-repeats have been resolved. A way around this is to have your directive on each of the list tags. I'd add transclude to the tag, and then you can remove the template from your directive all together.
You'd end up with something like:
<li ng-repeat="item in menuItems" my-nav ng-transclude>
Your directive would look like
angular.module('app').directive ('myNav', ['$timeout', function($timeout) {
return {
replace: false,
transclude: true,
compile: function (element, attrs, transclude){
// this will always return 0 unless you split this into two directives
// and emit or watch for the ng-repeats to complete in the parent
// directive
//var items = $(element).find('li'); //element.find('ng-transclude') === 1 !
//instead showing you how to access css for the given element
element.css( "color", "red" );
}
};
}]);
As I mentioned in the comments above, you could split the directive into two directives: one at the nav level and one on your ng-repeat that simply emits when the repeats are done and you can apply css accordingly as the find will then be able to find the child nodes as they are resolved. I think that approach is redundant however, as you'd be setting css for nodes to which you've already applied your change. I think as noted in one of the comments below, smaller directives work better and your project is less likely to become a transcluded mess of spaghetti like scopes. Happy coding :)