I am using ng-switch to create a filtering input field in the layout. I am switching on the route name and I use this field to filter the data in ng-repeat on each page.
div(ng-controller="NavbarCtrl", ng-switch on="route.current.name")
input.filter(ng-switch-when='offers',type="text", ng-model="$parent.$parent.search.title")
input.filter(ng-switch-when='merchants',type="text", ng-model="$parent.$parent.search.name")
The problem with this approach is that the value that I input on one page stays saved when navigating to the other route and filters data on the other page as well. I am using the inbuilt angular search filter, hence the model names.
Can I somehow reset the value of the filter on route change?
Another issue is that I find this $parent.$parent scope access impractical, but i couldn't find a more elegant way.
Any hints greatly appreciated!
As discussed in the comments, the confusion was around the search property, which is not a special filter in Angular, but just the name of a property that will be used with Angular's filter filter.
Also, $parent is not needed if a property on a parent scope object is used/referenced. I.e., if $scope.search = { ... } is defined in the parent scope, child scopes (but not directive isolate scopes) can access this object via normal JavaScript prototypal inheritance.
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.
I have two custom element directives:
<my-directive-parent></my-directive-parent> //only one
and
<my-directive-child></my-directive-child> //variable count
The my-directive-parent directive has its own controller and templateUrl properties defined in its directive definition object. The my-directive-child directive has its own link, scope, templateUrl and require properties defined in its directive definition object. The fourth parameter passed into the link function is the parent my-directive-parent's controller. This is working as expected.
Based on user input, instances of my-directive-child are appended to or removed from the parent my-directive-parent DOM via the my-directive-parent's controller. Since I'm using ngView for the top-level page, changing pages results in Angular automatically cleaning up the various directives and their controllers of the top-level page. Upon returning, the controller for the page re-runs, but the user appended views do not show up (since they were previously wiped out automatically by Angular). I'm using a service which holds the data representing the collection of my-directive-child instances that were previously added.
My issue lies in restoring the my-directive-child directives based on this cached service data. Currently, when the my-directive-parent directive's controller is run, I'm getting the service data array that represents the instances to be added, looping through it, and adding a new my-directive-child instance to the DOM. This is working, but upon restoring each instance I need to pre-populate it with data that was previously entered by the user (of which is recorded in the service). Currently, when adding to the DOM this way each instance is in a "blank" state instead of a desired pre-populated state.
So I have the data needed to recreate the child directive instances, but I'm unable to pre-populate them with the necessary data.
I've tried to place a custom attr on the my-directive-child in an effort to forward the data to the instance's scope, but I learned from Angular's API docs that:
The new scope rule does not apply for the root of the template since the root of the template always gets a new scope
Questions:
Is my approach wrong? What should I be doing?
Is there a way to pass attrs defined on the actual element directive into the directive's scope that represents the inner template HTML?
How do I ensure a custom directive within a custom directive properly pre-populates itself?
Thank you in advance for any ideas, answers, or thoughts that might lead to a solid solution.
I'm having the following problem. I have a search.js controller which uses a template results.html. That template uses a category directive to show all categories that any of the company products might have (as a sidebar). It also uses multiple product carousel directives to show the products of each matching company.
In my search.js controller I define a scope variable named filterCategoryId which starts at null. This variable is bound to the categories directive as well as all product carousel directives. Which makes me expect that a change to this variable in either any of the directives as well as in the search.js controller would propagate to all others using this variable. The variable is used to limit the display of products to only those of that category.
But on my product carousel directive, I have defined a scope method removeFilterCategory() which sets filterCategoryId back to null. It works within its own scope. I can see the effects. But it does not effect the filterCategoryId on the categories directive. I can tell because I use ng-class to set an active class to the list item mentioning the category id to which the products are filtered. But changing filterCategoryId on the categories directive does propagate to the product carousel directives. There is an ng-repeat before the product carousel directives...
In essence. The variable was first defined on the scope of my controller. If I change it on the categories directive, the change gets propagated (after a noticeable while) to the product carousel directives. But if I change it from any of the product carousel items, the categories directive does not reflect the changes. And I don't see why. The product carousel uses a $watch to check filterCategoryId and recalculate its rows..
All directives use an isolated scope with the same variable filterCategoryId and use '=' as scope value.
What could be the issue?
And secondary even if I change filterCategoryId on the categories directive, it takes 5-6sec to reflect changes on the product carousel directives.
I'm feeling I'm missing something here. Either I have an obvious code error I seem to overlook or I'm missing some angular understanding :). Any help in the right direction would be greatly appreciated.
I would suggest explicitly telling AngularJS to register the change using $scope.apply( function(){/*change var here*/}); , see the docs.
There is a nice article explaining the details of Angular's digest cycle: http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
This seems to be the main problem if you are experiencing delays though I also suggest ensuring that the variable filterCategoryId is always an object, so it is never passed as a copy, maybe change it to filter.CategoryId etc.
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
Related Post, but didn't help:
Scoping issue when setting ngModel from a directive
EDIT: Can I use ng-model with isolated scope? didn't work either.
I got the some problem but in a more complex way I guess. I want to write a pulldown that does not use any input for data saving. I'd rather have the ngModel to take care of it.
http://jsfiddle.net/QeM6g/6/
The jsFiddle example above shows a demo where the above described methods didn't work.
// this is what should work but doesn't
ngModel.$setViewValue(value);
scope.$apply(attr.ngModel,value);
For some reason the scope of the ngModelController is a sibling of my scope. so it doesn't pass the changes back to the parent. at least all other sibling scopes behave as you'd expect. i.e. ng-change works in combination.
Angularjs doesn't deal very well with direct bindings to primitive types.
If you change this line:
$scope.productId = 16;
to something like this:
$scope.selectedProduct = {
id: 16
}
and change those references on the rest of the code, you should be able to overcome the issue.
jsFiddle: http://jsfiddle.net/M2cL7/
Don't bind to primitives in a scope, bind to an object in the scope.
From https://github.com/angular/angular.js/wiki/Understanding-Scopes
... until you try 2-way data binding
(i.e., form elements, ng-model) to a primitive (e.g., number, string,
boolean) defined on the parent scope from inside the child scope. It
doesn't work the way most people expect it should work. What happens
is that the child scope gets its own property that hides/shadows the
parent property of the same name. This is not something AngularJS is
doing – this is how JavaScript prototypal inheritance works. New
AngularJS developers often do not realize that ng-repeat, ng-switch,
ng-view and ng-include all create new child scopes, so the problem
often shows up when these directives are involved.
This issue with primitives can be easily avoided by following the
"best practice" of always have a '.' in your ng-models
so
<input ng-model="tweetText">
becomes
<input ng-model="tweet.text">
A great summary is here:
https://www.youtube.com/watch?v=ZhfUv0spHCY&feature=youtu.be&t=30m