AngularJS - Make a directive "scope-sensitive" - angularjs

I have a page with 2 containers on it , each one contains images. I want to add some specific visual attributes and functionalities to these images. These specific stuff is similar in both depositories but it is not the same. So i want to create one directive for 2 types of images , but i want it to behaviour a little bit differently depending on in what depository ( actually, scope ) image is placed. Checking for element's scope in directive's body:
studio.directive('orientable', function () {
return {
link: function(scope, element, attrs) {
if(scope.id=="Depository1"){
// give to element some specific behaviour
}else if(scope.id=="Depository2"){
// give to element another specific behaviour
}
}
}
seems ugly to me.
I can't use passing arguments to a directive, because then image's tag will turn to unreadable and when there are many images it will be disaster . I do not want two directives because they will be basically same.
So i want one directive , but somehow it must be "polymorphic".
Is it some kind of inheritance possible here? I think i'm missing something important in understanding of Angular' directives .

If you want a single directive to behave differently based on scope, then checking some scope property seems perfectly reasonable to me.
The closest I think you can come to polymorphism in Angular is to put shared things (data, functions, etc.) into a service, then inject that service into the places you want polymorphism (directives, controllers, other services, etc.).

Related

Angular - Access Enums from view

I have some server side enums that I'm sending down to an angular application.
Ideally, I'd like to be able to access the enums for this sort of behavior:
<select ng-options="type.name as type.value for type in Enums.TYPES" />
I've tried several things to get this working:
angular.module('myModule').constant('Enums', {myEnumObject})
var Enums = {myEnumObject};
$window.Enums = {myEnumObject};
obviously, none of these ways make the object accessible from the view. I've also tried using services to return the object, but that doesn't make it accessible from the view.
My problem here is that I know it can be done from the scopes, using one of these:
$rootScope.Enums = {myEnumObject};
OR
$scope.Enums = {myEnumObject};
My concern with this is that this seems unsustainable. Using a scope seems like bad practice since every child scope created will be polluted with this data.
I could also do it by assigning the enums to a controller, but then that seems like it's kind of defeating the purpose of having these global objects. In reality, they ARE constants that never change.
It seems like I must be missing something here. Can somebody point me in the right direction for maintaining sustainability for this code, as well as handling it in an "angular" way. Thanks.
One way to avoid attaching the constant or service enum to every scope you need, is to take a directive centric design to your application. As directives can have isolate scopes, you can bundle your views/controller and scope bindings together in a nice reusable package. For example:
.directive('SomeEnumThing', function(Enums){
return {
scope: {}, // don't forget to set an isolate scope on the directive
templateUrl: 'sometpl.html',
link: function(scope, elem, attrs){
// bind the enums to your directive's scope
scope.enums = Enums;
}
};
});
The only downsides to this method are the extra verbosity in writing a directive (but ultimately more reusable), and the added requirement of setting up any other necessary bindings with outside objects (as you are now outside the general scope hierarchy).
You could do:
angular.module('myModule').constant('Enums', {myEnumObject})
Then in any scope that you want to use it, you can
$scope.Enums = Enums;
This is not very polluting, only a scope that requires it will have it set.
Global constants can be put on the $rootScope, and this can be seen as polluting. But if this is something that you do need all throughout your app in various directives, it's not such a big deal (IMO). There's no correct answer to this question.

Dynamically switching template of a directive

Is possible (I'm guessing it is) to dynamically change template of a directive, depending on certain factors?
So, I see how theoretically it's possible - you can put all different templates into the $templateCache beforehand, and retrieve the one you need during directive compilation
something like:
restrict:'E'
scope:
data:'='
template:'='
link:(scope, elem, attrs)->
html = $templateCache.get(scope.template)
tElement.html(html)
However real question is, would be the right thing to do so? Will that badly affects performance? If for example directive used as a cell in a big grid?
Switching between Directives is a bad practice.
The Why:
Lets assume you have a DropDown Menu ( <select> ), and you Have set couple of directives that each one is linked to its option (by ngModel).
When the App runs,
You will start with initial directive set by the default value of the <select> property, and when you would try to replace that value, you would have to "Delete" the directive from the DOM and "Write" use a new one.
By now this should sound to you that it sound like jQuery.
The What Else should I do:
Well, you almost answered it for your self: use ng-switch.
ng-switch by AngularJS
Notes:
1) Switching between directive templates is possible (in the exact way you mentioned it, you set couple of templates on the background, and you load the one that fits the most according to the user interaction), but it sounds like you are about to type serious Spaghetti Code Which will be very difficult to maintain.
2) Performance: I can't tell if that would affect seriously on the performance of the app, but it will definitely be much harder to maintain.
3) If ng-switch doesn't fit there, You should look at this problem in a different angle.
EDIT:
Yes, It's common to do so:
And I've done that in couple of projects.
You have to pass a function to your template property of the new .directive.
Example:
templateUrl: function (elem, attrs){
return attrs["template"] == "table" ? "tableTemplate.html" : "listTemplate.html";
}
What this means : is that you have to add a so called "Support" attribute to your new directive. In this case I called it template.
It would look like so : <div new-directive="data" template="table"></div>

