Recommended way to handle AngularJS Directive data - angularjs

I regularly see examples such as <calendar events="a.appointments"></calendar> in which data assigned within a controller is passed in to a directive via an attribute binding.
The code below shows an alternative solution, in which the required data is gathered directly within the directive link function. Using this approach eliminates the need for a separate controller.
diary.html
<calendar></calendar>
calendar.js
angular.module('diary').
directive('calendar', ['AppointmentsService', function(AppointmentsService) {
return {
template: 'calendar.html',
scope: {},
link: function($scope) {
$scope.events = {};
AppointmentsService.getAppointments().then(function(result) {
$scope.events = result;
});
}
};
}]);
Is this a suitable or ultimately flawed approach to take?

If your calendar directive is completely decoupled from everything, it is absolutely fine approach to go for. However, if you need a certain communication between calendar and, let's say, events-tracker directive, you might want to enclose them in a parent controller. Or if you have a collections of calendar directives on a single page (with an option to remove or add new calendar), then passing the data by reference is a way to go as well.
EDIT
e.appointments are probably passed to calendar through scope, not attribute, i.e. calendar having isolated scope with events field.

Having thought about this some more, I've realised the alternative solution does have a flaw and it's a reusability issue.
When providing events through an attribute binding to the directive's isolate scope, the source of the data is totally flexible.
Fetching the appointments directly within the link function of the directive however, creates a specific dependency on AppointmentsService as the data source.

Related

AngularJS - Sharing data from a parent route controller to all child directives on the page

