【问题标题】:Proper way to initialize angular services using the controllers使用控制器初始化角度服务的正确方法
【发布时间】:2014-03-17 17:05:54
【问题描述】:

我来自静态类型的面向对象背景 (C#),并且对 Angular 和 Javascript 不熟悉。我正在尝试使用 Angular 和 JQueryMobile 构建一个应用程序,并且面临服务的行为不像单例的情况 - 即即使它们已在一个控制器中初始化一次,服务中的属性也不会存储它们的任何状态当传递到另一个控制器或服务时设置为。此外,当我尝试调试如下所述的代码时,我遇到了一些意外行为:

我的设置:

  • 我正在使用 JQueryMobile 单页模板,该模板将应用程序的所有页面作为 div 在同一个 html 页面上,并且使用“href="#DivName" 模式进行重定向
  • 应用的每个 div(page) 都有一个关联的控制器和服务
  • 控制器会根据要求注入多个服务,并且注入的服务应保持在其他控制器中初始化时的状态。
  • 我没有使用路由和模板 - 整个 html 都在一个页面中

相关代码大纲如下:

HTML:

<div data-role="page" id="PageA" data-ng-controller="PageAController">      

<!-- page content and UI elements-->    

</div>


<div data-role="page" id="PageB" data-ng-controller="PageBController">

<!-- page content and UI elements-->    

</div>

服务:

var App = angular.module('App', []);

App.service('serviceA', function () {

    var propA;

    //objectX is an array of objects with each object containing a hashtable and some other properties 

    propA = [{},{}];  //hardcoded value for objectX gets returned properly

    return {

        initialize: function(objectX){          

            propA = objectX; // Dynamically initialized objectX value is returned as "undefined" by the getPropA function below
        },                 

        getPropA: function () {               

            return propA;
        }
    };
});

App.service('serviceB', function (serviceA) {

    var propB;

    return {

        initialize: function(){         

            propB = serviceA.getPropA();    //THIS DOES NOT WORK AS THE VALUE RETURNED IS UNDEFINED     
        },                 

        processPropB: function () {               

            //logic for processing of propB
        }
    };
});

控制器:

App.controller('ControllerA', ['$scope', 'ServiceA',
function ($scope, ServiceA) {    

    $scope.UITriggeredAction = function(){          
        ServiceA.initialize(objectX);
    };    

}]);

//This controller gets invoked at a later point during the user interaction where ServiceA is supposed to be initialised and injected


App.controller('ControllerB', ['$scope', 'ServiceB', 'ServiceA',
function ($scope, ServiceB, ServiceA,) {    

    var returnedPropA = ServiceA.getPropA(); //THIS RETURNS UNDEFINED TOO

    //process logic to use serviceB depending on valu of returnedPropA

}]);

问题:

  • 在上面的 serviceA 代码中,如果我在返回块之前通过硬编码来初始化 propA 的值,则在执行 getPropA() 方法时会返回该值。请解释这是如何工作的。

  • 我还想知道在 Angular 中调用 javascript 代码的顺序 - 我的理解是,当相关页面在浏览器中加载并且控制器代码依次调用时,Angular 运行时会调用适当的控制器函数服务方法。但是,如果我在不同的控制器和/或服务中有警报并加载页面,即使不应该调用页面或控制器/服务,警报也会立即显示。更奇怪的是,当实际的控制器或服务方法被执行时,警报并没有被执行。似乎所有控制器都在页面加载时执行。

请让我知道如何设置我的代码,以便我可以在不同的控制器中传递服务,而一旦在执行期间的任何地方初始化或更新,它们就不会丢失它们的状态。

谢谢, 山塔努

编辑:

我认为答案就在我的第二个问题中。对我来说 - 代码执行的控制流程并不清楚。以下是我认为的问题:

基本上,两个 Angular 控制器都在页面加载时由 Angular 运行时执行,而不是按照它们在应用程序中加载和使用的顺序执行 - 尽管这可能只是 JQMobile 的事情。

当对用户界面操作进行赋值操作时,服务中的相应变量设置成功。但是角度运行时不会重新评估所有控制器以确保新分配反映在所有控制器中。

所以另一个控制器继续返回它在初始化时设置的值 - 未定义。

但是,如果变量是在页面加载时设置的——那就是在初始化时返回的值与所需的值一起发生并且一切正常。

根据要求,我还在下面创建了一个简单的 plunk,以使用 cmets 显示上述要点: http://plnkr.co/edit/yBOL0P3ycsoNzVrydaMs

作为问题的解决方案,以下是我认为应该可行的方法:

  • 以某种方式确保控制器仅在需要时初始化,而不是在页面加载时一次性初始化,以便可以根据前面操作中设置的状态进行初始化。我认为可以通过模板和路由来实现,这样只有在所需的路由和模板被称为 foe 时才会调用控制器。我不确定这是不是它的工作方式。

  • 第二个选项是在设置控制器中发布事件并在需要时订阅它们以保持同步。这似乎是一种更好的方法。

如果可行,我会确认并发布答案。

请告诉我你的想法。

抱歉,帖子太长了:)

