In an AngularJS expression is there a way to compare a scope value to a value in another library? - angularjs

I'm creating a directive around a third party library, to go in a form, where the option chosen in a select drop-down will bring up a different set of form elements.
In the parent element of each subset of form elements I'm trying to use an expression similar to this: ng-if="myScopeObj.val === ThirdParty.CONSTANT_VAL". I came to realize it's not working because the "ThirdParty" library isn't on the scope.
Should I just assign the library to a variable on the scope, or is there some pattern that can address this? It seems like creating isThis() or isThat() functions for every constant in the library wouldn't be a great solution.
Should I create a service to wrap the third party library and then inject it into the directive? Though I'd still need to put the injected service on the scope. Would that be overkill for a library that doesn't access remote APIs? I don't think it'd need to be mocked for testing, anyway.

You're correct that you do need to get the value on the $scope somehow in order for it to be usable. And you're correct that one of the primary benefits of wrapping in a service is that you can mock the library. Another benefit of wrapping in a service is self-documentation. As someone else (or yourself at a later time) looking at your code, I could be confused as to where ThirdParty is coming from. Working in Angular, the assumption is that all dependencies are injected, and breaking convention comes at a cognitive cost. Having a service also can make it easier to swap out the underlying library later for a different implementation. Anyway, your simplest fix is:
$scope.ThirdParty = ThirdParty;

Related

Should I call parent directive controller or create service

Let's say there is a "conversation" directive, made of "message" directives.
The message directive has a button to delete it.
The conversation controller has a method to remove a message from its list.
In Angular documentation, they say we can use "require" to access a parent controller from a directive controller.
And here, the answer suggests to use require too, and to create a service only if you can't use require because the 2 directives are not related.
Isn't it a bad practice to call a controller from another controller ?
I thought service was typically used to share information between controllers.
Don't you think it would be better to create a service with the deleteMessage method, and inject this service in the message controller ?
I'm personally not a fan of calling a function on the parent or root or stuff like that, as its coupled in a kind of implicit way. I prefer to use a service as you mentioned or injecting the concrete function(s) into your directive.
Below are some pros and cons for both approaches. Please note they are based on my personal experience (and I'm not an expert).
Services
Services are singletons, meaning there is only one instance in your whole application. This can have several advantages, if you need them:
it's always there (you don't need to create it explicitly)
it can cache data which can be usefull, e.g. to maintain the state of a view/page
it can be used to exchange data between different components
it can easily be injected where ever you need it
This can also lead to some disadvantages though:
if different components share the same service, they might mess up each others data
components (i.e. directives) will depend on that service, leaving the user of the directive no flexibility in how he wants to use that directive (e.g. what should happen when a user clicks a button). Meaning that basically the concrete functionality is "hard-coded" in to the directive.
I personally like to use services, if they have a direct link to the directive (and to nothing else) or do not store any state, for example if they only contain simple helper functions.
If in your application you have one conversation list, then I would consider implementing that in a service that exposes functions like addMessage, deleteMessage, sortBy, etc.. Then it can be injected in to any component which needs access to that (central) list.
Passing functions as arguments
On the other hand, if you have a message-directive, you might want to use it for other kind of messages as well..? That is messages which are not from the conversation but maybe from a mailbox (just a stupid example ;)). Then I wouldn't couple the service and the directive. You could inject the deleteMessage-function, which in one case would remove it from the conversation and in the other case remove it from the mailbox.
I think both approaches are valid, but always depend on the scenario and how the components are shared/reused over your application.
The second approach might be nicer from a "coupling"-perspective, but might get complicated in case of lots of parameters and when passing parameters over several levels of components (e.g. to child-child-child directives).
The first approach is easy to implment and can have several advantages, but results in the component being directly coupled to that service, lowering its reusability.
Do you really need to put the delete button in the message directive ?
Since the delete button is to suppress the message from the conversation controller list, I'd rather put the button in the conversation view.
That being said, if you really want to have the delete button on the messagedirective, I would simply pass the delete method as an argument in your directive. When doing this you don't have to worry about which controller defined the function, you simply call it from your child directive (and if you want the parent controller to execute it, simply pass a binded function to your directive).

Why are global functions considered "wrong" in Angular 1.3

