Angular directive template is not compiled by outer directive - angularjs

I have simple tags:
<form show-errors>
<date-input />
</form>
as You can imagine I have two directives: showErrors and dateInput.
showErrors should work on DOM that is loaded by dateInput template but that never happens :(
showErrors runs (DOM is empty) and after that dateInput loads its template but it's way too late. I have impresion that im using directives in wrong way :(
Please take a look at my plunker for details:
http://plnkr.co/edit/3QEd1HvJETo30tZd1zl3
Update:
I updated my plunker and it seams that it's all about lazy loaded template from URL. Any ideas how that should be solved in angular clean way ?

First of all you have a typo in the directive's name: instead of dateinput there should be dateInput.
However, it is impossible to refer to the children from the parent in directives. Only communication in the other direction is allowed (usually using controllers).

You're making two mistakes.
First, your inner directive should be named dateInput:
app.directive('dateInput', function() {
Second, you need to explicitly close the date-input tag.
<date-input></date-input>
It's important to note, that the AngularJS compiler does cannot parse
<date-input />

Related

Function never called in an AngularJS directive controller

I am facing a strange situation in which I have created a directive, to which a controller is attached, and one of the two tiny functions of the controller is never called from the view whereas the other function is.
Here is the plunker.
The message I expect is (bold is what does not show up)
You are limited to: Prison
I have already created tens of directives, whether in their own right or as wrappers around existing directives available on GitHub, from lightweight ones such as custom-select to behemoths such as angular-ui-grid.
I am at the end of my wits here as to why {{getArea()}} produces no text at all in the view. I've scrutinized the code, trying to do it with new eyes, so to speak, and I see nothing wrong. I've created a specific project in Eclipse for this tiny piece of code, installed Wampserver just so I could set breakpoints in Firebug and God knows to what great lengths I had to go just so that I could understand what is wrong with the code I wrote.
For instance, in isRestricted(), I can call getArea() without any problem. However, Angular seems to not find the function from the directive.
A few similar questions have already been asked but none of the errors (missing controller or ng-app specification, missing dependency list at module declaration, nested controllers, etc.) seem to apply. There's obviously an important lesson to be learned here and I'm truly eager to learn it.
EDIT: The lesson learned is that ng-if creates a new scope. That new scope comes in between the controller and the directive, which leads to the template of the directive losing access to anything defined in the controller (at least, that's how I would phrase it). (Note that a comment hinted at directive priority.)
There are several solutions, which all maintain the prototypical inheritance needed for the template to access the functions defined in the controller:
not using an isolate scope
not defining the ng-if directive on the top-level element of my directive, as that causes a conflict (between my controller's scope and the scope defined by ng-if). I believe ng-if wins here, which leads to the controller's scope being out of reach of the directive. Using ng-if on a child div does the trick (because then, the ng-if scope inherits my controller's scope, hence making the functions available to the template).
Because of the CSS styling needed with this directive, I have used scope: false.
<span class="scoop-badge-content">{{$parent.getArea()}}</span>
Or in directive :
scope:true
This is because ng-if use how own scope
The strange thing is that when i have this problem i usually use dot notation. But it doesn't work here, probably because we're inside a directive, and i didn't had the case until now.
EDIT : a last way of doing this chaging the template :
<div class="scoop-badge scoop-badge-ua">
<div ng-if="isRestricted()">
<span class="scoop-badge-title">You are limited to:</span>
<span class="scoop-badge-content">{{getArea()}}</span>
</div>
</div>
I think this work because you have replace true and ng-if will conflict with ng-scope if it's on the top DOM element.
When you have scope = {} in your directive, Angular creates an isolated scope. Which means it can't get to the getArea() function.
You can completely remove the scope = {} line or set it to scope = true or scope = false depending on what you're trying to achieve later on.
When set to scope = true Angular will create a new scope object and assign it to the directive. This scope object is prototypically inherited from its parent scope.
When set to scope = false the directive will use its parent scope. (This is the default value. It has the same effect if you remove this line).
More information about scopes here
Removing scope: {} from directive definition solves problem.
app.directive('scoopBadgeUa', function() {
return {
restrict : "A",
scope: {}, // This is not needed, creates conflict
templateUrl : "scoop-badge-ua.html",
replace : true,
controller : 'ScoopBadgeUaController',
};
});
Your code is correct, you don't have to do anything more than adding a <div></div> wrapping your code in scoop-badge-ua.html.
<div>
<div class="scoop-badge scoop-badge-ua" ng-class="{'visible': isRestricted()}" ng-if="isRestricted()">
<span class="scoop-badge-title">You are limited to:</span>
<span class="scoop-badge-content">{{getArea()}}</span>
</div>
</div>
I like this solution better than transclude: true because it doesn't include the directive's tag in your html code, which ultimately translates to a cleaner code.

Making a directive load before ngController

I am trying to get experience with angular by making an app (for my own use) and I ran into an issue.
I decided to try to implement lazy loading (like in the "Complex" part of the answer to this question ) and had that working with my own additions to it.
I wanted to make components more self-contained though and decided to try to make a directive to load any javascript dependencies for me.
This left me with the following template html
<div dependencies="app/components/home/controller.js" ng-controller="HomeCtrl as home">
This is the home page.<br />
{{home.test}}
</div>
When I try to load this page I see the following error
Argument 'HomeCtrl' is not a function, got undefined
If I remove the ng-controller directive then my directive works fine and controller.js is loaded.
Based on the source for ngController I thought that setting the priority for my directive higher than 500 would work but it didnt seem to help.
I also tried moving my directives contents from link to compile.pre. It didnt help either, my directives compile runs before the error but compile.pre does not.
Is there any way to make my directive load/run first, before ngController?

Angular - using the form in the controller and in controller testing

The docs specify that you can expost the form to the scope with name. so lets say I have this form:
<form name="myForm" ng-submit="submit()">
<input type="text" ng-model="somemodel"/>
<button ng-submit="submit()"></button>
</form>
but I'm trying to access the form through the controller with $scope.myForm and fail:
$scope.submit = function(){
console.log($scope.myForm) // or form[0] or scope.myForm[0] etc..
if(!$scope.myForm.$dirty){
//do this and that
}
//do something
}
they all fails as undefined. how is this done? ultimately I would like to also call $setPristine() from the controller. but I just can't find out where the form is hiding. is he even available to the controller or just in the view scope?
using angular 1.2.5
EDIT: this doesn't work also when the form name is with the dot notation: myForms.myform
Another edit: after forking the suggested plunker I found out that the form doesn't exists on the $scope before submitting, but does exist after.
So I guess the refined question should be: is this the expected behavior? Is there a workaround this (in the ctrl and not in a directive)?
If this is expected and no workaround so I'll move the custom validation checks to specific directives - since in the directives (if they require:"^form") the form is available before submit.
Thanks!
Answer this issue seems to be when the form is added to the scope. from the plunkers it's obvious that not in the beginning, but attached later, after the Parent Ctrl got loaded. can we control the loading order? I don't know. seems the place for this kind of form scope manipulation should be a directive and not the ctrl
One reason you might not be able to see the form on your Controller $scope is if your <form> is inside a directive that introduces a new child scope. e.g. if the form is inside an ng-if or ng-switch.
In other words, in the example below, myForm ends up getting published to the child scope introduced by the ng-if and you won't be able to see it on the MyController $scope.
<div ng-controller="MyController">
<div ng-if="true">
<form name="myForm"></form>
</div>
</div>
One way to deal with this is to use dot-notation to publish the form into an object wrapper instead of putting it directly on the scope.
<div ng-controller="MyController">
<div ng-if="true">
<form name="myStuff.myForm"></form>
</div>
</div>
And in MyController, make sure an object is there so it will publish to it instead of the child scope.:
$scope.myStuff = {};
Take a look at this egghead.io video that does a good job explaining this: https://www.youtube.com/watch?v=DTx23w4z6Kc
If you change the reference from $scope.form to $scope.myForm, then that is defined, as in this plunker. Unless you have a parent form called myForms that you haven't mentioned, you won't be able to access it as $scope.myForms.myform.
Edit: the refined question asks whether it's expected behaviour that on the form's parent controller that the form is undefined when the controller is first run. The answer is yes, it's not added until later.
The question suggests that the form is only defined when the form is submitted. This isn't quite accurate. The form is added on the scope before this: there is nothing special about the submit function. You should be able to quite happily call form functions in response to other button presses. In the parent controller you can have
$scope.makeTheFormPristine = function() {
$scope.myForm.$setPristine();
}
As can be seen in the following Plunkr:
http://plnkr.co/edit/dDVrez9UP1GYRIDrUxJs?p=preview
Edit: from the comments, if you want a variable in the parent scope, say notification, to depend on a property of the form, you can setup a manual watcher:
$scope.$watch('myForm.$pristine', function(newValue, oldValue) {
if (newValue) {
$scope.notification = 'Pristine';
} else {
$scope.notification = 'Not pristine'
}
});
You can see this at http://plnkr.co/edit/2NMNVAfw8adlpFRGuSoC?p=preview . However, I would be a bit careful about putting too much "view" logic into the controller. It might be better placed in the template, or maybe event a custom directive. This is probably beyond the scope of this question, and maybe better on https://codereview.stackexchange.com/.
Also: I don't think you need the 2 ng-submit attributes, both on the button and the form, but I suspect that isn't related to this issue.
Oops. Just re-read your Question and realized that you are using the wrong scope path. As already indicated by Matt, the form will be pusblished to $scope.myForm and not $scope.form.myForm.

access controller scope from Bootstrap-UI Typeahead template

I am unable to call a controller function from inside a custom-template with ui-typeahead:
<input typeahead="val for val in autoComplete($viewValue)"
typeahead-template-url="searchAutocompleteTpl.html"
ng-model="query"/>
<script type="text/ng-template" id="searchAutocompleteTpl.html">
<span ng-repeat="eqp in match.model.equipment"/>
<a href="" ng-click="showItem(eqp.model)">
found in: {{eqp.model}}
</a>
</script>
The problem is that the controller's scope seems to be absent in the template:
showItem(eqp.model)
is never called. I have also tried with:
$parent.showItem(eqp.model)
to no avail.
How can I call a function/value on the controller's scope then?
I ran into the same problem and had a look at the typeahead code on github to see if that might offer any clues. It appears that there are several directives involved in the creation of the suggestions list and each gets its own child scope.
In other words, your $parent.showItem(eqp.model) was a good attempt, but you didn't go up enough levels. What worked for me was: $parent.$parent.$parent.showItem(eqp.model)
I also have same problem. I'm not sure but its working.
You can use double $parent instead of single.
e.g. $parent.$parent.showItem(eqp.model)
The solution of your problem is to initiate an object in your template controller scope like this:
$scope.typeaheadObject = {
query : '',
}
now in your form you will need to change your ng-model with:
ng-model="typeaheadObject.query'
This will create the object typeaheadObject in all your controller if you don't re-initiate it in one of your controller. So when you will want to access to the content of the object in one of this controller you will just have to do for example:
console.log($scope.typeaheadObject.query)
This is a classical issue in angularJs. You only can access and modify a parent scope if the variable is stock in an object
Finally you have to use a '.' in your ng-model. This will permit your ui-bootstrap module and your own module to share their scope with the object.
I just did an example on plunker to be sure you understand well the issue.
http://plnkr.co/edit/4YWNMagm571Gk2DrCERX?p=preview
Have a good day :)
It worked for me after adding 4 parents.
$parent.$parent.$parent.$parent.

Angular UI select2 with tags not working inside custom directive

Using the Angular UI Select2 directive, with tags defined on an input field. If the input is itself inside a custom directive, then it is not initialised correctly and the console gives an error:
query function not defined for Select2 tagging
I suspect this might be to do with the order in which the directives are compiled / linked vs when the select 2 function is called.
Maybe there is a simple workaround, perhaps using the compile function or a directive controller instead of a link function? Or maybe it is an issue with the Angular UI select2 directive.
I have made a plunker that displays the problem:
http://plnkr.co/edit/myE5wZ
So my question is - How do you get get select2 tags working from inside a custom Angular directive?
In the end I managed to find a solution I was happy with involving nesting two directives, that way the logic can be encapsulated inside the parent directive (not spilling out into the controller).
A Plunker of my solution is here for anyone who may stumble across the same issue:
http://plnkr.co/edit/ZxAPF5BzkgPtn9xddCRM
I just encountered this today and summarily realized the fix:
PostLinking functions are executed in reverse order (deepest grandchild to greatest grandparent).
Put your custom modal's code (or anything that sets $scope data for use in its children) inside a PreLinking function. PreLinking functions go from parent to child, and all PreLinking functions are performed before the PostLinking functions.
I had a similar issue. Your solution works but IMHO I think an even better solution is to use the controller function instead of the link function inside the directive. Doing this you do need nested directives.
By using the controller function instead of the link function in the directive it's working. Example:
function myFunction() {
var dir = {};
dir.scope = { myModel: '=' };
dir.restrict = 'E';
dir.templateUrl = 'myTemplate.html';
dir.replace = true;
dir.controller = function ($scope) {
$scope.myVar = ...;
};
return dir;
};
I have this error too. My short solution:
<input type="hidden"
name="citizenship"
class="form-control input-sm col-sm-10"
style="width:500px"
multiple
ui-select2="params.options.citizenshipOptions"
ng-model="cvLang.content.citizenship"
ng-repeat="a in [1]"
/>
ng-repeat="a in [1]" is a magical thing!!! It is not clear for me a logic of a context, but this is working. May be this can help?

Resources