I have a control that should display breadcrumb navigation. It needs data (route & title) to display the navigation correctly. Data is taken from scope and used inside a directive.
What causes my problems is that I use a localization directive in the control that should translate the title. And this localization directive is called even when expression in ng-show is evaluated to false. Then the translation in localization directive ends with exception because it tries to translate incorrect string (see 'localize' directive in http://jsfiddle.net/F97wn/7/).
That seems quite weird. I would expect that if something sets whether the inner content should be visible or hidden, then it is evaluated first and then the inner content..
Ok, then I found that ng-show only sets some css attribute, so it's quite useless for me.
The question is: How should I solve the problem - what to use instead of the ng-show?
An example is at http://jsfiddle.net/F97wn/7/
You could use ngSwitch instead with the on part set to "toshow()" and the inner ng-switch-when="true" part to have your custom directive inside that area. This will then not execute the custom directive if the value of toshow is not true.
If the directive is throwing an exception, more information should probably be passed to the directive, in one of the following ways, so that the directive can decide if it has the required information to do what it needs to do:
attribute data -- e.g., localize="..." show-me="..."
something defined on the scope associated with ctrl -- e.g., $scope.showMe. The directive scope will have access to this property as scope.showMe, based on the way you currently have the directive defined.
or inject a service (that has the data) into the directive -- e.g., directive('localize', function(myShowMeService) { ... }
You might also want to look into <ng-if>. The ngIf directive removes and recreates a portion of the DOM tree (HTML) conditionally based on "falsy" and "truthy" values evaluated within an expression. It might be more intuitive than <ng-show> for your needs.
However, it is currently only available in the unstable version of AngularJS. If you can use that version of AngularJS you can find more information about it here.
Related
I'm making a dinamic list of dates. The user can adds all datepickers he wants, but I have to validate that there are not matching dates, all of them have to be different, that's the only requisite.
I've made a custom directive validation and it's triggered correctly, but when I try to use its isolate scope, I just get that error (Multiple directives). Other questions/solutions that I've seen here, propose to delete the isolate scope, but I need it to pass to the directive the array of dates and to be able to compare them with the current selected.
Here is a codepen that reproduces the problem. If you remove the noMatchingDates directive's scope, the error just disappears and you can see and add datepickers properly. I mean this scope:
scope: {
getAllDates: "&allDates"
}
I think that it has to do with this line in docs:
Multiple directives requesting isolated scope.
And it probably also has to do with the md-datepicker which would have more directives using the isolate scope. So, how can I solve this error (and still being able to send the dates list)?
If it can't be solved (keeping the scope) given the nature of the md-datepicker, how can I reach this dynamyc validation? I think it could be done using a controller and the ng-change, but I'm not sure if it would be a proper solution.
Indeed there is no reason for your directive to require an isolated scope. Use isolated scope when your directive is like a reusable "visual component". Your directive is about logic validation and shouldn't prevent another such component.
To fix your problem, you can remove the isolated scope and use your directive in the HTML this way:
<div ... no-matching-dates="overtimeList">
Then in your link function, you can retrieve the value of that attribute this way:
var dates = scope.$parse(attr.noMatchingDates);
This will give you the content of what is bound to no-matching-dates, so in this case it will return overtimeList.
I have never used the ctrl.$parsers.unshift syntax, but it seems that you can also use it to retrieve that value. Simply remove the scope.$parse line that I just gave you and write:
ctrl.$parsers.unshift(function(arrayOfDates) { ... })
This should work as well. Note that in the first approach you need to $watch for changes if you want to run the validation every time.
A simple example to explain my case:
I have a directive for labels
<input label="{{obj.label}}"/>
But for some other directives I want to use an attribute with name "label"
<other-directive label="My label"></other-directive>
just as an attribute, not processing the label-directive.
I could just rename the attribute to e.g. "my-label":
<other-directive my-label="My label"></other-directive>
but it would be nice to use "label" as an attribute name.
As #ExpertSystem points out in the comments to the question, angular really has no way of knowing out of the box whether your directive should be applied in one case versus another. The only way I can think of to get around this is to include logic in your directive's compile function that knows how to determine whether it should be applied or not. This plunker demonstrates how I would accomplish this. You basically need to return two different link functions from the compilation phase; one if your directive should be applied (in this case adding a label before the input), and a different one if it should be skipped. You are then free to use that as an argument to a separate directive. This may not work if your directive needs to do things like transclusion or isolated scopes (things that angular doesn't like two directives on the same element to do).
I'd be very sparing with how you use this, however, as it will create an inconsistent API for other developers who may be working with this code. They may not know when the directive will apply and when it won't.
Given the following code:
http://jsbin.com/uYETOSUM/2/edit?html,js,output
I want to only show the first dropdown first (parent question in JSON). After user have selected an option I want to show one of the two other dropdowns (children in additionalQuestion object in JSON). Which dropdown to show is given in the JSON used to create the form input fields.
To do this I've experimented with ng-show, but I don't seem to find the proper place to put the custom ng-show code. I want ng-show to be like this in the template:
ng-show"showQuestion()"
where showQuestion() iterates through all questions and matches the PreviousQuestionConstraint to see which of the other questions to show based on the value of the selected option in the first question.
Should it be in the Controller?
Should it be in the Directive?
Should it be in the linker function in the Directive?
All attempts so far have resulted in nothing being shown.
After much hassle I've managed to get a working example for you using the &attr isolated scope syntax which provides a way to execute an expression in the context of the parent scope, in your example all the way up to the showQuestion() function on your QuestionsCtrl controller.
http://jsbin.com/EkIqAju/2/edit
I have an Angular app with a custom element directive (<uber-table>). <uber-table> is supposed to take a collection of objects, render them into a <table> and add some functionality (click row to toggle the underlying object as selected, search box for live filtering, action links on each row with customized click callback(s) for each object). I created a Plunker with the relevant code. Please note that Plunker is giving an error about map (Object [object Object] has no method 'map'), but locally I am not getting any errors.
The post-link function's element parameter is not the <uber-table> element as I expected. Instead it is the template's <div class="uber-table"> element. This is preventing me from extracting data from <uber-table>. What am I doing wrong? Any help will be much appreciated.
Here's a rundown on some of the issues.
First main issue is you want existing content already within the uber-table markup to exist, as well as a new template. Unless told otherwise the existing content ( columns) in this case will be overwritten. In order to include existing content in a directive that has a template, you need to set transclude:true then identify within template where this existing content needs to be placed using ng-transclude on element that will be parent of the content.
Here's demo with transclude fixed
New problems arise now where you are trying to use jQuery to loop over columns and return angular attrs => column.attrs . This throws undefined error.
I haven't tried to unravel this enough to sort out the columns issues yet. They should likely be handled by directive of their own
UPDATE: here's slightly revised error free version using jQuery to parse column count. I'm afraid am still confused a bit by structure of this. I don't see need to use jQuery to parse colunms, this could be converted to directive or pass column definitions into main directive from controller scope
After trying several things and looking at the documentation again, I finally got it working. The solution was to put the post-link function inside the compile function. Also I had to update my isolated scope to use =, set replace to true and set transclude to 'element'.
I updated Plunker if anybody wants to see the changes. The Plunker version isn't working, but since it is working locally, I'm not going to spend too much time on it.
I'm very(!) new to Angular.js and am trying to understand where to put the various parts of my logic in order to follow best practices and separate business and presentation logic.
My specific use case is that I have a list of courses with a number of signups and a number of available seats. Based on these values I want to present a progress bar (or, if the available seats is not set, just a text).
My question is where to put the various parts of the logic, and how to pass the values along properly. So far I've created the HTML-part of a directive, like so:
<signupprogress available="{{course.available_seats}}" filled="{{course.filled_seats}}"></signupprogress>
My question is then (first and foremost) if a directive is the proper way to do this and, of so, if the logic for constructing the progress bar should go in the compile function, link function, in a template, or some other place. To me the compile of link function seems to be most correct, but I don't want to fill them with HTML, nor am I able to properly get the attribute values from the HTML (the attrs.$observe examples I've seen only implement the getting of one attribute).
Yes, use a directive since you need to modify the DOM.
If all of the HTML for the progress bar and the alternative text can be placed in the directive's template, then do that. And, if that is possible, use '#' for one-way binding, which makes it clear that the directive does not need to modify the "available" and "filled" values. If you find you need a linking function, then as #ShaiRez mentioned, '=' is probably easier. (BTW, here is a way to $watch multiple attributes, instead of using $observe. Maybe the same technique can be used for $observe.)
To display either a progress bar or the alternative text in the template, use ng-hide or ng-show in the template. Here's a simple example of that (which also uses '#').
The directive is the way to go in my opinion.
I would have my HTML content inside of a template, the logic inside of the link function (the compile function is usually more for repeaters etc).
And use the "scope" property in the directive definition, set to "=", that way changes are reflected automatically.