Traditionally I have managed my Angular code like this
//File 1
angular.module('name',[])
//File 2
function TestController(){
}
TestController.prototype.// inherited stuff
angular.module('name').controller('testController',TestController);
This worked great and allowed me to partition my files easily. Now I try to upgrade to 1.3 and get the infamous...
Error: [ng:areq] Argument 'TestController' is not a function, got undefined
Of course this is due to this change which claims a desire to clean up the way people write code. What about this pattern is more complex? Is there a way to maintain this pattern without changing the global settings?
There is actually a comment on the page you linked to that had a fairly solid explanation.
Global controllers refer to your controllers being defined as function
on the window object. This means that they are openly available to
conflict with any other bit of JavaScript that happens to define a
function with the same name. Admittedly, if you post-fix your
controllers with ...Controller then this could well not happen but
there is always the chance, especially if you were to use a number of
3rd party libraries. It is much safer to put these controller
functions inside the safety of a module. You then have more control
over when and where this module gets loaded. Unfortunately controller
names are global across an individual Angular app and so you still
have the potential for conflict but at least you can't clash with
completely different code in the JavaScript global namespace.
So the idea is that global controller functions could conflict with any other global function in any javascript you use. So to eliminate the chance of a conflict with your own code or a third-party script, not using global controllers makes your code safer and more consistent.
As mentioned in the comments by #Brett, you can use IIFE around your prototyping. Here is an update of your plunk that uses that. The main change just looks like this.
(function() {
TestController.prototype.name = 'World'
})();
What comes to my mind is 2 things:
1) in that way functions wont be kept in memory more than they should.
2) if you minify your code, minifyer will have to generate new names for all global objects, which is sfine when you have small project, but will be a problem when it's not.
Also it should prevent tests to modify unnecessary data.

angularjs using angular.extend to bind $scope to a service bad/good practice

I'd like to know if using
angular.extend($scope,MyService);
Does it break oop encapsulation principle ?
Does it smell like MyService.call($scope) ?
Could you face variable and function conflicts ?
Is this a bad/good practice?
Typically from my experience services are injected into the controller and then will be called from that scope. I wouldn't say that using extend to copy over functions and properties is necessarily bad, but it might mitigate some of the purposes of IoC (inversion of control), which is what injection in angular is based on.
Does it break oop...?
My understanding is that what you would get from this call is additional functions and service calls directly on your scope. This doesn't break OOP in the sense that scope is an object and would have functions applied. Provided those functions + properties make sense on the scope it seems like a fine thing to do from that perspective.
Does it smell like MyService.call($scope)?
As I said in the first paragraph - I don't see why you wouldn't just call the service and either share data or pass in references to objects to the service. Another pattern that is common in angular is to use a promise to process returned data in your scope. That looks like:
MyService.callFunction(parameters).then(function (data) {
// process data here. Since this runs in $scope you can also use $scope normally.
// $scope.$apply() is in progress and will complete when the function returns
});
All the service does is provide the data to the scope then. Point is that I think there are better patterns than "extend".
Can you face conflicts?
In the call angular.extend(a, b); data, properties and functions are copied from b to a. If something already exists on a it will be overwritten with the data from b. So technically the short answer is "yes", you can face conflicts.
The bottom line
So at the end of the day this isn't a bad pattern but there are probably more common patterns I would try to use first.

Putting presentation data in angular controller?

Got a webapp I'm building in Angular.
This app walks a user to authorizing accounts, presenting specific instructions based on the users choices.
I've implemented this as HTML that is shown or hidden based on values in the model, so for 3 different choices, I have 3 different sets of HTML sections that are similar but with different texts.
In the spirit of DRY, I should instead have one set of HTML sections, and instead switch the text based on the values of the model. This means putting text data inside the model, including small snippets of markup, like anchor and strong tags.
Does putting presentation data into the controller violate the principals of Angular?
There are quite a number of options to avoid repeating code depending on what you are looking to do. The following ideas are things I would consider and use when they make sense (I placed these from simple to complex, so you probably can skip the first few):
ng-bind -- Put it on a span/div. Simple & works to bind the model to the display
ng-switch, ng-if, ng-hide, ng-show -- Work to conditionally show an element
custom directive -- use this when you want to alter the behavior of an element or if you want to alter the dom based on a template. If you use "ng-transclude" the contents of the element you template will be included in the result. This can be very elegant but it works best when you have a single format. I can provide examples but angular's documentation also has excellent examples.
service -- I generally use this just to provide data only. This could be via a restful api and $resource or via $http calls. Either way, I wouldn't recommend doing much more than load/save data here.
$scope method -- In other words:
$scope.myMethod = function(x,y,z) { /* code making decisions based on the model */ }
Then you can call this method from one of the previous either via a prebuilt directive (ng-show, etc) or via a custom directive that manipulates the dom for how you expect it to be.
ng-bind-html -- Last option I know to suggest is to use this directive combined with the $sce service to bind whatever you want to the DOM. If you are binding something with angular code in it - make sure to use the $compile service as well. I generally don't favor this approach except as a last resort because it makes it harder to find where elements in the DOM are coming from and it can make debugging + testing a real pain. That said, these tools wouldn't exist if people didn't need them.
I'm sure that this isn't complete and maybe others have suggestions but that is where I would start. Best of luck!
I would put the text data in a separate angular service. This article gives an example: http://joelhooks.com/blog/2013/04/24/modeling-data-and-state-in-your-angularjs-application/
Then if you decided at some point to move it to some other storage, your service would still be the single access point for the rest of the app.

