Angular 1.2.0-rc.3 Directive Priority Change Issue - javascript

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.

Related

Where to implement ng-click functions, controller or directive?

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.

Using and Communicating with Directive from different Controllers

I would like to create a directive that is similar to a control. It should be able to be instantiated from "anywhere" and I want to call functions on it's scope from a parent scope/controller.
A very easy example would be:
I have a special directive called myContactForm (which can be used in different places and pages). Now this myContactForm, in it's templace, uses the directive myEditControl. myEditControl is, when you look at the template nothing but an input and a button and in the scope has the function "clear()" so you can delete whatever is inside the input and maybe other functions.
Now when something happens in myContactForm (for example a button is pressed or a broadcast is received or whatever) I want to call "clear()" on the myEditControl that's inside it. Just like it was a normal control.
How would that be done?
I know about the require but that only works for parents and I would have to know the Controllername of the Directive that uses myEditControl. Instead I want any View/Controller to be able to use myEditControl. And I cannot require the myEditControl-Controller from myContactForm because it is a child not a parent.
What is the technique used in angular to solve these kind of problems?
My idea was to transmit a callback or "myContactForm"-Scope to the myEditControl with an attribute so the myEditControl can "register" itself on the parent and the parent then saves that controls scope for later reference and calling of functions. This seems very hacky though....

AngularJS : directives newbie

I'm starting my great adventure with angular and wanted to ask a question regarding directives usage, as I am not 100% sure after seeing multiple tutorials.
I want to make a simple app giving you directions:
1) click a button, fire in the controller a function to get current position from navigator geolocation (i think no service is necessary for this, and this can stay in the controller?)
2) after getting the coordinates I have some information about the place, which should be shown to the user, and here is the question: Should there be a directive with template for binding these information from the scope and showing in the dom, or is it enough to use simply "ng-hide" (which is in fact a directive - sic!:)) on a div, fetch the information on a place with a service, bind it with the hidden div, and set "ng-hide" to false to display the dom containing place information.
The "ng-hide" variant seems easy, but is it the proper "angular way" or just bad practice of beginners?
Thank You for your time and help in advance:)
IMHO
You can put it in a service if you want to use that method from different controllers or for clean-code purpose.
I use directives when I want a specific behaviour or a group of controls that repeat along the application. If you are using basic html controls and you just need to display/hide I would use ng-hide.

I don't understand Angular data binding at all

I've spent several hours trying minor variations of this code, and I don't understand why one works and the other doesn't.
Here's the scenario: I'm trying to present a list of registered users (which I'm getting with a simple database query that returns just a few columns), then when one user's name is clicked on, I'll retrieve more information about that user from the database, and present it in a different view. At the moment, I'm doing this with regular <a> elements with an ng-click directive that sets a value called currentid. Elsewhere in my code, I use $watch() to send out a new database query whenever currentid changes. That part seems to be working (I see the console.log() output from my watch callback, and the database query is spitting out the right data)... but sometimes currentid changes, and sometimes it doesn't, and I cannot figure out why.
A jsfiddle where it doesn't work: http://jsfiddle.net/aLcRL/11/
A jsfiddle where it DOES work: http://jsfiddle.net/aLcRL/12/
(Click on the "rmunn" and "admin" links in the table: the "Currrent ID" value below should update. And please pardon the almost total lack of CSS; I'm a coder, not a graphics designer ("Dammit, Jim!"), and it's also very late in my timezone right now so I have no motivation to pretty this up. It's functional, it demonstrates the problem, and I'm going to leave it at that.)
The only difference between these two is that one is binding to currentid, and the other is binding to vars.currentid. Now, I understand why binding to currentid wouldn't work in the case of a parent and a child scope (the child's value would overshadow the parent's value); since I'm coming from a Python background, this makes sense to me (it's similar to how Python's instance namespaces can shadow the namespaces of anything defined on the class). But in this jsfiddle, I'm only using one controller -- aren't all these variables in the same scope?
I've been beating my head against this all day, and the several Stack Overflow answers I've tried to read (like How does data binding work in AngularJS? for example) have just left me even more confused: $apply() and $digest() and scopes, oh my! So if anyone can give me a nice simple beginner's guide to scopes in Angular (or point me to an already-written one that I've missed), I'd be very grateful.
"I've learned Clojure and Haskell, I can learn a simple Javascript framework," I thought. "This'll be easy." Boy, was I wrong. :-)
You are running into an issue where ng-repeat creates a child scope.
<tr ng-repeat="user in data" blarg="{{user.id}}">
<!-- currentid here is part of the ng-repeat child scope
and not your root scope -->
<td>{{user.userName}}</td>
<td>{{user.email}}</td>
</tr>
If you need to access the parent scope you can use $parent.currentid
{{user.userName}}
You need to use $apply() whenever you modify a value outside of the angular world. For example using setTimeout() or a jQuery Plugin. Calling $apply() alerts Angular to reevaluate the scope to see if anything changed and update accordingly.

