web-dev-qa-db-fra.com

Problèmes de dépendance circulaire et OOP dans AngularJS

AngularJS + OOP est une fonctionnalité un peu sexy à utiliser

Bonjour, J'utilise avec succès OOP avec AngularJs depuis déjà un certain temps (a commencé avec angularjs avec l'héritage oop en action ), l'approche fournie vous permet de définir vos classes. as angular services, que vous pourrez ensuite étendre ou hériter de cette manière:

Application.factory('AbstractObject', [function () {
    var AbstractObject = Class.extend({
        virtualMethod: function() {
           alert("Hello world");
        },
        abstractMethod: function() { // You may omit abstract definitions, but they make your interface more readable
           throw new Error("Pure abstract call");
        }
    });

    return AbstractObject; // You return class definition instead of it's instance
}]);

Application.factory('DerivedObject', ['AbstractObject', function (AbstractObject) {
    var DerivedObject = AbstractObject.extend({
        virtualMethod: function() { // Shows two alerts: `Hey!` and `Hello world`
            alert("Hey!");

            this._super();
        },
        abstractMethod: function() {
            alert("Now I'm not abstract");
        }
    });

    return DerivedObject;
}]);

Plunker: http://plnkr.co/edit/rAtVGAsNYggBhNADMeoT

utiliser l'approche décrite vous donne la possibilité de définir des classes qui s'intègrent parfaitement à l'infrastructure angular. Vous obtenez toutes sortes de fonctionnalités intéressantes à partir de deux mondes - OOP et AngularJs L’injection de dépendance est gratuite pour vos classes et simplifie vos classes. Elle permet de mettre beaucoup de code de contrôleur standard dans une classe de base qui peut être réutilisée ultérieurement.

Toutefois

L’infrastructure d’AngularJs empêche la propagation de ses ailes à 100%. Le problème se produit lorsque vous essayez de définir des définitions de classe récursives (agrégation récursive), par exemple, vous avez deux définitions de classe comme Blog et Tag.

Application.factory('Blog', ['Tag', function (Tag) {
    var Blog = Class.extend({
        tags: function() {
            return this.tags;
        }
    });

    return Blog;
}]);

Application.factory('Tag', ['Blog', function (Blog) {
    var Tag = Class.extend({
        Blogs: function() {
           return this.blogs;
        }
    });

    return Tag;
}]);

Cela ne fonctionnera pas parce que Blog et Tag se font eux-mêmes référence, ce qui entraîne une dépendance circulaire.

P.S

La dernière chose, j'ai trouvé une solution un peu laide qui résout mon problème dans mon cas spécifique mais ne fonctionne pas en général et comme je l'ai dit, ce n'est pas joli:

Application.factory('BlogNamespace', [function () {
    var Blog = Class.extend({
        tags: function() {
            return this.tags;
        }
    });

    var Tag = Class.extend({
        Blogs: function() {
           return this.blogs;
        }
    });

    return {
        Tag: Tag,
        Blog: Blog
    };
}]);

Question

Le correctif ci-dessus ne fonctionnera pas car les espaces de noms peuvent également faire l'objet d'une dépendance circulaire. Cela signifie que ce n'est pas une solution au problème décrit, mais plutôt un problème plus profond maintenant.

Des suggestions sur la façon dont il est possible de résoudre le problème décrit dans un cas général?

57
Lu4

Une dépendance circulaire est toujours le signe d'un mélange de préoccupations , ce qui est une très mauvaise chose. Miško Hevery, l'un des auteurs d'AngularJS, explique une solution de Nice sur son blog génial . En bref, vous avez probablement un troisième service caché quelque part, qui est la seule partie de votre code réellement nécessaire aux deux autres.

66
Blackhole

Je réponds à ma propre question simplement parce que j'ai trouvé un moyen technique de résoudre le problème sur lequel j'ai posté à l'origine. Mais avant cela, je vous encourage fortement à utiliser la suggestion de Blackhole car elle permet de résoudre un ensemble plus large de problèmes généralement causés par une mauvaise architecture. Préférez s'il vous plaît son approche d'abord, et revenez à la version actuelle au cas où vous savez ce que vous faites.

Alors, voici:

Vous pouvez utiliser $injector service et injecte les définitions requises au moment de l'exécution, ce qui est légal du point de vue technique, mais toujours selon this post (difficile d'imaginer qu'il soit écrit en 2008), cela ressemble à un magie noire, faites-le et cela vous frappera:

Application.factory('Blog', ['$injector', function ($injector) {
    var Tag = $injector.get('Tag'); // Here is your tag

    ...    
}]);

Application.factory('Tag', ['Blog', function (Blog) {
    ...
}]);

Modifier

Il s'est avéré que l'approche actuelle est un exemple de modèle de localisateur de service, qui est IoC Antipattern.

54
Lu4

DERNIER RECOURS: PAS ENCOURAGÉ

Dans mon cas, le meilleur moyen de contourner un problème de dépendance circulaire comme celui-ci en angulaire est de déclencher des appels de fonction via $rootScope _ émissions. L'autre service peut alors écouter cette émission et réagir avec l'appel de fonction souhaité. Ce n'est peut-être pas la solution la plus élégante, mais dans certains cas où, de toute façon, l'interaction entre les services est principalement unidirectionnelle, il peut s'agir d'une solution de rechange raisonnable. (notez que cela permet également aux valeurs de retour d'être renvoyées à la fonction de diffusion via des rappels uniquement)


Un pseudo-exemple de ceci serait:

angular.module('myApp').factory('service1', ["$rootScope",
  function($rootScope) {
    function func1() {
      // do something
    }
    $rootScope.$broadcast("callFunc2"); // calls func2 from service 1

    return {
      func1: func1
    }
  }
]);
angular.module('myApp').factory('service2', ["service1", "$rootScope",
  function(service1, $rootScope) {
    function func2() {
      // do something
    }
    service1.func1();  // calls func1 from service 2
    $rootScope.on("callFunc2", func2);
  }
]);
1
Lars Frölich