Is it recommended or good practice to use a directive that matches standard html tags?

I wanted to write a directive that only applied to IMG tags throughout my whole page, and initially I thought I would have to decorate each tag with a custom directive name, such as:
<img my-img />.
But, while I was putting together some sample code for this question, I decided to see if the directive would match on the element IMG itself. And it worked!
Here's what I did: http://plnkr.co/edit/z4n4a3MN89nRNYyXKCih?p=preview
app.directive('img', function () {
return {
restrict: 'E',
link: function (scope, element) {
element.bind('load', function () {
element.addClass('fadeIn');
});
element.bind('error', function () {
element.removeClass('fadeIn');
});
}
};
});
As you can see, I wanted all images on a page to fade in when they loaded. I wanted to do this in an angular fashion without using jQuery, so I thought this was a good approach, but is it good practice? In my case, I really do want this logic to apply to all the images on my page (and there may be hundreds), so I thought this would be a clean way of doing it, but for the life of me I haven't found anywhere where anyone else does this (i.e., matching a directive to an IMG tag or any standard tag for that matter).
I think I would avoid the img directive. Take note that Angular has already added their own directives which match html element names (e.g. - form, input, select, script), so it seems conceivable that there could potentially be a conflict if they (or any library you use) utilize the same directive name. And do you really want to fade in all images? What if you use an image as a decoration on the page?
It seems like it would be best to instead add the attribute. It's very intuitive with nominal effort. If you don't care about the built in attributes, you could also create your own element (e.g. ).

AngularJS: Using services in directives

This is an angularjs app. I have a service that handles the loading of content (ajax). While the service is getting the content, a number of things throughout the app hide, later showing again (depending on the content returned). They might have the same scope, different scope, whatever. They just need to hide while content is loading, and then show when it's done. Pretty normal stuff.
Right now, I have separate controllers watching a "loading" property of the service and using regular angular directives (ng-show, ng-hide, etc.) to show/hide. But this feels like overkill. I'd prefer to write a custom "loading" directive that injects the loading service and does the watching and showing/hiding.
My question is: Is what I want to do "bad"? The controller way, I end up boilerplating a bunch of code, maybe up to like 5 or 6 times, or even more as the app grows. The custom directive way, I write it once and use an attribute where I need it. Yeah - there's a dependency on that service, but that just doesn't feel like the end of the world that some people have made me start to think I should think it is.
For what it's worth, I feel like I've heard "separation of concerns" so many times I've become paralyzed by it. It leads me to overthink everything because I want to do things the right way, but it sure doesn't feel like I'm being very productive.
If I understood correctly, you have a bunch elements that should hidden when a particular service is loading data, and then be displayed again when the data is loaded, right?
In that case, events might be a good solution:
they can be global to the appliciation (which i think is what you are aksing for).
they allow for avoiding direct coupling between elements (also one of your concerns).
So, in your service, just broadcast events when stuff happens:
$rootScope.$broadcast('loading-data');
axajStuffOrWhatever(function() {
$rootScope.$broadcast('data-loaded');
});
Then, wrap the show/hide behaviour in a directive that will listen to those events.
.directive('hideWhileLoadingData', function() {
return {
link: function(scope, el, attrs) {
scope.$on('loading-data', function() {
el.css('display', 'none');
});
scope.$on('data-ready', function() {
el.css('display', 'block');
});
}
};
});
Use the directive:
<div hide-while-loading-data>something</div>
The advantage of using events here, is that later on, they could be originated by a different service, or by multiple services, and the directive will not be affected by that as long as the events are the same.
For more complex behaviour, you could also parametrize the events and the directive, so different elements will react to different kind of stuff.
I've made an example of this.
In my opinion all scopes which depend on this service should be children of one parent scope. If you have the parent scope responsible for talking with the service then any directive of any scope can access it via $parent on the $scope.