How to make two controllers perform same action in Angular, but change visual aspect of site?

I would like to replicate Google's home page functionality in Angular, and it's causing me grief. I've gone through the Egghead videos and read the entire API, but there's no particular example for that exact behavior. What I'd like it to do is the following:
user comes to home page, main search bar is present and generic black header bar
user searches for something, and only when he presses "search" does the main search bar disappear, the url changes to mysite.com/q/searchTerm and a new sub-header appears under the black header bar much like with Google's home page, where the main search field is removed and placed in a grayish bar under the main header bar (if you have instant-search off)
the results of the search appear in place of the now gone main search bar, just like with Google, but this part I can handle with routes and views. The layout switch between two identical controllers is what bothers me.
So far what I've tried was:
make a parent controller for both MainCtrl sub controllers, and set its scope.data = {searchHeaderDisplay: false}
have both sub controllers share the same name (MainCtrl) because they share the exact same functionality
make the one in the header bar ng-show="data.searchHeaderDisplay" and the main one ng-hide="data.searchHeaderDisplay" and then try switching the data.searchHeaderDisplay on ng-click of Search Button. This didn't work - no effect was produced.
I'm still coming to terms with AngularJS, so I'm sure it's quite simple, I just need a practical example or two to learn from.
Edit: would it be better to shove the secondary header (with the smaller search field) into a separate view template along with the search results, and just have the root view be the main search field? The documentation is very lax on best practices regarding views and routes, especially routes that will have multiple controllers doing something.
If I understand you correctly, you set scope.data = {searchHeaderDisplay: false} on the parent controller of the two MainCtrl controllers, with the intention to enable the MainCtrl controllers to share the same model data. That's all fine.
Without seeing your code, my guess is that the problem lies in how you switch data.searchHeaderDisplay on ng-click. You might have set data.searchHeaderDisplay at the child scope level (ie. the scopes that corresponds the MainCtrl controllers) when you should have assign the value to their parent scope level. Let me know if you need me to elaborate.
UPDATE:
After taking a look of the provided code, the problem is indeed as what I suspected earlier (above).
mainProductSearch() is what needs to be changed. Instead of scope.data.searchHeaderVisible = true;, you need scope.$parent.data.searchHeaderVisible = true for the reason briefly explained earlier. If the rationale behind is still not clear to you, then you probably need to familiarize yourself with prototypal inheritance chain of AngularJS scope (and/or Javascript object in general). Scope prototypal inheritance an essential part of AngularJS. Here is a great article on the topic.
Instead of an AppCtrl (which is essentially acting like $rootScope), and using $parent (which is a fragile solution because changing the HTML structure could cause this to break -- e.g., you might find you need to use $parent.$parent... if you add an intermediate ng-controller), I suggest a service for storing model data related to your header. Let's call it searchService.
Controllers that need to affect this model can inject the service. This has the additional advantage that the dependencies are clear (vs controller $scope inheritance, where the dependencies are not clear). E.g., when the user presses "search", the controller can call a notification method defined on the service: searchService.newSearchTerm(searchTerm). Now, all views (like the header view) that are watching for changes in the model will notice the change and can update accordingly.
You might consider using ng-view for the main content area of your page.
See also https://stackoverflow.com/a/14619122/215945, where a very similar layout is discussed. In that SO post, a shopping basket with an item count is in the header. The item count needed to be updated by multiple controllers, so we put it into a service.

Categories