Angularjs: Find all instances of a directive

I'm trying to create a directive to allow the user to navigate the page with arrow keys by section. But I also want to be able to have those sections be scattered around the dom, and to have this not break when stuff gets added and removed. I can think of several ways to do this, but none of them are satisfactory:
Create a directive with a controller that lets other directives register themselves (and unregister on $destroy). But this will be out of order if I add something in the middle later. Also, I've tried writing it this way, and it seems like way more code than necessary.
Whenever the user hits an arrow key, make an empty array, and $broadcast an event, with a callback for directives to register themselves on that list. Then, once that list is full, advance or go backwards on it. They (should?) come back in the order they're in on the DOM, but I'm not sure since this way seems crazy and hackish.
Mark things that are 'tabbable' with css, and write this the simple way in jquery, something like this: On a new click event, var all = $('.tabbable'), and then do the obvious with that. But I really don't want to do it that way, because it's not 'the angular' way. Not out of some sense of purity, but because I'm building this as part of a larger library of widgets, and I want this functionality to be accessibly to them.
So, is there any way for me to get the scopes of all directives of a certain type, without resorting to weird hacks, or spreading the logic out all over the place?
This is a good question. +1
First, finding all directives or nodes by type goes against the Angular way. The View is the official record in AngularJS, so directives should say what they do and do what they say. Coding some process somewhere to scan for DOM nodes and act accordingly is problematic for several reasons, not the least of which are separation of concerns and testability.
I'm glad to see you're looking at other options, but I agree that the other options you provided are sub-optimal for the very reasons you mentioned. But I have one more. This is one that I've used for a different application, but that required knowledge of scattered DOM nodes.
First, we create a service to manage the state of this component. It's simple. Let's call it SectionsService. Next, we create a directive to register sections. Let's call that section for simplicity. The section directive registers the DOM node's ID (maybe created programmatically to ensure uniqueness) with the SectionsService during its linking phase. Since the DOM is processed (mostly) in order, the nodes added to the SectionsService will also be in order. So the DOM looks something like this (irrelevant stuff omitted):
<div section>...</div>
<!-- other stuff -->
<div section>...</div>
<!-- other stuff -->
<!-- etc. -->
(Though out of scope here, it would not be very difficult to program it in such a way that the order wouldn't matter, but it'd be based on specifics of your app that I don't know.)
Next, you create your triggers, like an arrow key handler. On these events, you simply tell the SectionService to go to the previous/next node in the list. AngularJS comes with a service called $anchorScroll that can be used to emulate the browser's hash-based positioning we're familiar with. You could obviously also use a jQuery plugin to animate the scrolling if you wanted to.
And that's it! A very simply directive, a fairly simple service, and whatever trigger(s) you need. All told, I'd guess less than 100 lines of code including tests. All components are decoupled and easily testable, but still really quite simple. The view remains The Truth. The Angular Way is preserved.
And there was much rejoicing.
I hope this sets you on the right direction, but of course feel free to ask a follow-up question. We can also talk code specifics too if you'd like; as I said, they wouldn't be very complicated.
AngularJS services are singletons and can be required via dependency injection. You could have your directives require a state manager service and call incrementers/decrementers.
Alternatively, a little easier but more brittle, you could keep an array in $rootScope. It's more idiomatic "angular" (but not by much) than a jquery selector global, but probably not the best route if you're building a widget library.

Resources