Circular dependencies between services.

7,965 views
Skip to first unread message

Witold Szczerba

unread,
Jun 2, 2011, 7:10:11 PM6/2/11
to ang...@googlegroups.com
Hi there,
I have encountered a problem today like this: I have a $xhr.error service:

angular.service('$xhr.error', function(loginService) {
return function(request, response) {
console.log(request.url+":"+response.status);
if (response.status == 401) {
loginService.loginRequired(request);
}
};
}, {$inject: ['loginService']});

This service is supposed to handle all the HTTP 401 problems, so it
notifies "loginService", so it can notify a login dialog, login dialog
is supposed to ask for credentials and ask my loginService to
authenticate. When authenticate is successful, all the failed requests
are to be resend. So far so good, but my loginService needs $xhr
service:

angular.service('loginService', function($xhr) {
....
$xhr is needed in login method, so it can send credentials to the server.
....
}, {$inject: ['$xhr']});

The problem here is $xhr requires $xhr.error, $xhr.error requires
loginService and loginService requires $xhr. It looks like <angular/>
does not support circular dependencies of services, does it?
When I open my application in Firefox, it stops responding. Chromium
logs: RangeError: Maximum call stack size exceeded.
Is there any way to walk this problem around?

Thanks,
Witold Szczerba

Witold Szczerba

unread,
Jun 3, 2011, 6:55:30 AM6/3/11
to ang...@googlegroups.com
Maybe my use-case is not _that_ common, but the following causes the
same problem:

angular.service('$xhr.error', function($xhr) {
....
}, {$eager: true});

Attaching an example. WARNING: this will crash Firefox (Chromium is
fine, don't know about other browsers). Remove $xhr dependency and
everything works fine.

The real problem is how to introduce XHR error handler which is able to resend.
$xhr.error cannot depend on $xhr, but $xhr does depend on $xhr.error
and this is hard-coded. Traditional approach to break circular
dependencies between "A" and "B":
A --> B, B --> A
is to introduce "C":
A --> C, B --> C and C --> [A,B]

In our case (A is $xhr and B is $xhr.error) the first dependency
cannot be removed:
A --> B (fixed), B --> C, C--> [A,B]
but this is still a circular dependency and will crash in runtime.

I remember Misko was writing about this some time ago, found it:
http://misko.hevery.com/2008/08/01/circular-dependency-in-constructors-and-dependency-injection/

Will try to apply that to my problem :)

Regards,
Witold Szczerba

Witold Szczerba

unread,
Jun 3, 2011, 6:57:06 AM6/3/11
to ang...@googlegroups.com
interdepended.html

Witold Szczerba

unread,
Jun 3, 2011, 7:01:47 AM6/3/11
to ang...@googlegroups.com
Sorry, the previous file did not reference Angular correctly.

On 3 June 2011 12:55, Witold Szczerba <pljos...@gmail.com> wrote:

interdepended.html

Adam Pohorecki

unread,
Jun 3, 2011, 7:08:45 AM6/3/11
to ang...@googlegroups.com
You could get that service later, with something like this:

angular.service('foo', function(...) {
var scope = this;

return function(...) {
if(should_resend){
var xhr = scope.$sevice('$xhr');
...
}
};
}, {$inject: [...]});

Adam

> --
> You received this message because you are subscribed to the Google Groups "angular" group.
> To post to this group, send email to ang...@googlegroups.com.
> To unsubscribe from this group, send email to angular+u...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/angular?hl=en.
>
>

Witold Szczerba

unread,
Jun 3, 2011, 7:12:41 AM6/3/11
to ang...@googlegroups.com
This is brilliant, thanks!

Vojta Jina

unread,
Jun 3, 2011, 7:25:41 AM6/3/11
to ang...@googlegroups.com
Hey Witold,

I thought angular's injector threw exception on circular dependency, but you are right. It does not.
Created issue:  I hope it should. Thanks for pointing that out...

Your solution - bringing new object C doesn't solve the problem.
To do so, you have to extract C from B, so that A requires C, B requires A and C.
I guess you just made a typo.

Another solution could be using not mandatory dependency:
Let's say A requires B = A can't work without B -> inject in constructor
Sometimes B requires A, but could work without it -> for example logger... Then you can inject it through setter - later.

It's kinda hack, but you can do that in service:
this.$service('$name')

Anyway, in your case, I would think about using event bus. Actually, I think it could be good to bring event bus into angular to allow for example communication between widgets etc...
But I have to think about it more deeply, it's just quick idea...

V.

Vojta Jina

unread,
Jun 3, 2011, 7:27:09 AM6/3/11
to ang...@googlegroups.com
Sorry, forgot link to the issue:

Witold Szczerba

unread,
Jun 3, 2011, 7:59:28 AM6/3/11
to ang...@googlegroups.com
On 3 June 2011 13:25, Vojta Jina <vojta...@gmail.com> wrote:
> Anyway, in your case, I would think about using event bus. Actually, I think
> it could be good to bring event bus into angular to allow for example
> communication between widgets etc...
> But I have to think about it more deeply, it's just quick idea...
> V.

Hello,
I have, actually, introduced an event bus concept already. At first I
wanted to create a generic event bus, so anyone interested would
announce "anything" and anyone listening could add a condition and
callback functions. Then I changed my mind, because such a service
could become a global context of entire application, anyone would
declare dependency on it and I was worried it could make the code
harder to maintain and follow. Couldn't it? I am not sure anymore.

