Communicating between a Multiple Controllers and a directive - angularjs

I have a directive that powers an HTML5 Canvas visualization. This directive has a wide array of methods to modify different parts of the visualization. The issue is that multiple controllers that have a different parent/child/sibling relationship need to communicate to this directive. Right now I have it wired this pretty awful way of emitting events up to the parent controller of the directive and then broadcasting them to the directive.
I have heard of using a service to do something like this, but nothing really explain why. I thought of using something like this:
angular.service('CanvasCommunication', function($rootScope) {
this.canvasAction = function() { $rootScope.broadcast('canvasAction'); };
}
And then have listener in the canvas to actual execute that action. This service could be then injected into any controller that communicates with the canvas.
The issue with this is that $rootScope.broadcast() has terrible performance and I want to make sure this communication channel is built in the most efficient way.
Has anybody dealt with something like this and thought of something better?

I've had the same issue - controllers needing to interact with each other, different parts of the app sending messages to each other, etc. In my projects, I've implemented a MessageService. Here's a very basic version of one (but honestly more than sufficient):
module.factory('MessageService',
function() {
var MessageService = {};
var listeners = {};
var count = 0;
MessageService.registerListener = function(listener) {
listeners[count] = listener;
count++;
return (function(currentCount) {
return function() {
delete listeners[currentCount];
}
})(count);
}
MessageService.broadcastMessage = function(message) {
var keys = Object.keys(listeners);
for (var i = 0; i < keys.length; i++) {
listeners[keys[i]](message);
}
}
return MessageService;
}
);
You might want to have listeners registered for particular subjects, and filter messages by subject, or not. Mine also queue messages on subjects until they're cleared, so that the messages can be viewed when a new view loads (in order to pair for ex. 'Success - Saved file' with a page change).

Related

ngRoute How to pass data to controller in other view [duplicate]

I am working with AngularJS for my latest project. In the documentation and tutorials all model data is put into the controller scope. I understand that is has to be there to be available for the controller and thus within the corresponding views.
However I dont think the model should actually be implemented there. It might be complex and have private attributes for example. Furthermore one might want to reuse it in another context/app. Putting everything into the controller totally breaks MVC pattern.
The same holds true for the behaviour of any model. If I would use DCI architecture and separate behaviour from the data model, I would have to introduce additional objects to hold the behaviour. This would be done by introducing roles and contexts.
DCI == Data Collaboration Interaction
Of course model data and behaviour could be implemented with plain javascript objects or any "class" pattern. But what would be the AngularJS way to do it? Using services?
So it comes down to this question:
How do you implement models decoupled from the controller, following AngularJS best practices?
You should use services if you want something usable by multiple controllers. Here's a simple contrived example:
myApp.factory('ListService', function() {
var ListService = {};
var list = [];
ListService.getItem = function(index) { return list[index]; }
ListService.addItem = function(item) { list.push(item); }
ListService.removeItem = function(item) { list.splice(list.indexOf(item), 1) }
ListService.size = function() { return list.length; }
return ListService;
});
function Ctrl1($scope, ListService) {
//Can add/remove/get items from shared list
}
function Ctrl2($scope, ListService) {
//Can add/remove/get items from shared list
}
I'm currently trying this pattern, which, although not DCI, provides a classical service / model decoupling (with services for talking to web services (aka model CRUD), and model defining the object properties and methods).
Note that i only use this pattern whenever the model object needs methods working on its own properties, that i'll probably use everywhere (such as improved getter/setters). I'm not advocating doing this for every service systematically.
EDIT:
I used to think this pattern would go against the "Angular model is plain old javascript object" mantra, but it seems to me now that this pattern is perfectly fine.
EDIT (2):
To be even clearer, I use a Model class only to factor simple getters / setters (e.g. : to be used in view templates). For big business logic, i recommend using separate service(s) that "know" about the model, but are kept separated from them, and only include business logic. Call it a "business expert" service layer if you want
service/ElementServices.js (notice how Element is injected in the declaration)
MyApp.service('ElementServices', function($http, $q, Element)
{
this.getById = function(id)
{
return $http.get('/element/' + id).then(
function(response)
{
//this is where the Element model is used
return new Element(response.data);
},
function(response)
{
return $q.reject(response.data.error);
}
);
};
... other CRUD methods
}
model/Element.js (using angularjs Factory, made for object creation)
MyApp.factory('Element', function()
{
var Element = function(data) {
//set defaults properties and functions
angular.extend(this, {
id:null,
collection1:[],
collection2:[],
status:'NEW',
//... other properties
//dummy isNew function that would work on two properties to harden code
isNew:function(){
return (this.status=='NEW' || this.id == null);
}
});
angular.extend(this, data);
};
return Element;
});
The Angularjs documentation clearly states:
Unlike many other frameworks Angular makes no restrictions or
requirements on the model. There are no classes to inherit from or
special accessor methods for accessing or changing the model. The
model can be primitive, object hash, or a full object Type. In short
the model is a plain JavaScript object.
— AngularJS Developer Guide - V1.5 Concepts - Model
So it means that's up to you how to declare a model.
It's a simple Javascript object.
I personally won't use Angular Services as they were meant to behave like singleton objects you can use, for example, to keep global states across your application.
DCI is a paradigm and as such there's no angularJS way of doing it, either the language support DCI or it doesn't. JS support DCI rather well if you are willing to use source transformation and with some drawbacks if you are not. Again DCI has no more to do with dependency injection than say a C# class has and is definitely not a service either. So the best way to do DCI with angulusJS is to do DCI the JS way, which is pretty close to how DCI is formulated in the first place. Unless you do source transformation, you will not be able to do it fully since the role methods will be part of the object even outside the context but that's generally the problem with method injection based DCI. If you look at fullOO.info the authoritative site for DCI you could have a look at the ruby implementations they also use method injection or you could have a look at here for more information on DCI. It's mostly with RUby examples but the DCI stuff is agnostic to that.
One of the keys to DCI is that what the system does is separated from what the system is. So the data object are pretty dumb but once bound to a role in a context role methods make certain behaviour available. A role is simply an identifier, nothing more, an when accessing an object through that identifier then role methods are available. There's no role object/class. With method injection the scoping of role methods is not exactly as described but close. An example of a context in JS could be
function transfer(source,destination){
source.transfer = function(amount){
source.withdraw(amount);
source.log("withdrew " + amount);
destination.receive(amount);
};
destination.receive = function(amount){
destination.deposit(amount);
destination.log("deposited " + amount);
};
this.transfer = function(amount){
source.transfer(amount);
};
}
As stated by other posters, Angular provides no out-of-the-box base class for modeling, but one can usefully provide several functions:
Methods for interacting with a RESTful API and creating new objects
Establishing relationships between models
Validating data before persisting to the backend; also useful for displaying real-time errors
Caching and lazy-loading to keep from making wasteful HTTP requests
State machine hooks (before/after save, update, create, new, etc)
One library that does all of these things well is ngActiveResource (https://github.com/FacultyCreative/ngActiveResource). Full disclosure--I wrote this library--and I have used it successfully in building several enterprise-scale applications. It's well tested, and provides an API that should be familiar to Rails developers.
My team and I continue to actively develop this library, and I'd love to see more Angular developers contribute to it and battle test it.
An older question, but I think the topic is more relevant than ever given the new direction of Angular 2.0. I would say a best practice is to write code with as few dependencies on a particular framework as possible. Only use the framework specific parts where it adds direct value.
Currently it seems like the Angular service is one of the few concepts that will make it to the next generation of Angular, so it's probably smart to follow the general guideline of moving all logic to services. However, I would argue that you can make decoupled models even without a direct dependency on Angular services. Creating self contained objects with only necessary dependencies and responsibilities is probably the way to go. It also makes life a lot easier when doing automated testing. Single responsibility is a buzz work these days, but it does make a lot of sense!
Here is an example of a patter I consider good for decoupling the object model from the dom.
http://www.syntaxsuccess.com/viewarticle/548ebac8ecdac75c8a09d58e
A key goal is to structure your code in a way that makes it just as easy to use from a unit tests as from a view. If you achieve that you are well positioned to write realistic and useful tests.
I've tried to tackle that exact issue in this blog post.
Basically, the best home for data modeling is in services and factories. However, depending on how you retrieve your data and the complexity of the behaviors you need, there are lots of different ways to go about the implementation. Angular currently has no standard way or best practice.
The post covers three approaches, using $http, $resource, and Restangular.
Here's some example code for each, with a custom getResult() method on the Job model:
Restangular (easy peasy):
angular.module('job.models', [])
.service('Job', ['Restangular', function(Restangular) {
var Job = Restangular.service('jobs');
Restangular.extendModel('jobs', function(model) {
model.getResult = function() {
if (this.status == 'complete') {
if (this.passed === null) return "Finished";
else if (this.passed === true) return "Pass";
else if (this.passed === false) return "Fail";
}
else return "Running";
};
return model;
});
return Job;
}]);
$resource (slightly more convoluted):
angular.module('job.models', [])
.factory('Job', ['$resource', function($resource) {
var Job = $resource('/api/jobs/:jobId', { full: 'true', jobId: '#id' }, {
query: {
method: 'GET',
isArray: false,
transformResponse: function(data, header) {
var wrapped = angular.fromJson(data);
angular.forEach(wrapped.items, function(item, idx) {
wrapped.items[idx] = new Job(item);
});
return wrapped;
}
}
});
Job.prototype.getResult = function() {
if (this.status == 'complete') {
if (this.passed === null) return "Finished";
else if (this.passed === true) return "Pass";
else if (this.passed === false) return "Fail";
}
else return "Running";
};
return Job;
}]);
$http (hardcore):
angular.module('job.models', [])
.service('JobManager', ['$http', 'Job', function($http, Job) {
return {
getAll: function(limit) {
var params = {"limit": limit, "full": 'true'};
return $http.get('/api/jobs', {params: params})
.then(function(response) {
var data = response.data;
var jobs = [];
for (var i = 0; i < data.objects.length; i ++) {
jobs.push(new Job(data.objects[i]));
}
return jobs;
});
}
};
}])
.factory('Job', function() {
function Job(data) {
for (attr in data) {
if (data.hasOwnProperty(attr))
this[attr] = data[attr];
}
}
Job.prototype.getResult = function() {
if (this.status == 'complete') {
if (this.passed === null) return "Finished";
else if (this.passed === true) return "Pass";
else if (this.passed === false) return "Fail";
}
else return "Running";
};
return Job;
});
The blog post itself goes into more detail on the reasoning behind why you might use each approach, as well as code examples of how to use the models in your controllers:
AngularJS Data Models: $http VS $resource VS Restangular
There's a possibility Angular 2.0 will offer a more robust solution to data modeling that gets everyone on the same page.

Angular - reusing code (service or controller)

I'm using Angular to develop commenting functionality for a web app.
Currently there are two sections in the application were a user can comment:
Category
Product
About 90% of the commenting functionality is the same for both sections and as such I would like to make this reusable - i.e write some service or controller that I can reference/use as a base.
So far, my research seems to point to using a factory service but unfortunately this doesn't seem to work (I've spent the whole day running through various tutorials).
It is quite possible that I am over thinking this and making it far too complicated but I honestly don't know which way to turn anymore.
Herewith a quick and dirty overview of what I have so far:
HTML view for the category
Controller for the category (receives data from service and posts data to service in order to bind data to model)
Service for the category (retrieve and stores all the necessary
data)
The product uses the same logic and a lot of the code in the service and controller will be duplicated.
I've merged the two services into one service successfully but I'm having trouble doing the same for the controller.
Do I:
Write a base controller that will communicate with the above mentioned service and that will hookup with the two existing controllers
OR
Write a factory/provider service that hooks up to the two existing controllers as well as the above mentioned service.
If you go the route of using a factory, you could put all the common functionality into its return object and reference that from your controllers.
Factory
angular.module('myModule').factory('CommonFunctions', function(){
return {
foo : foo,
bar : bar
}
function foo(){
console.log('foo');
};
function bar (){
console.log('bar');
};
}
Controller
angular.module('myModule')
.controller('myController', ['CommonFunctions', function(CommonFunctions) {
var vm = this;
vm.foo = CommonFunctions.foo();
vm.bar = CommonFunctions.bar();
}
angular's separation of service types ie:
for specific values
constant
value
(constant for specific values needed before other services are created)
for functions
factory
service
provider
(provider for specific instances when you need a services before other services are created, usually taking advantage of constants)
allow the ability to share data and ways to process that data between controllers and or directives, anything that can be a value can also be a constant, the only difference there being where they can be injected. Similarly any service can be rewritten to a factory or a provider, it is more your specific use case / what your more comfortable writing that would determine which to use, but really the best way to think about it is if you have a value that needs to be shared but is not needed inside angular.module.config then use value, otherwise use constant, now if you have a single function that you want to share, (maybe it processes that value in some way or maybe it just does something else) you should write it as a factory, then when you have a few of those factory's that deal with either that value, or anything else, you can combine them into a service or configure and combine them using a provider. here is a simple example (note i am using the recommended syntax for writing angular services):
'use strict';
var app = angular.module('test.app',[]);
app.constant('configureableValue',{defaultValue:55});
app.value('editableValue',{defaultValue:100,editedValue:null});
app.provider('configureValue',configureValueProvider);
configureValueProvider.$inject - ['configureableValue'];
function configureValueProvider(configureableValue){
var defaultVal = configureableValue.defaultValue,
originalVal = defaultVal;
return {
getValue:getValue,
setValue:setValue,
resetValue:resetValue,
'$get':providerFunc
};
function getValue(){
return defaultVal;
}
function setValue(val){
defaultVal = val;
}
function providerFunc(){
return {
get:function(){ return getValue(); },
reset:function(){ resetValue(); }
};
}
function resetValue(){
defaultVal = originalVal
}
}
// this factory is an example of a single function service, this should almost always be defined as a factory
app.factory('getEditableValue',getEditableValue);
getEditableValue.$inject = ['editableValue'];
function getEditableValue(editableValue){
return function(){
return editableValue.editedValue ? editableValue.editedValue : editableValue.defaultValue;
};
}
// same with this one
app.factory('setEditableValue',setEditableValue);
setEditableValue.$inject = ['editableValue'];
function setEditableValue(editableValue){
return function(val){
editableValue.editedValue = val;
}
}
// now this is an example of a service service collecting the factorys for an object with all the related behavior we need
app.service('editableService',editableService);
editableService.$inject = ['getEditableValue','setEditableValue'];
function editableService(getEditableValue,setEditableValue){
var self = this;
self.setVal = setEditableValue;
self.getVal = getEditableValue;
}
app.config(appConfig);
appConfig.$inject = ['configureValueProvider'];
function appConfig(configureValueProvider){
configureValueProvider.setValue('i changed '+ configureValueProvider.getValue() +' to this!!!!');
}
app.run(appRun);
appRun.$inject = ['configureValue','editableService'];
function appRun(configureValue,editableService){
console.log('before editing: ',editableService.getVal());
editableService.setVal('changed!!!');
console.log('after editing: ',editableService.getVal());
console.log('we changed this in the config func: ',configureValue.get());
configureValue.reset();
console.log('and now its back to the original value: ',configureValue.get());
}
i know thats a lot for a simple example, but there are a lot of features provided by angular, and many ways to use them, hopefully this helps.

Best way to keep and share huge data between controllers

The App I'm building is complex and the data is huge. It is an order administration dispatch app.
At the beginning the first thing I have to do it to get from the API all the orders, all the stores and all the couriers.
During the life cycle of an order I receive several updates from the API and I handle everything through Server Sent Events.
Once I load the data I put everything in 3 different $scope: $scope.orders, $scope.stores and $scope.couriers and a I created a service to handle and share updated data between controllers.
One of the service look like this:
App.factory('Orders', ['logs', function (logs) {
var orders = {};
var newOrder = null;
var updatedOrder = null;
orders.list = [];
orders.getNewOrder = function () {
return newOrder;
};
orders.setNewOrder = function (order) {
newOrder = order;
};
orders.updateOneOrder = function (index, order) {
orders.list[index] = order;
updatedOrder = order;
};
orders.getUpdatedOrder = function () {
return updatedOrder;
};
orders.getAllOrders = function () {
return orders.list;
};
return orders;
}]);
And I $watch for those methods to see changes and update the relative $scope.
As you can see in my service I have one more Array to store data in order to be able to handle it due to the fact I can not use $scope in a Factory.
I followed a guide online regarding this. I know there is also Broadcast and Emit but probably it was too complex to handle all those things with that method.
One more thing I avoided is to store orders, stores and couriers in $rootScope.
I think the App is a bit slow and use a lot of memory to handle everything so I wish to know the best practice to handle huge data that need to be shared between controllers and updated in real time...
I think my method at the moment is using too much memory.

Accessing factories in the same Angular module

In my Angular app, I have some resource modules, each containing some cache factories.
For example,
projectRsrc.factory('getProjectCache', ['$cacheFactory', function($cacheFactory){
return $cacheFactory('getProjectCache');
}]);
I have a few of these to cache values received from the servers.
The problem is that at times I'd like to clear all the caches. So I want to put all the cacheFactories into one CacheCentralApp module and delete all the caches with a single call.
The trouble is, I don't know of any way to access other factories inside my module. So for instance, if I create a module CacheCentralApp, and in it, declare factories that provide cacheFactorys, how can I create a function in there that calls removeAll() on every cacheFactory?
I don't think it is possible to target all the factories of a certain module. I think however that another solution to your problem is to send a event that all factories has to be cleared. This will prevent that you will have to loop through all your factories and call a .clear() function on everyone.
You could send a event request with the following code:
$scope.$broadcast('clearAllCaches');
And listen to this event in every factory with:
$scope.$on('clearAllCaches', function() {
clearCache();
}
In a separate module you might create a factory for that:
var cacheModule = angular.module('CacheCentralApp', []);
cacheModule.factory('MyCacheFactory', ['$cacheFactory', function($cacheFactory) {
var cacheKeys = [];
return {
clearAll: function() {
angular.forEach(cacheKeys, function(key) {
$cacheFactory.get(key).removeAll();
});
},
get: function(key) {
if(cacheKeys.indexOf(key) == -1) {
cacheKeys.push(key);
return $cacheFactory(key);
} else {
return $cacheFactory.get(key);
}
}
}
}]);
To create new or get existing Cache you simply call MyCacheFactory.get(cacheName). To clear all the caches ever created in the factory you call MyCacheFactory.clearAll().
Note: I am not quite sure that Array.indexOf is available in every browser, you might want to use Lo-Dash or another library to make sure your code works.

How to destroy an angularjs app?

I need to be able to load/unload angular applications dynamically without causing memory leaks. In jQuery you can do $("#elementHoldingMyWidget").remove(); and the proper destruction code gets executed, event handlers are unbound etc.
I've been unable to find anything in the angular docs mentioning the possibility of tearing down an app once it's been bootstrapped.
My 1st attempt was to destroy the rootScope like so:
var rootScope = $("body").scope();
rootScope.$destroy();
But this doesn't seem to be working, and I'm not sure how the injector and services would be cleaned up even if it did.
How should this be done?
Using AngularJS 1.4.0, $rootScope.$destroy() is working again (as it was broken in 1.2). Using this permits to switch between several angularJS apps:
var appManager = new function () {
this.currentAppName;
this.currentApp;
this.startApp = function (appContainerId, appName) {
if (this.currentApp) {
this.destroyApp(this.currentApp, this.currentAppName);
}
var appContainer = document.getElementById(appContainerId);
if (appContainer) {
this.currentAppName = appName;
this.currentApp = angular.bootstrap(appContainer, [appName]);
}
}
this.destroyApp = function (app, appName) {
var $rootScope = app.get('$rootScope');
$rootScope.$destroy();
}
}
// Call this when page is ready to rebootsrap app
appManager.startApp('divContainerId', 'app');
To tear down my application without presenting the user with a white page via $('body').empty, I first $delete() the child scopes and then remove all the properties from $rootScope:
/*
* Iterate through the child scopes and kill 'em
* all, because Angular 1.2 won't let us $destroy()
* the $rootScope
*/
var scope = $rootScope.$$childHead;
while (scope) {
var nextScope = scope.$$nextSibling;
scope.$destroy();
scope = nextScope;
}
/*
* Iterate the properties of the $rootScope and delete
* any that possibly were set by us but leave the
* Angular-internal properties and functions intact so we
* can re-use the application.
*/
for(var prop in $rootScope){
if (($rootScope[prop])
&& (prop.indexOf('$$') != 0)
&& (typeof($rootScope[prop]) === 'object')) {
$rootScope[prop] = null;
}
}
UPDATE March 10, 2013: I found that $('body').empty(); does not tear down the app. It still lives.
ORIGINAL POST:
Well, this post: https://github.com/angular/angular.js/issues/1537#issuecomment-10164971 claims that there is no 'official' app tear down (at the time of writing), but you can just empty the element holding the app like so:
$('body').empty();
If this is not what you are looking for you can go through these steps for a temporary solution to tearing your app down:
https://github.com/angular/angular.js/issues/1537#issuecomment-10184033

Resources