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.
Related
I'm fairly new to angular, after using a video tutorial and reading some documentation, I decided to rebuild an old app of mine as an example with angularjs.
So this app has a table showing some data. It has a form underneath which helps you modify the data from the list. You have a button on each line which allows you to edit the line, it then fill all the fields in the form and you can then save or cancel your changes.
I made a controller to handle the list, it works fine, it gets a json from http.
I used ng-click on my edit button to trigger a function in this controller, giving it the whole object it's supposed to edit.
I made a controller to handle the form in which the edit should take place and I don't really found a 'non-hacky' way to pass the data from the list controller to the form controller.
So, my question is : what is the best practice and/or the common way to get this data from my list controller to my form controler ?
It depends how you are using the form controller. If it's being used within a template using ng-controller attribute, then this controller has access to parent scope, so you can work with list controller's data. (although note some scope-inheritance quirks solved by "dot notation", explained nicely by egghead.io: https://egghead.io/lessons/angularjs-the-dot)
If you're launching your edit form in a another url (e.g. /items/2/edit) and handle it in a routing configuration, then you can use resolve property to pass any data to the controller: $routeProvider docs.
You can pass the entire object as parameter
<row ng-repeat="theModelInRow in modelList" ng-click="edit(theModelInRow)">
if form controller isn't a nested controller of list controller on view, then you can use rootScope.
Use case
For use in a form, I created a directive that tracks changes in an array. It allows changes to be reverted and deletions and additions to be stored separately. It allows for an array (one to many mapping in the database) to be updated incrementally (rather than requiring the server to either diff, or rewrite the entire list).
Problem?
My question is about the way I expose the functionality to the controller's scope. I currently use an two-way databound attribute on the directive's scope. This works, and it seems reliable (of course you can easily break it by reassigning the scope's value, but intentionally you can break anything).
Code
You can see this plunk to see this in action. It allows methods on the directive's controller to be called from the view and the view's controller. (I am using the directive controller intentionally because that's what I do in my actual code for the directive to directive communication, but this could also just be placed in the linking function.)
Question
Is this way of doing it bad design? Am I completely throwing AngularJS out of the window now and hacking in my own code. Are there any better ways to expose functions from a directive (keep in mind that there'll be multiple of these in a single form).
It's very easy to pass in my-attribute="someFunction()" to have the directive be a consumer of the view controller. I can't find a better way to do the opposite and have the view controller consume from the directive.
Alternative?
I've been thinking about using a service here, in which the service will provide an object that is instanciated in the view, passed to the directive, and have the directive blurp out it's results to that object. Then in turn have the view controller consume the information from that service's object. Would this be a better approach?
There's nothing wrong with your approach. In fact built-in angular directives such as ng-form use this approach to store the controller in the scope (see the name property of ng-form) http://docs.angularjs.org/api/ng.directive:ngForm
For more re-usability though I would put the api methods on the controller and then put the controller itself in the api:
this.getChanges = function () {};
this.resetChanges = function(){};
$scope.api = this;
In directives, the main purpose of the controller is to serve as an api for other directives (if you didn't need an api for other directives you could just do everything in the link function). Doing it this way ensures the api is available both on the scope as well as to any directive that 'requires' the oneToMany directive.
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.
I have a shared function which returns of the scope of the topmost element (document) in my AngularJS application.
function topScope() {
return angular.element(document).scope();
}
This always works and I am always guaranteed to have access to any subscopes located within the application (whether it be inside controllers or directives).
Here's an example of what I would use it for:
topScope().$emit('pageReady');
Now I've noticed that $rootScope also works the same way.
$rootScope.$emit('pageReady');
Which also works and achieves the same affect. But since $rootScope is designed to be the "$scope off the shelf" scope (any scope created will inherit it's methods and properties) then does this still mean that it is in fact the topmost scope of the page? Thus being the parent of the scope object attached to the document node?
$rootScope is a parent scope of all scopes in a given AngularJS application. Since it is possible to bootstrap multiple AngularJS applications on one page (only manually, this can't be done using ng-app) it is also possible to have multiple $rootScope instances in one HTML documents.
Each $rootScope is "attached" to either the element where ngApp was declared or the element passed into angular.bootstrap as described here.
In short, the $rootScope is a root of all scopes for one AngularJS application but there is no "super-root" scope that would serve as a parent scope of all other scopes for a given HTML document.
In your case using the $rootScope might be OK if you've got only one AngularJS application in the whole HTML document.
i'm using angular js in my website and i'm getting a strange behavior when i update my model.
My model is an json complex object like this:
$scope.DataSource = {prop1:"", prop2:[{a:1, b:b2}, {a:3, b:4}], prop3:"value"}
use a controller to edit object an use custom directives to edit separatelly child objects like in prop2 ( in this case are tabs ).
My problem is that when i update $scope.DataSource the previous generated DOM elements are not removed or updated, i just get a duplicated UI for each object in Prop2.
is there any way to force angular to update or remove previous generated elements? preventing a duplicated tabs(in this case)?
Duplicated DOM elements are likely the result of duplicated data in the scope associated with the view. Without any code, it is not clear if the scope generating the view is tied to a controller or a directive. Try logging the scope that the view is generated from to the console.