I have a page that has multiple components and I've created each component as a directive. When the page is first loaded, that's when I grab all the data that should be available on the page. So all of the data exists on the controller for that route, which we'll just call pageCtrl. And then what I've been doing is binding any required data to each directive through the attributes, which of course ends up creating an isolate scope for each of them.
I know there are a few ways to share data, so given this situation, is there a recommended way of doing it? Or has anyone had better success doing it one particular way? While it's working perfectly fine the way I'm doing it, I've run into a few caveats. If I need just even one bit of information from the pageCtrl, I need to add another attribute to the directive. So it ends up creating more code on the directive element itself.
I was thinking about just creating a service that would store all the data, which the pageCtrl could initialize, instead of setting it on itself. Any feedback would be appreciated.
good question :)
First solution is to create in parent controller object and pass this object (via ng-model) to all directives. This object will be passed by reference (not by value) so controller and all directives will have access to the same object.
```
// in controller
$scope.shared_data = {someItems: []};
// in html
<my-directive ng-model=shared_data></my-directive>
Second solution is to create some simple service to store all of those data.
// in this solution you have to inject additional service to directive controller
(extended idea of point 2) creating service/factory that will be responsible by collecting and returning data. This service could be injected into directive and use the same methods to collect data. To avoid making multiple calls to API (REST) it could have some cache for each sensitive method.
Communication via events.... (probably the worsts solution for your example)
The first two ideas are probably the best, I do not know full specification of your product so final solution picking belongs to You:).
My advice is to try/play with all of those methods to really understand what is going on and how and when to use each of them :)
You can directly call your parent controller from child directive controller by using $parent.
App.controller('aCtrl', ['$scope', function ($scope) {
$scope.refresh=function(){
.......... //Updated Data get from DB
};
...........
}]);
App.directive('bDirective',function(){
restrict: 'EC',
replace: true,
scope: {},
controller: function($scope) {
$scope.$parent.refresh();
}
...
});
HTML:
<div ng-controller="aCtrl">
<div class="bDirective"></div> //directive
</div>

Communicate between two same directive having isolated scope

I am novice to angularjs and i have a custom directive which has an isolated scope. I am having problem communicating between two instance of same directive .
How can i do that ? Your suggestion is highly appreciated .
<div date-control="cal1" ng-model="mydate" calendar-properties="calendarProperties1"></div>
<div date-control="cal2" ng-model="mydate2" calendar-properties="calendarProperties2"></div>
What i want to do is open both date-control at once having 'to' and 'from' attribute in calendar properties using some data sharing or any thing ? you can check this plnkr sample.
directives are just the result of a function call
angular.directive('myDir',function(){
var common;
return directiveObject;
});
directiveObject being the any of the variantes you use to create your directive.The main thing is that right before returning you can declare common(class if you will) variables and methods that can be checked for changes or invoked so you could do something like
app.directive('myDir',function(){
var bus={
value1:0
};
function increase(){
bus.value1++;
}
return {
scope:{},
template:"<div><h1>{{counter}}</h1><button ng-click="increase()">add</button></div>"
controller:function($scope){
$scope.bus=bus;
$scope.increase = increase
$scope.$watch('bus',function(){
//something here
})
}
};
});
a sample of this can be found here. this can be shaped in many ways
http://plnkr.co/edit/d9dpIYCAjOaOBNjoI80u?p=preview
some other methods can be used like emitting and broadcasting events or even using services, but i like the simplicity of this method.
You can either store this information directly in a Service, or you can have the directives communicate with each other. In this case, I'd have them communicate.
One option that would work is to use .$broadcast to send a message from the $rootScope and then use .$on to receive that message.
Example: inside your "From" directive
// Once "From" date is selected
$rootScope.$broadcast('from:set', date);
Then, inside your "To" directive
scope.$on('from:set', function (event, data) {
console.log(data); // Will log "From" date
});
There are a lot of different ways you can do this that will work (e.g. you could put them both on the $rootScope or you could have them .$emit upward instead of .$broadcast downward), so use whatever you feel makes the most sense in your case.
There is a great blog post on the subject here: http://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
You are basically trying to share data across different $scope. You will get many suggestions, about storing in $rootScope, $broadcast, but these solutions are not optimal as they do the job but with an overhead.
Create a service, and store the properties "to" and "from" into that service, then inject that service into your directive..
http://plnkr.co/edit/xJrBneiU8RU92czHAZ43?p=preview
.service("MyService", function(){
var from = 'Cal1';
var to = 'Cal1';
return {from:from, to:to};
})
.controller('DatepickerDemoCtrl', function ($scope, MyService) {
$scope.calendarProperties1 = {
format: "yyyy-MM-dd",
label: "Enter the Date From",
from: MyService.from,
opened:false
}
$scope.calendarProperties2 = {
format: "yyyy/MM/dd",
label: "Enter the Date To",
to: MyService.to,
opened: false
}
})
In this example, you will be able to see how we can use Service variables inside directives ... Using this solution will solve your problem
**Edit, I have edited your plunker code, to modify the calendar properties object to use data from the service. Now both calendar properties object will get data from the same service variable.. I hope this might solve your problem
I want to thank you all for answering and providing some suggestion.If i want to communicate through one directive with another directive of same type i will retrieve the scope of the directive to call or share/get the data of another directive by using .scope() method . There may be other way but i am just a novice of angular and i am using this way to access the methods and data between directives .

How to communicate between controllers while not using SharedService between them?

I was reading all the answers about communication between controllers and directive, but is seems to me occurred using shared service and inject it to each one of them. When I'm developing a very large scale application, I don't know what my page is going to have in it. I may have 2 controllers need to communicate between them, and I also may have 5 directive and 2 controllers in the same page. I don't know from scratch what is going to be inside my view/page, so I need a better way how to communicate between them. I'm looking for that better way to do it, the right AngularJS way.
http://i60.tinypic.com/2z87q05.png
Above is my view example I'm working on: on the left side I have a directive tree, on the right side I have chart controller and directive grid. I may have more than this, but this is a good example of what I may have. Keep in mind, the view can have X componenets, you don't know from the beginning what will be in it.
Now, lets say each time I select node in tree on the left, I want to be able to tell the other controllers that nodeSelectedChange event happend. I don't want to inject each one of them a service that holds that info, so I thought about something like having a Page Manager Controller, which is the father of the all view. All my controllers/directives inside my page should talk with each other only by the PageManagerController, he is the only thing in page that knows what he has inside of it.
Important here to keep in mind: The tree don't know the page has chart or grid, they don't need to know each other in order to communicate. The page manager knows everything, now I want it to make the magic - Should it has a service? Should each other component has service and service can talk with PageManager service?
Help me to think. Hope I can connect all the dots to create a BETTER way for communication.
I think the sharedService way is good for small app, when you know from start what is going on in your app, but most of the time - You just don't know who and when is going to use your directive, it should be able to talk with everyone.
What I don't like about events:
The events being fired inside the controller, while it should be inside a service.
The listener controller should know the name of the event he is going to listen too. If I change the event name being $emit or $broadcast, I need to go all over the listeners $on("eventName") in all app and change to that unique name.
Directive is like a black box, I don't want to check inside of it each time and find the names of the events he is being broadcasting in order to communicate with it.
I need a way to exposed the events NAMES out of the directive, probably with a service connected to that controller.
There are a couple of good practices:
Use the $scope to communicate between directives and controllers for runtime dependencies (e.g. models you fetch from the server, instantiated classes, etc.). The way to go is to have an isolated scope with attributes mapped to the dependencies:
directive('tree', function(){
return {
scope: {
somevalue : "="
}
}
});
Use it like this:
<tree somevalue="objectFromController">
Use the injected services to communicate with static dependencies (Presentation Models, global sharable state, etc.)
directive('tree', function(treeState){
return {
scope: {
somevalue : "="
},
link: function(scope){
// some logic updating the treeState
treeState.openNodes = ['x', 'y'];
}
}
});
controller('ctrl', function($scope, treeState){
// react to treeState changes
// you can use $scope.$watch for it
// or any other way you like
// see: https://github.com/mr-mig/angular-react-to
});
If you want to have maximum composability, stick to the first pattern:
directive('tree', function(){
return {
scope: {
model : "="
},
link: function(scope){
// some logic updating the treeState, stored as scope.model
scope.model.openNodes = ['x', 'y'];
}
}
});
controller('ctrl', function($scope, treeFactory){
$scope.treeModel = treeFactory.create();
// react to treeState changes
// you can use $scope.$watch for it
// or any other way you like
// see: https://github.com/mr-mig/angular-react-to
});
And compose this stuff using template and binding as a communication bus:
<tree model="treeModel">
Sticking to this pattern you get:
No events
Well-defined directive "interface" (attributes in the isolated scope)
Easy composability
Reactive behavior based on scope change propagation
It really depends on the type of information you want to share from the tree directive to the other sections of the page.
You have a few options in front of you:
Use Scope Events
As someone mentioned above, you could fire an event and listen for events in various controllers and/or services. That said, it can get really ugly, really fast. Tracing events and figuring out what event listeners are active at a given point can give the best engineers a migraine!
Use a Service
Another option would be to use a Service, let's say a PageManagerService. In that case,
Each click of a tree item would set some information on the PageManagerService, saying which page, what objects, and what items it needs to display
Each component that needs to change could
Register a listener to be triggered when the PageManagerService changes
Add a watch on the service and run its code when the PageManagerService changes
The service itself is just going to be shared state, and it would be upto the directives and components on how it wants to consume and respond to changes in the state.
Use UI Router
But the more I think about this, the more it seems like a good use case from something like UI Router. UI Router allows you to define states, and have different parts of the page respond in different ways to state changes. Each section could respond to a state change in its own way, by loading
A different controller
A different template, possibly with different components and widgets
So what you would end up having is a structure with
The Tree directive on the left
A ui-view named, let's say, top
A ui-view named, let's say, bottom
Each item in your tree directive can then be a ui-sref, which is just a fancy way of saying instead of redirecting to a URL, redirect to a state.
You could then define your configurations in your application in a single place, like so:
$stateProvider.state('dashboard', {
views: {
"top": { templateUrl: 'my/dashboard.html', controller: 'DashboardCtrl'}
"bottom": { templateUrl: 'my/dashboard-grid.html', controller: 'DashboardGridCtrl'}
}
})
Similarly, you could have a state definition for each item in your tree directive, and each one just be a link to a different state.
Of course, the state definitions are done in your config section in an AngularJS application, which you would know is before the application starts. What if you needed dynamic states as well?
Well, a few answers / thoughts to lead you down the way for that as well:
The controllers and services would have to be predefined, you would not be dynamically creating HTML content and/or JS controllers
It is possible to expose the $stateProvider as a global variable in the config section, and dynamically call stateProvider.state inside a controller / service, wherever you have new state definitions.
More often than not though, the controllers and HTML remain constant, and we just need to trigger the different states with various parameters. That can easily be done by calling transitionTo function to transition to a defined state with various state parameters.
you can use $on, $emit and $broadcast to communicate among different controllers/scopes .
Please follow one of my earlier post . I have a setup a plunk . You can try out the example.
Angular Js newbie - link in a controller view that triggers another controller action
$on : setup a event handler
$emit : communicate to parent controllers
$broadcast : communicate to child controllers
To know more visit - https://docs.angularjs.org/api/ng/type/$rootScope.Scope

Exposing directive controller to parent controller

I want to expose some of my directive's functionality through its controller (think a public API for this directive).
return {
restrict: 'E',
scope: {},
controller: function($scope) {
this.method1 = ...;
this.method2 = ...;
},
controllerAs: 'dir',
link: function (scope, element, attrs) { ... }
}
Then in my parent controller or template call dir.method1 to get stuff accomplished inside the directive. Any ideas if this is possible as of Angular 1.3?
I'd like to refrain from event passing or even function passing, I have heard this is possible although I have never seen an implementation of this.
It is possible, but your issue isn't to figure out how to get the API out. It's how to get TO it from the parent. You're creating an isolate scope through your use of the 'scope' option. You're also making an element-type directive, so I'm guessing you're doing something like this:
<my-parent>
<my-child></my-child>
</my-parent>
where <my-parent> is the parent directive, and <my-child> is the directive with the API you want to expose.
The real question is what you're trying to achieve here. There is totally a way to do what you're asking. Just because the scope is isolated doesn't mean you can't get to it. You can just iterate through the parent $scope's $$childHead/etc list to find the child whose API you want to access. Anything you define in the child like this:
$scope.myApiFunction = function() {
};
will be visible here. (Things you put into 'this' will not - use the $scope storage bucket instead.)
That means if you only had ONE child you could do something like this from the parent controller:
$scope.$$childHead.myApiFunction();
Simple. Also, very crude. There are lots of problems here: what if you have many children? What if this child with its API ends up one level down? Etc. It's breaking all kinds of OO patterns and it's going to get messy, fast.
Your question is very abstract - it might be good if you updated it with an exact example. Without that, let me guess at your goal. There are two ways to do something "like this" that are encouraged within Angular:
Services. Whenever you say "API", think Service first. A service is a singleton (automatically) so it's tailor-made for creating APIs. And services can use the Factory pattern to return objects of a type, so THOSE are tailor made for doing things like having a manager service handle, say, a buddy list in an IM client, with API methods for creating, removing, and finding buddies.
Items that add "optional" functionality to their parents when they're defined. Let's say we have three possible types of tooltips: tooltips that have a hover effect, those that have a click effect, and those that are triggered by a "walkthrough" system in some order. For this kind of thing, the easy thing to do is just reverse the API, like this:
Parent Controller:
$scope.tooltipHandler = {
showTooltip: function() {},
hideTooltip: function() {}
};
Child Controller:
$scope.$parent.tooltipHandler = {
showTooltip: function() {
// Do some real work
},
hideTooltip: function() {
// Do some real work
},
}
What happens here is if there's no tooltip defined, when the parent runs its walkthrough, nothing happens. If you add the blue tooltip display module, when the parent runs its walkthrough now, it's going to show blue tooltips.
Make sense?
I arrived here looking for a similar response. So far the best that I can figure is to do what Angular does with ngForm.
In the documentation clearly states
If the name attribute is specified, the form controller is published onto the current scope under this name.
This basically makes the form controller accessible from anywhere.
If you have the following DOM
<div ng-controller="MyCtrl as parentCtrl">
<form name="parentCtrl.frmCtrl">
<my-child-directive>
</form>
</div>
You can use require: 'ngForm' in my-child-directive to get access from an inside directive. If you are in the parent controller you can access it trough the frmCtrl variable.
Not sure if this is best practice. In ngForm the name attribute works well, but I don't even know how to call such an attribute for a custom directive.
Thats why I arrived here, I wanted to know if this is "The Angular way" and what types of convetions are on the subject.
Hope it helps!

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.

Resources