At the end I decided to start with a "loginService" which is kid of
specialized event bus for login related stuff. As I said, I am not
sure if the generic event bus would be a bad or good idea though. As
for the bringing an event bus into angular - it could be quite a
simple service considering current angular's infrastructure.
Introducing this could have a positive impact on how people try to
sort things out in their applications (considering a generic event bus
is fine).

Thanks,
Witold Szczerba

Witold Szczerba

unread,
Jun 3, 2011, 2:07:09 PM6/3/11
to ang...@googlegroups.com
On 3 June 2011 13:25, Vojta Jina <vojta...@gmail.com> wrote:
> Your solution - bringing new object C doesn't solve the problem.
> To do so, you have to extract C from B, so that A requires C, B requires A
> and C.
> I guess you just made a typo.

Going back to the $xhr and $xhr.error example, suppose one needs:

Acceptance criteria:
The $xhr.error, for every HTTP 500, to wait 10 seconds and retry. One
cannot ask for $xhr in $xhr.error and one cannot ask any other service
which depends on $xhr.

It looks like now - there is no simple solution for that task. The
only possible way, would be to introduce 2 extra services:

1) message bus - like middleman and
2) "http500RetryService"

Here we go:
============================================
service('$xhr.error', function(messageBus ) {
return function(request, response) {
if (request.status == 500) {
messageBus.publish("$xhr.error_http500", request);
}
}
}, {$inject:['messageBus']});

service('messageBus', function() {
....
....
....
....
....
... nice implementation of message bus ..
....
....
....
....
....
....
});

service('someEagerService', function($xhr, $defer, messageBus) {
messageBus.register("$xhr.error_http500", function(request) {
$defer(10 * 1000, function() {
$xhr(request.method, request.url, request.data, request.callback);
});
});
}, {$inject:['$xhr', '$defer', 'messageBus'], $eager: true});
============================================

That would work, but comparing it to a simple few lines of code:
============================================
service('$xhr.error', function($xhr, $defer ) {
return function(request, response) {
if (request.status == 500) {
$defer(10 * 1000, function() {
$xhr(request.method, request.url, request.data, request.callback);
});
}
}
});
============================================
makes me wonder if allowing interdependencies in angular would not be
something to consider.

What do you think?

Regards,
Witold Szczerba

Igor Minar

unread,
Jun 3, 2011, 3:53:09 PM6/3/11
to ang...@googlegroups.com
I just skimmed through this thread, so apologies if I missed something.

Circular dependency in DI usually means that your (or our) code is not well structured/designed.

In this case, what you are trying to do does make sense, but angular doesn't provide you with the right apis to do it. I would go with your event bus approach for now, but we need to address the issue internally in angular by providing a way to handle typical errors (like flaky connection, overloaded server, etc) gracefully.

When we worked on the injector, we did consider the situation where there are circular dependencies. Our tests in chrome showed that a stack overflow error is thrown from the injector, which is typically a sufficient hint that there are circular dependencies present. We didn't realize that FF doesn't handle this situation well at all. So we should throw a nice error when this situation is detected as requested in the issue that Vojta created.

/i

--
You received this message because you are subscribed to the Google Groups "angular" group.

avi tshuva

unread,
Jul 7, 2013, 9:18:45 AM7/7/13
to ang...@googlegroups.com

Generally, the "service" and "DI" mechanisms of angular are two pieces of crap.  If there was lazy/smart/cached loading/disposition, there would be justification for their existence. Otherwise, it's best to do it like this and enjoy the power of pure JS and everything that comes with it:

var MyService = {

}
(or using prototype based classes)

function UserOfService() {
     MyService.method("bla");

Witold Szczerba

unread,
Jul 7, 2013, 9:44:18 AM7/7/13
to ang...@googlegroups.com

Years ago, most of the Java developers were saying the same things. Now they are in minority.
I was expecting the same will happen in JavaScript world, I mean, years will have to pass, but I am positively surprised.

Regards,
Witold Szczerba

--
You received this message because you are subscribed to the Google Groups "AngularJS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to angular+u...@googlegroups.com.

To post to this group, send email to ang...@googlegroups.com.
Visit this group at http://groups.google.com/group/angular.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Pawel Kozlowski

unread,
Jul 7, 2013, 9:54:00 AM7/7/13
to ang...@googlegroups.com
Hi Witold!

He, he - I hear what you are saying. I like to play the "show me your
tests" card with those people...
Unless I bump into someone who also claims that tests are "crap" / nonsense ...
Sight....

Cheers,
Pawel
--
AngularJS book:
http://www.packtpub.com/angularjs-web-application-development/book
Looking for bootstrap-based widget library for AngularJS?
http://angular-ui.github.com/bootstrap/

avi tshuva

unread,
Jul 7, 2013, 10:07:45 AM7/7/13
to ang...@googlegroups.com

:-)

My friend, as someone who wrote hundred of thousands (if not millions)  of lines of Java code, i think there is a huge misuse of programming patterns and frameworks. The herd syndrome is very dominant in enterprises  and software houses. What is wrote here is wrong there as well. The good programmers will probably always be a minority among the herds of programmers, thus your predication might be true, but not due to the fact these patterns are better, but rather the opposite :)

i hope that Angular will continue to mature and either remove the redundant structure or make them valuable by adding the features i mentioned. 

avi tshuva

unread,
Jul 7, 2013, 10:11:13 AM7/7/13
to ang...@googlegroups.com

Sorry pal, but you could do these mock logic around vanilla JS objects as well. It is all based on simple reflection, after all. There's no magic there.
Reply all
Reply to author
Forward
0 new messages