I have a recursive directive the compiles an HTML element and appends to the DOM structure. The input data I get is the JSON tree. I want to augment the tree to do some pre-processing and set a watch on the tree.
How can I do this considering I want the directive to be reusable?
Can I attach my own controller to the directive scope and do it there? Or maybe post vs pre link function?
Edit : Consider this way of printing a tree. This approach indents the data using css to give the effect of a 'recursive' template but does not use recusrion. If you look at the js file, it actually loops through the JSON data, add parameters to it and applies a $watch on it to notify for the click event.
On the other hand, consider the angular way of using a $compile for runtime DOM manipulation. This approach is very good. I want to modify this method and add parameters to the JSON data before the directive is actually applied. While this should be trivial to do in the application controller, I want to create a reusable directive that can work out of the box like the first approach.
Hope this brings clarity.
Related
I'm pretty new to angular, but I have managed to get most of the concepts down. However, I can't seem to figure out how I should go about solving my issue.
I have one component, that has other components as its children. I want to be able to loop through the child components, and add data to them separately, either before they are rendered, or after they are rendered.
The master component is an HTML version of a government form, and I have scattered throughout it a simple component that is supposed to display text from a database relevant to the field it is in.
If I was using something like jQuery, I would just attach an id to each component, and loop through them, adding data if the component id matches the key of the JSON object.
Is there any standard way to do something like this with angular?
You'd be better off "binding" any information you'd like to provide to your child components instead of implementing a vanilla/jQuery solution that involves querying the DOM and then manually manipulating the DOM.
Take a look at property-binding and two-way binding.
Here's a quick example:
<my-child-component [someProperty]="myValue"></my-child-component>
<my-menu-component [menu]="menuData"></my-menu-component>
You probably should rethink the way you are trying to do it, when you start with Angular you need to leave a lot of concept that you learnt in jQuery behind.
We don't manipulate the DOM directly in Angular, we manipulate the data that is bound to the template and that binding is what updates the DOM.
Tom added an answer as I was typing, he is saying what I was going to.
The angular way to achieve what you want is to use one-way data binding. Check out the example in the documentation.
In the example they have a child component in the template html of the app.
<bank-account bankName="RBC" account-id="4747"></bank-account>
You can one-way bind data by referencing a public property on the parent and using the square bracket syntax to indicate one-way binding like so:
<bank-account [bankName]="somePropertyOnTheParent" [account-id]="someOtherPropertyOnTheParent"></bank-account>
I'm trying to make an angular directive that combines ng-mouseover, ng-mouseout, and ng-mousemove. Ideally, I'd like to have a custom attribute hover="thing" which, through a custom directive, would be shorthand putting all three of these mouse event attributes in an element, each calling some function with thing as a parameter.
Is there an easy way to do this? I can use a template, but I want this directive to simply be able to be added to any existing element, from images to divs. I don't want to have to make separate directives for every type of element I might want to use it on.
What is the best way to accomplish this?
I'm a beginner in AngularJS, I understand most of the mechanics but I'm still grasping the "culture".
I'd like have clean separation between my HTML, DOM, data and communications.
My impression of a controller is a module that implements a "data" model, but is void of UI semantics (i.e. DOM manipulation).
However in my HTML, if I use an ng-click it is the controller's scope that is accessed for the click function implementation, which more then likely will need to call back into the DOM.
So where should I implement my click functions if I do not want DOM manipulation in my controller? Are DIRECTIVES the universal answer to this?
Suppose I have 2 controls on a page that need to interact with each other, should I create a directive on the parent of those controls parent that implements the click functions of both child controls? Or perhaps create a directive for each control and possibly pass the ID of the other control as an attribute? OR maybe a directive for the parent AND children?
--------- UPDATE 1 -----------
The following HTML is a simplified and contrived example that [hopefully] illustrates my question.
<div id="searchComponent">
<input id="txtSearchText" ng-keyup="..."/>
<input name="Go" id="btnDoSearch" ng-click="..."></input>
<div id="autoCompleteResults"></div>
<div id="fullResults"></div>
</div>
As the user types in the txtSearchText, the autoCompleteResults is populated, factoring in the usual minimum characters and timouts.
When the user presses or clicks the btnDoSearch, the autoCompleteResults is cleared/hidden and the fullResults is populated.
Finally, if the user begins typing new txtSearchText, the fullResults would be cleared/hidden and the autoCompleteResults is again seen with results.
Any guidance would be appreciated!
So where should I implement my click functions if I do not want DOM manipulation in my controller? Are DIRECTIVES the universal answer to this?
DOM manipulations, in my opinion, means something like document.querySelector(), addCliss, etc. ng-click is the event, which is supposed to deal with some business logic. Put it in the controller is fine.
Of course, directive is your another choice. directive is usually used to extract some reuseable components, such as modal, across different pages. If you repeat some code in different controllers, consider extracting them to directives or service.
Suppose I have 2 controls on a page that need to interact with each other,...
In short: use service, which is designed for that scenarios.
The general philosophy is to reference the DOM explicitly as little as possible. Most (if not all) things you want to do can be done by binding an aspect of you HTML element to a property on $scope, and manipulating that property. So you never have to do some like "Change the class of <span id="foo"> to red now that isRed is true". Instead you would have <span ng-class="{ red: isRed }>". So if you have two click handlers that share information, it's perfectly valid to have them change some common variable in your controller, and have state of the UI accordingly with DOM bindings. Directives are used more for reusing DOM elements, or when you do have to explicitly refer to DOM elements, i.e. adjusting the scroll properties of a div. You could use a directive to add the same click handler to many elements for example. Services can be used to share information, although if both of the controls belong to the same scope there's less of a reason to do that.
I have been evaluating a lot of different client-side JS template engines (doT, parrot, dust.js, microtemplating, underscore, etc). They all work similarly, using some type of tags to represent data, and with some giving the ability to embed pure JS into the template, including loops, if/then, etc. However, they all work by converting the template itself into a string, then into javascript, in order to interpolate the variables, execute loops, etc.
When this conversion happens, any event handlers that were attached to the original objects within the template (i.e. created by jQuery at document.ready) are of course lost. To add these handlers back to the resulting HTML would then necessitate going back and re-applying any such event handlers after each template rendering.
I'm trying to think of a way to create a template engine with full javascript support, but which preserves any events attached to the template before cloning.
Imagine a scenario where the template is for a list of items. Each item includes buttons which perform specific tasks on that item (i.e. edit, delete, rename, copy, you get the idea).
To make the code clean and easily maintainable, it would make sense to apply Click events to these buttons in the template HTML at document.ready(). Then each time the template is cloned for a new list item, the events are cloned too.
However, with current templating libraries, all events are lost at the cloning stage, which necessitates applying all events to the cloned object each time the list is updated. If this is live data, or if the user is adding new items to the list, this seems like it would become very convoluted to keep track of the events and ensure they are properly attached each time an item is added.
I know jQuery has a clone() function which clones events, and this works great for basic templates, but when you get try to incorporate arbitrary JavaScript into the template, this becomes impractical.
I am trying to avoid templates which depend on html element attributes to configure loops, decisions, etc, because the template code becomes very ugly. A clean template with simple tags for data substitutions, simple JS for-loops for repeated elements, and simple references to the source data, is desired.
Anyone have ideas on how to do this?
Instead of over-complicating templating, you should use event delegation, so that this problem does not even present itself.
And yes, jQuery had .delegate – it is deprecated, and has been replaced by .on, which can do the same thing, see http://api.jquery.com/delegate/#entry-longdesc
So I have this code example that uses Angular 1.2 RC2 and everything works fine, you click on the handle to toggle the display of the content and the controller and directive have seperate scopes as intended:
http://plnkr.co/edit/e3XAZuhSMAxmkWzKKM39?p=preview
Now I upgraded to Angular RC3 yesterday and now the functionality does not work as it stands in the plunker, I get the error the specific requires generic which is can't find. Going through the change log, I though this might have to do with this breaking change:
$compile: due to 31f190d4, the order of postLink fn is now mirror opposite of the order in which corresponding preLinking and compile functions execute
To fix this they either suggest converting the post linking to a pre linking (which I can do since my post linking needs access to the scope which is not available in the pre linking) or to decrease the priority of the directive. So this plunker does that and functionality does work:
http://plnkr.co/edit/arP3aruH8HEdiwFg6mWp?p=preview
However there is a major issue and that is now because specific has a higher priority, the isolate scope that generic needs is no longer being created so now contentVisible is on the controller scope which is not good.
Now I could just move the scope: {} from the generic directive to the specific directive however it should be possible to use the generic directive by itself and if I did it would attached to whatever scope it is part of and not its own (which would make it impossible to have multiple instance of this directive, which is way it needs its own scope).
The only thing I can think of is to add a directive, called something like isoScope, make sure it has a very high priority, and have it define scope: {}. Then if I need to use generic by itself, I just have to make sure to also add the isoScope directive to make sure it has an isolate scope. Like this:
http://plnkr.co/edit/1NYHpUcPFWEbAzvkCeRH?p=preview
Now I am hoping there is a better way to accomplish what I am looking for without the isolateScope directive. Am I missing a way of that this without that?
UPDATED EXAMPLE
So here is another plunker that includes hopefully better examples of what I am trying to convey (still has virtually no styles but should not be needed to get the point across):
http://plnkr.co/edit/KtRMa1c9giDrhi1Rqyho?p=preview
I have 3 directives:
expander
notification
isolateScope
The expander directive only adds functionality to be able expander and collapse content, nothing more. This functionality is something that should be able to be used alone or with another directive (which is why it has a controller).
The notification directive is used to display notification however since we don't want to display the notifications all the time, we use it with the expander directive so that the user can toggle the display of the actually notifications (similar to how stackoverflow.com does it in the top left).
While I imagine the expander would most likely be used with another directive it should be possible to use alone and that is where the isolateScope directive comes into play. Since the expander directive adds data to the scope and you may want to have multiple expanders on the same page, it needs to have an isolate scope in order to work. Now on a users profile page you have have data like developer key and address that you don't really need to display all the time so lets have the user control that. I have the isolate scope to be able to control both of those independently because without the isolate scope, both of them would be on the same scope and be controlled by the same instance on contentVisible.
I just don't see anyway with how directives now run in 1.2.0 RC3 to be able to accomplish this without that isolateScope directive (though I would be happy to be proven wrong).
I have updated your code so that it does what I think you want (at a minimum this works the way your old code does, but under rc3 as you wanted): http://plnkr.co/edit/nsq4BGAih3lfNmS2mLP7?p=preview
But I made quite a few changes and a significant architectural change so let me know if this moves away from the spirit of what you're trying to achieve.
I think the gist of the issue was that your two directives (generic and specific) were tightly coupled around contentVisible which created a complex dependency that resulted in you having to very carefully manage invocation timing. My approach was to decouple the two directives- encapsulating contentVisible within generic. This allows generic and specific to instantiate fully independently. So you're not dependent on any invocation timing. And thus the directive priority change no longer has any impact on this code. So, one big win with the solution I propose is it should be robust against further changes by the Angular team.
Specifically, I moved the template in to the same directive (generic) as the controller which manages contentVisible . This way the controller that changes contentVisible lives on the same scope as the template which uses it.
Now specific just calls over to the required: generic controller to toggle visibility (effectively as a setter function).
I also moved the ng-class assignment into the template in order to encapsulate that change within one place (the template) and so you don't need jquery or a link:/compile: on generic.
This is a regression. A fix is in the works: https://github.com/angular/angular.js/issues/4431
I have problem, looks very close to your. So if anything will change want to be notified.
My task: I have contact, that could be shown in defferent ways (very common task), but difference between views is in templates, whereas help functions and preparations are same, so I need generic directive for all views.
What I found:
1. in rc2 it works fine in rc3 unstable
2. in rc3 it could work same only when template is inline, but not when it is templateUrl (even if cached)
So I created two planks rc2 version and rc3 version.
Hope this will help.