Managing communication between independent AngularJS directives independently

This is more of an organizational approach to solving this issue rather than a direct solution. My question itself is that if I have two directives which are not dependent on each other and can both work independently to serve their purposes. But if one of the directives is present then the other one needs to execute once the other is ready. In this case then what would be the logical way to make sure that it works out that way without the need to hardcode any function calls or events?
Lets say for example you have one directive which builds a grid of some sort:
angular.module('App').directive('appGrid',function() {
return function($scope, element) {
$scope.rows = ...
};
});
Then I have another directive that makes the element horizontally scrollable:
angular.module('App').directive('appPane',function() {
return function($scope, element) {
element.attachHorizontalScroll();
};
});
So an example of my HTML would look like this:
<div data-app-grid data-app-pane>
<div data-ng-repeat="row in rows">
<div data-ng-repeat="cell in row.cells">
{{ cell.data }}
</div>
</div>
</div>
Basically the appPane directive needs to run after the appGrid directive has been executed and the table is ready.
One solution I can think of is to watch the data to see when it is ready using the $scope.$watch method, but this poses a problem since the change can occur multiple times and this would be bad design to redundantly update the page and it also poses a problem if multiple directives are writing to the same scope variable that is being watched.
Another solution is to have the first directive emit an event (something like elementReady) and then have the 2nd directive take over. But what about if the first directive isn't there? Then how would the 2nd directive know when to do it's job? There could be another directive which is basically an empty directive which just fires the event for all other elements, but this is a bit of hack. Also what happens if multiple other directives fire the elementReady event?
One more solution is to create a 3rd directive which shares the logic between the two directives via a shared service. But this makes the 3rd directive fully reliant on both other directives as well as the shared services in between. This also require more, unnecessary testing code as well as actual code to write the directive (much more code compared to the 2nd solution, which would require only one + one lines of code).
Any ideas?
Have a look at the priority attribute of the directives.
Here is a copy of the exact description from the angular docs :
priority - When there are multiple directives defined on a single DOM element, sometimes it is necessary to specify the order in which
the directives are applied. The priority is used to sort the
directives before their compile functions get called. Higher priority
goes first. The order of directives within the same priority is
undefined.
you should be able to find it in
http://docs.angularjs.org/guide/directive
under the Writing directives (long version) --- Directives definition Object section.
Hope this answers your question.
I had a similar problem. I couldn't use priority, since the wiring occurred after clicking on the element. I solved it using $rootScope. Here is a simplified example:
link : function (scope, element, attrs) {
element.on('click', function() {
$rootScope.$emit('myEvent', myData);
});
}
In the other directive you 'listen' for myEvent:
link : function (scope, element, attrs) {
$rootScope.$on('myEvent', function(data) {
// do sth
});
}
Great question. I would use a combination of attribute(s) and event(s).
Since only the user of the directives (i.e., the person writing the HTML) knows that he/she wants the two directives to interact (i.e., run dependently), I think he/she needs to specify this somehow, and attributes seem like a good way (the only way?). Once the directives know they need to interact, they can use events to do the signaling.
So, if directive B needs to wait for directive A, one or more attributes can be used to specify who should do the waiting, who should fire an event, and/or what the event name is. (This of course can be expanded to more than two directives, and more than one event.) Some possible implementations:
<div data-app-grid data-app-pane idc-wait="appPane" idc-event="idc-appGridDone">
<div data-app-grid data-app-pane idc-wait="appPane" idc-emit="appGrid" idc-event="idc-appGridDone">
By examining the attributes, the appGrid directive can determine that it doesn't need to wait, but it does need to emit event "idc-appGridDone". Similarly, by examining the attributes, the appPane directive can determine that it needs to wait for an "idc-appGridDone" event before it runs.
If the directives don't find any of these special "inter-directive communication"/"idc-" attributes, they run normally (no events, no waiting).
The "(in)dependent inter-directive communication pattern" is born. ☺

Resources