I haven't really been fiddling for angularjs's directive for a while and I do not still have a good grasp on it. Before I dive into it, I am thinking react, on how they do their components.
So I have search on how to create reusable components using directives, and found this article:
http://michalostruszka.pl/blog/2015/01/18/angular-directives-di/
But the implementation on his final solution is quite blurry, as I cannot figure out on how to use it correctly.
Let's say I create a title directive:
<epw-title store="epwEventStore">{{vm.title}}</epw-title>
And another directive that uses the same service epwEventStore so that it can update the state
<epw-event-list store="epwEventStore"></epw-event-list>
Where the epw-event-list renders a list and when clicked should change the value of vm.title of the epw-title.
How is this possible?
Update
Q: Are they nested?
A: No, they are siblings.
Don't put Services inside Views
Just to avoid any misunderstanding, if epwEventStore is an AngularJS Service (such as provider, factory, service), it is not meant to be put as attribute value inside your template like that:
<epw-title store="epwEventStore">{{vm.title}}</epw-title>
In a well-designed decoupled architecture, your Views (template) should not "know" anything about your Services. They should only refer to controllers, directives and filters.
Different directives can use the same service
That means you can perfectly have
...
.directive("first", function(myService) {
...
})
.directive("two", function(myService) {
...
})
...
where, e.g., both directive can access and manipulate the same data.
Angular way is declarative
Following Angular's philosophy, if your directive depends on a Service, you declare it in the directive's definition. So whoever reads the code - knows immediately about all dependencies. This makes the code readable.
What the author of the article seems to be suggesting is to use events instead. Which is the publish-subscribe pattern. However, using it inside your directive, especially with $rootScope, makes the directive "leaky" as it is no more encapsulated and can both affect the external state and be affected. And worse - the reader has now manually search all your codebase for whoever is affected by the events emitted from the directive. This pattern has its uses but should be enjoyed with care.
Related
I'm trying to figure out what would be the best way to perform cleanup on different angular directives.
I have different types of directives, some do not define their own scope, some have an isolated scope, and some have a child scope.
I need a generic mechanism that will take care of the cleanup in a separate component that my directives use.
So basically I'm looking at two different options, either register on angular's element.on('$destroy', function() {...}) or on jquery's scope.$on('$destroy', function () {...}).
Here's the problem:
If I register on the underlying element destruction then I miss destruction of directives that they're element was not destructed (not sure exactly how's that possible, noticed it via testing...).
If I register on the underlying scope destruction then (I think) I miss destruction of directives that they're element was destructed, for instance when the directive is not defining its own scope and is using its parent scope.
Looking at angular's directives documentation I came across this:
Best Practice: Directives should clean up after themselves. You can use element.on('$destroy', ...) or scope.$on('$destroy', ...) to run a clean-up function when the directive is removed.
There's no mention as far as I can tell about which strategy to use when.
Also, looking at Angular's documentation I came across this:
When child scopes are no longer needed, it is the responsibility of the child scope creator to destroy them via scope.$destroy() API.
I don't understand the scenario in which I am supposed to call the scope.$destroy() API on my own.
Does it make sense to use angular-services when we use ES6 modules? For example we need a singleton module (userService) in our code and we can do like this:
var app = angular.module('app', []);
app.service('userService', function(){
this.users = ['John', 'James', 'Jake'];
});
app.controller('FooController', ['$scope', 'userService', function($scope, userService){
console.log(userService);
}]);
But we can define the service in separate file:
/* ./user-service.js */
export default users = ['John', 'James', 'Jake'];
, then make the code like this:
var app = angular.module('app', []);
var userService = require('./user-service')
app.controller('FooController', ['$scope', function($scope){
console.log(userService);
}]);
and result will be absolutely the same as with services using. So why use angular services when we can use modules?
Yes! It makes perfect sense.
Services implement a particular responsibility in your application, moving data between the data store and views.
Modules allow you to organize your code and separate sections with different responsibilities.
By putting each service into a module, you make it easier to browse and test your code. It's easy to find all of the code that implements a responsibility.
Source: Difference between service, directive and module =)
From my own personal notes (mostly snippets from the docs, google group posts, and SO posts):
Modules
provide a way to namespace/group services, directives, filters, configuration information and initialization code
help avoid global variables
are used to configure the $injector, allowing the things defined by the module (or the whole module itself) to be injected elsewhere (Dependency Injection stuff)
Angular modules are not related to CommonJS or Require.js. As opposed to AMD or Require.js modules, Angular modules don't try to solve the problem of script load ordering or lazy script fetching. These goals are orthogonal and both module systems can live side by side and fulfill their goals (so the docs claim).
Services
are singletons, so there is only one instance of each service you define. As singletons, they are not affected by scopes, and hence can be accessed by (shared with) multiple views/controllers/directives/other services
You can (and probably should) create custom services when
two or more things need access to the same data (don't use root scope) or you just want to neatly encapsulate your data
you want to encapsulate interactions with, say, a web server (extend $resource or $http in your service)
Built-in services start with '$'.
To use a service, dependency injection is required on the dependent (e.g., on the controller, or another service, or a directive).
Directives (some of the items below say essentially the same thing, but I've found that sometimes a slightly different wording helps a lot)
are responsible for updating the DOM when the state of the model changes
extend HTML vocabulary = teach HTML new tricks. Angular comes with a built in set of directives (e.g., ng-* stuff) which are useful for building web applications but you can add your own such that HTML can be turned into a declarative Domain Specific Language (DSL). E.g., the <tabs> and <pane> elements on the Angular home page demo "Creating Components".
Non-obvious built-in directives (because they don't start with "ng"): a, form, input, script, select, textarea. Under Angular, these all do more than normal!
Directives allow you to "componentize HTML". Directives are often better than ng-include. E.g., when you start writing lots of HTML with mainly data-binding, refactor that HTML into (reusable) directives.
The Angular compiler allows you to attach behavior to any HTML element or attribute and even create new HTML elements or attributes with custom behavior. Angular calls these behavior extensions directives.
When you boil it all down, a directive is just a function which executes when the Angular compiler encounters it in the DOM.
A directive is a behavior or DOM transformation which is triggered by a presence of an attribute, an element name, a class name, or a name in a comment. Directive is a behavior which should be triggered when specific HTML constructs are encountered in the (HTML) compilation process. The directives can be placed in element names, attributes, class names, as well as comments.
Most directives are restricted to attribute only. E.g., DoubleClick only uses custom attribute directives.
see also What is an angularjs directive?
Define and group Angular things (dependency injection stuff) in modules.
Share data and wrap web server interaction in services.
Extend HTML and do DOM manipulation in directives.
And make Controllers as "thin" as possible.
I'm stuck on structuring components inside a large AngularJS application I've been maintaining and I'd really love some guidance since I'm at my wits end. Please bear with me!
Context:
I've got some directives that I'd like to have communicating with each other. As such, I thought it was appropriate to define a controller in each directive to expose an API for other directives to make use of. Now, I'm well aware of the require property in directives and how one can pull in the controllers of parent directives to use. Unfortunately, in my current circumstances, I have directives that don't necessary fit the use of requiring controllers.
Instead of using require, the code base I'm faced with has mostly chosen to add directives directly to the DOM and then to compile them afterwards. I suppose this was to allow for flexibility on customising how directives depend on each other.
I've included a snippet from the link function out of the demonstration Plunker further below that I created to help visualise the problem I'm facing. Note how directives are being attached to the DOM and then being compiled. I tried as best as I could to create a simplified version of the code I'm actually working on because I can't post it.
link: function(scope, elem) {
scope.data = '...';
var d2Elem = elem.find('li').eq(0);
d2Elem.attr('d2', '');
var input = angular.element('<input type="text" ng-model="data">');
elem.find('li').eq(-1).append(input);
$compile(d2Elem)(scope);
$compile(input)(scope);
// Able to get d1 directive controller
console.log(elem.controller('d1'));
// Not able to get compiled d2 directive controller
console.log(d2Elem.controller('d2'));
// Able to get compiled ng-model directive controller
console.log(input.controller('ngModel'));
}
Question:
Could somebody please explain why I'm seeing the behaviour I commented on in my Plunker? Why is it that when I compile a directive I've defined (i.e. d2), I cannot access it's corresponding controller even though it exists in the directive definition?
Coincidentally, I found that after compiling the built-in ng-model directive, I can in fact get its controller.
An extra point I'm pondering: Is the process I've described the least painful way to go about managing directives that communicate with each other? Noting that these directives don't necessary have strict parent-child relationships.
PLUNKER
Would very much appreciate some thoughts!
It is taking time until d2.html is being loaded asynchronously by Ajax, until it's loaded completely you cannot access it's controller, see attached screenshot, after ajax call it's able to access controller of d2.
I tried by replacing
console.log(d2Elem.controller('d2'))}
with
setTimeout(function(){console.log(d2Elem.controller('d2'))},1000);
And it worked for me, may be this will give you some hint or may be putting delay will resolve your issue, I know this is not good practice!!
Based on this Plunker: http://plnkr.co/edit/GufJjrn3OxYVSf2oLD5n?p=preview
I have two directives, for the sake of simplicity, let's name them directiveBlue and directiveRed.
directiveRed has to be a sub element of directiveBlue.
The MainCtrl of our mini app has a simple array under the variable $scope.elements.
This variable is passed to the isolate scope that directiveBlue creates via the data-elements attribute. Notice that the directiveBlue has to be a transclude directive.
Then my main problem is, how do I pass the array of elements to the directiveRed without having to get it doing it via $scope.$parent.elements which seems to me, is a bad practice and then it makes the code tightly coupled.
Any changes to the elements in the deepest directive should then be synched with the rest of the scopes.
Is there any good practice or valid solution for this?
Thanks!
EDIT:
To be more concrete on my use case:
I've created a plunker (http://plnkr.co/edit/i2Busz6E8ehlkG3uEllh?p=preview) with a more concrete situation, where I want to have directive for an action group, I've implemented an option as a simple directive and I want to place my logic in the directives controllers. The method selectAll is pretty simple, but I can imagine having more complex actions which would require the elements from the top scope.
there are plenty of solutions, but without knowing what your goal is, it is more or less guessing.
The following quote is from the angular docs for $compile and describes the use of controller
(...)The controller is instantiated before the pre-linking phase and it is shared with other directives (see require attribute). This allows the directives to communicate with each other and augment each other's behavior. The controller is injectable(...)
a fork of your plnkr to show how to access MainCtrl's $scope.elements in directiveBlue and directiveRed
I have a problem reasoning about components communication.
The main question I tried to reason about and failed: what should I use - $watch or $on ($broadcast/$emit) to establish the communication between components?
I see three basic cases:
Controller + Directive. They communicate naturally using the $scope bidibinding. I just put the service, which incapsulates some shared state, in the $scope using some object ($scope.filter = {}).
This approach seems very reasonable to me.
Controller + Controller. I use the DI to inject singleton services with incapsulated state to communicate between controllers. Those services are bounded to directives using the previous approach. This gives me the data binding out-of-the-box.
Directive + Directive. This is the blind spot in my reasoning. I have directives, that reside in different scopes, in the same scope, etc.
I have directives that must reflect all changes (think about slider + charts) and directives, that must trigger the http request (think about select input).
So, the questions are:
What should I use - $watch or $on ($broadcast/$emit) to establish the communication between components?
Should I tend to use $watch in directive-to-directive communication?
Or should I tend to use $broadcast in directive-to-directive case?
Is it better to share the state using injection+binding or injection+events?
I think this depends on the use case for your directives/components. If you expect to be able to re-use a component without having to modify the scope that the component lives in then using broadcast/emit/on would make more sense. IMO if a component internally has some information that I want to be able to retrieve and do different things with, then the broadcast/emit/on scheme makes the most sense.
If on the other hand I need to trigger some service calls in response to something in a directive or I want to share state between a couple of views I end up using a service.
As noted in the comments another alternative that exists is using the require property in the directive definition object:
require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent']
require - Require another directive and inject its controller as the
fourth argument to the linking function. The require takes a string
name (or array of strings) of the directive(s) to pass in. If an array
is used, the injected argument will be an array in corresponding
order. If no such directive can be found or if the directive does not
have a controller, then an error is raised. The name can be prefixed
with:
(no prefix) - Locate the required controller on the current element.
? - Attempt to locate the required controller, or return null if not found.
^ - Locate the required controller by searching the element's parents.
?^ - Attempt to locate the required controller by searching the element's parents, or return null if not found.
This can be useful in cases where you're creating a "compound component" where multiple directives make more sense than trying to encapsulate all of the functionality into one directive, but you still require some communication between the "main/wrapping directive" and it's siblings/children.
I know this isn't a clear cut answer but I'm not sure that there is one. Open to edits/comments to modify if I'm missing some points.