【问题讨论】:

  • 这是一个很长的问题。也许尝试将其划分为不同的问题,并让 jsFiddle/plnkrs 来展示您的问题。
  • 我怀疑你可能有错字。 JavaScript 区分大小写,因此 serviceA 与 ServiceA 不同。
  • 这是直接复制/粘贴吗? function ($scope, ServiceB, 'ServiceA',) {。不应该是:function ($scope, ServiceB, ServiceA) {
  • @TheRocketSurgeon - 这不是直接复制/粘贴,而是已更正的错字。还添加了一个 plunker 示例 - 请查看。谢谢。
  • 不确定它是否有帮助,但我为类似的问题做了一个代码笔:codepen.io/yakovkhalinsky/pen/LeirK

标签: javascript angularjs jquery-mobile


【解决方案1】:

这是 javascript 异步特性的一个示例。基本上所有内容都在页面加载时实例化,因此可能会或可能不会填充该值。 Angular 为我们提供的一个很棒的解决方案是使用一个 promise - $q - 或者我们可以使用 $timeout 进行轮询,但这只是草率。

这是您的代码,具有令人敬畏的救援承诺!

首先是plunker的链接:http://plnkr.co/edit/OCgL8jATTdScRbYubt9W

一些代码看看:

控制器:

var app = angular.module('plunker', []);

app.controller('ControllerA', ['$scope','serviceA',function($scope, serviceA) {

  $scope.initializeA = function(){          
        serviceA.initialize("Chris");
    };  
}]);


app.controller('ControllerB', ['$scope', 'serviceB', 'serviceA', function($scope, serviceB, serviceA) {

  $scope.initializeB = function(){          

        serviceA.getPropA().then(function(data) {
          alert("ControllerB : " + data);
        });

        serviceB.initialize();

        serviceB.processPropB();

    };

    /////////////////////////////    
    //Controller Initialization//
    /////////////////////////////

        serviceA.getPropA().then(function(data) {
          alert('now i have the data: ' + data)
        });

        serviceB.initialize();

        serviceB.processPropB();

}]);

他们现在设置为在解决承诺时处理他们的代码。

现在我们设置承诺:

服务:

服务A:

 app.service('serviceA', function ($q) {

    var 
      propA,
      deferred = $q.defer()
    ;

    return {

        initialize: function(name){          

            propA = name; 
            deferred.resolve(propA);
        },                 

        getPropA: function () {               

            return deferred.promise;
        }
    };
});

服务B:

app.service('serviceB', function (serviceA, $q) {

    var 
      propB,
      deferred = $q.defer()
    ;

    return {

        initialize: function(){         

            serviceA.getPropA().then(function(data) {
              propB = data;
              deferred.resolve();
            }); 
        },                 

        processPropB: function () {               
            deferred.promise.then(function() {
                 alert("ServiceB: " + propB);
            })

        }
    };
});

对于刚开始使用 JS 和/或 Angular 的人来说,这可能是一个非常令人困惑的问题,但是因为我们拥有对付这种情况的强大武器 - “$q!” - 一旦我们学会正确使用它,我们的异步噩梦就会再次变成蓬松的云彩和蝴蝶。

【讨论】:

  • 您的解决方案似乎确实有效,但就像我在问题中所说的那样 - 我是 javascript 新手,所以我还不太明白 - 我正试图围绕承诺的概念和方式控制流与他们一起工作。当我完全理解它时,我会将其标记为答案。感谢您花时间回答这个问题。
  • Promise 是一种表示暂停并且不再继续的方式,因为您依赖于先前的函数返回,当该函数返回时,然后继续。
  • 另外不要忘记阅读语言的异步行为和事件队列。 javascript.info/tutorial/events-and-timing-depth
  • 谢谢。我想我现在可以合理地理解承诺了。但是你能告诉我在 $scope 对象上使用 Promise 与 pub/sub 事件(看起来更容易和更清晰)在控制器之间进行通信的区别/优势吗?
  • 对于控制器之间的通信,您基本上有 2 个选项 - 广播事件 (pub/sub) 和服务。在我正在处理的当前项目(网上商店)中,我们都在使用它们。它们都有各自的优势,并在您的应用程序中发挥独特的作用。找到那个角色真的取决于你。就承诺而言,它们在(主要)我上面提到的两者的服务方面发挥着至关重要的作用。由于我们通常在需要时使用服务,比如服务器请求数据,所以我们永远无法 100% 确定什么时候返回某些东西,而承诺可以帮助我们赢得这场战斗。
【解决方案2】:

我不知道这是否解决了问题,但我认为您正在实例化服务,就好像它是一个工厂一样。

工厂返回并反对,就像你正在做的那样......

但是服务应该使用“this”类型的语法:

App.service('serviceA', function () {
   var somePrivateProperty;
   this.propA = [{},{}];      // public property
   this.initalize = function(objectX) { 
       this.propA = objectX
    }
   this.getPropA = function() { return this.propA }
   // although, your controller which Injects this service, 
   // could access propA directly without the getter

   var somePrivateMethod = function() {}


  });

服务与工厂的示例:http://jsfiddle.net/thomporter/zjFp4/1/

【讨论】:

  • 谢谢 - 我会试试这个并在这里更新结果。但我想我已经确定了问题所在,我很快就会在这里发布。
  • 使用“this”类型语法没有解决问题。如答案中所述,这是使用承诺或事件解决的。感谢您抽出时间帮助我@timh。 :)
【解决方案3】:

所以我知道这已经有一年了,但这里有一个更简单的实现。

单例模式的 Angular 服务实现:

app.service('serviceA', function () {

var privateProperty;

this.publicObject = {};

this.publicObject.sharedProperty = "Hardcoded value";

this.initialize = function(value){
  this.publicObject.sharedProperty = value; 
}

});

首先,通过使用 this.Service 语法,您可以直接在原型上分配变量/函数,并且该服务的行为类似于“服务”。而且感觉更有棱角!

其次,通过将属性移动到一个对象上,它维护了对象的范围,以便它可以被多个控制器更改,因为我相信 Angular 控制器通过引用存储对象,并通过值存储特定的变量实例。

所以如果你的控制器 A 包含:

$scope.variableByValue = serviceA.publicObject.sharedProperty;

$scope.variableByValue = "来自 A 的新值"

比控制器B:

alert("serviceA.publicObject.sharedProperty") //这不会是“来自 A 的新值”

重组服务的Plunker:

http://plnkr.co/edit/3Ux4y37OtXjHsFLv6MSY?p=preview

【讨论】:

    猜你喜欢
    • 2016-03-07
    • 1970-01-01
    • 1970-01-01
    • 2015-04-05
    • 2016-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多