简体   繁体   English

AngularJS手表无法在对象内部使用数组

[英]Angularjs watch not working with array inside of object

I have an object {Client:[],Employee:[],Product:[],Project:[],PayPeriod:[]} in which each array gets pushed and spliced by components through a two way binding. 我有一个对象{Client:[],Employee:[],Product:[],Project:[],PayPeriod:[]} ,其中每个数组都通过双向绑定被组件推送和拼接。 The main controller connects all 5 of the arrays and gives them to another component. 主控制器连接所有5个阵列,并将它们分配给另一个组件。 In said component I need to watch that binding but no matter what I do it does not work. 在所说的组件中,我需要注意该绑定,但是无论我做什么都行不通。 This is what I have now. 这就是我现在所拥有的。

$scope.$watch('ctrl.parameters', ctrl.Update(), true);

ctrl.Update(); ctrl.Update(); is a function and works. 是一种功能并且有效。
ctrl.parameters does get updated but does not trigger $watch. ctrl.parameters确实会更新,但不会触发$ watch。

It's a bit of a complicated so if you need anything explained butter I can. 这有点复杂,所以如果您需要任何可以解释的黄油,我可以。

ctrl.Update = function () {
        $.post("/TrackIt/Query.php?Type=getViaParams&EntityType="+ctrl.entity,{Params:ctrl.parameters},function(Data,Status){
            if(Status=="success"){
                if (Data.Success) {
                    ctrl.List = Data.Result.Entities;
                } else {
                    AlertService.Alert(Data.Errors[0],false,null);
                    SessionService.Session(function () {
                        ctrl.Update();
                    });
                }
                $scope.$apply();
            }else{
                AlertService.Alert("Something is up with the select options",false,null);
            }
        },'json');
    };

Edit 1 : 编辑1:
Par = {Client:[],Employee:[],Product:[],Project:[],PayPeriod:[]}
5 Components with two way binding = Par.X (these are what edit the parameters) 5种具有双向绑定的组件= Par.X(这些是编辑参数的内容)
1 Component with two way binding = Par (I need to watch the binding inside here) 1个具有双向绑定的组件= Par(我需要在此处查看绑定)

Edit 2 : 编辑2:

<script>
    TrackIT.controller('EntryController', function EntryController($scope, $http, AlertService, SessionService, DisplayService) {
        $scope.Parameters = {Client:[],Employee:[],Product:[],Project:[],PayPeriod:[]};
        $scope.Values = {};

    });
</script>
<style>
    entity-select{
        float: left;
        display: inline;
        padding: 0 5px;
    }
    #SelectParameters{
        float: left;
    }
</style>
<div ng-app="TrackIT" ng-controller="EntryController">
    <div id="SelectParameters">
        <entity-select entity="'Client'" ng-model="Values.Client" multi="true" ng-array="Parameters.Client"></entity-select>
        <entity-select entity="'Employee'" ng-model="Values.Employee" multi="true" ng-array="Parameters.Employee"></entity-select>
        <entity-select entity="'Product'" ng-model="Values.Product" multi="true" ng-array="Parameters.Product"></entity-select>
        <entity-select entity="'Project'" ng-model="Values.Project" multi="true" ng-array="Parameters.Project"></entity-select>
        <entity-select entity="'PayPeriod'" ng-model="Values.PayPeriod" multi="true" ng-array="Parameters.PayPeriod"></entity-select>
    </div>
    <br>
    <parameter-table entity="'Entry'" parameters="Parameters"></parameter-table>
</div>

TrackIT.component('entitySelect', {
templateUrl: "/Content/Templates/Select.html",
controller: function SelectController($scope, $http, AlertService, SessionService) {
    var ctrl = this;
    ctrl.Options = [];
    ctrl.Display = [];

    ctrl.Add = function () {
        var Display = {'Label':ctrl.Label(ctrl.ngModel),'Value':ctrl.ngModel};
        ctrl.ngArray.push(ctrl.ngModel);
        ctrl.Display.push(Display);
    };

    ctrl.Remove = function (Key) {
        ctrl.ngArray.splice(Key, 1);
        ctrl.Display.splice(Key, 1);
    };

    ctrl.$onInit = function() {
        $.post("/TrackIt/Query.php?Type=getSelectList&EntityType="+ctrl.entity,null,function(Data,Status){
            if(Status=="success"){
                if (Data.Success) {
                    ctrl.Options = Data.Result.Entities;
                    if(ctrl.ngModel==undefined){
                        if(ctrl.none){
                            ctrl.ngModel = "NULL"
                        }else{
                            ctrl.ngModel = angular.copy(ctrl.Options[0].Attributes.ID.Value.toString());
                        }
                    }
                } else {
                    AlertService.Alert(Data.Errors[0],false,null);
                }
                $scope.$apply();
            }else{
                AlertService.Alert("Something is up with the select options",false,null);
            }
        },'json');
    };

    ctrl.Label = function(Value) {
        for (var prop in ctrl.Options) {
            if(!ctrl.Options.hasOwnProperty(prop)) continue;
            if(ctrl.Options[prop].Attributes.ID.Value.toString()==Value.toString()){
                return ctrl.Options[prop].DisplayName;
            }
        }
    };

},
bindings: {
    entity:"<",
    multi:"<",
    none:"<",
    ngModel:"=",
    ngArray:"="
}
});

TrackIT.component('parameterTable', {
templateUrl: "/Content/Templates/BasicTable.html",
controller: function ParameterTableController($scope, $http, AlertService, SessionService, DisplayService) {
    var ctrl = this;
    ctrl.List = {};

    ctrl.Update = function () {
        $.post("/TrackIt/Query.php?Type=getViaParams&EntityType="+ctrl.entity,{Params:ctrl.parameters},function(Data,Status){
            if(Status=="success"){
                if (Data.Success) {
                    ctrl.List = Data.Result.Entities;
                } else {
                    AlertService.Alert(Data.Errors[0],false,null);
                    SessionService.Session(function () {
                        ctrl.Update();
                    });
                }
                $scope.$apply();
            }else{
                AlertService.Alert("Something is up with the select options",false,null);
            }
        },'json');
    };

    $scope.$watch('ctrl.parameters', ctrl.Update.bind(ctrl), true);

    ctrl.$onInit = function() {
        DisplayService.DisplayTrigger(function () {
            ctrl.Update();
        });
        ctrl.Update();
    }
},
bindings: {
    entity: "<",
    parameters: "="
}
});

There are two problems here. 这里有两个问题。

Problem 1: ctrl is not a property on the scope 问题1: ctrl不是作用域上的属性

After seeing the full controller code, I can see that ctrl is just an alias for this , the instance of the controller which will be published on the scope as $ctrl by default. 看完完整的控制器代码后,我可以看到ctrl只是this的别名,默认情况下,控制器的实例将在$ctrl Scope上发布到作用域中。 But you can avoid having to worry about what it is called by instead passing a function instead of a string to $scope.$watch() : 但是您可以不必担心调用什么,只需将函数而不是字符串传递给$scope.$watch()

// ES5
$scope.$watch(function () { return ctrl.parameters; }, ctrl.Update, true);
// ES6/Typescript/Babel
$scope.$watch(() => ctrl.parameters, ctrl.Update, true);

It's all functions to Angular Angular的所有功能

You may not be aware that as far as Angular is concerned, it is always calling a function for each watch to get the value to compare. 您可能并不知道,就Angular而言,它总是为每个手表调用一个函数以获取要比较的值。 When you pass a string to $scope.$watch() , Angular uses $parse to create a function from that expression. 当您将字符串传递给$scope.$watch() ,Angular将使用$parse从该表达式创建函数。 This is how Angular turns strings into executable code in bindings, expressions, and so on. 这就是Angular将字符串转换为绑定,表达式等中的可执行代码的方式。

The function that gets created takes in a single parameter, which is the "context" to evaluate the expression on. 创建的函数采用单个参数,即用于评估表达式的“上下文”。 You can think of this as which scope to use. 您可以将其视为要使用的范围。

When you pass a function to $scope.$watch() as the first parameter, you effectively save Angular having to create a function for you from the string. 当您将函数作为第一个参数传递给$scope.$watch() ,可以有效地节省Angular不得不从字符串中为您创建函数的麻烦。

Problem 2: the way you specify the watch listener function 问题2:您指定监视方法的方式

Your ctrl.Update() function is just a function that you want run whenever ctrl.parameters changes. ctrl.Update()函数只是要在ctrl.parameters更改时运行的函数。

What you have said in your code of $scope.$watch('ctrl.parameters', ctrl.Update(), true); 您在$scope.$watch('ctrl.parameters', ctrl.Update(), true);代码中所说的内容$scope.$watch('ctrl.parameters', ctrl.Update(), true); is: 是:

Do a deep watch (watch changes to any property) on ctrl.parameters , and when it changes, call the result of calling ctrl.Update() , which will be a jQuery promise, not a function. ctrl.parameters进行深入监视(监视任何属性的更改),并在更改时,调用ctrl.Update()的结果,这将是jQuery的承诺,而不是函数。

Instead, you want to pass the ctrl.Update function itself as the second parameter to $scope.$watch() , so it gets called when a change is detected. 相反,您想将ctrl.Update函数本身作为第二个参数传递给$scope.$watch() ,以便在检测到更改时调用它。 To do that, just pass ctrl.Update instead of ctrl.Update() : 为此,只需传递ctrl.Update而不是ctrl.Update()

$scope.$watch('ctrl.parameters', ctrl.Update, true);

A Note of Caution 注意事项

Using ctrl.Update in this particular case will work, because there is no use of this inside that function. 在这种特殊情况下,可以使用ctrl.Update ,因为该函数内部没有使用this功能。 For others looking at this answer, note that when you pass a function in this way, the this binding (the "context") is not maintained as ctrl as you might expect. 对于其他正在看此答案的用户,请注意,当您以这种方式传递函数时, this绑定(“上下文”)将不会像您期望的那样被保持为ctrl To get around this, use ctrl.Update.bind(ctrl) , or just wrap it in a function so it gets called with the correct context: $scope.$watch('ctrl.parameters', function () { ctrl.Update() }, true); 要解决此问题,请使用ctrl.Update.bind(ctrl) ,或将其包装在函数中,以便使用正确的上下文进行调用: $scope.$watch('ctrl.parameters', function () { ctrl.Update() }, true); .

Use deep/value watches sparingly 谨慎使用深度/价值手表

You should be very sparing in your use of deep watches in an Angular app (also known as value watches). 在Angular应用程序中使用深表(也称为超值手表)时,您应该非常谨慎。 The reason is that it is a very expensive operation for big objects, as Angular has to do a deep comparison of the object on every digest cycle - traversing through every single property on the entire object, and then, if there is a change, making a deep clone of the object, which again requires traversing every single property to make a completely separate copy to compare against next time. 原因是对于大型对象而言这是一个非常昂贵的操作,因为Angular必须在每个摘要循环中对对象进行深入的比较-遍历整个对象的每个属性,然后进行更改(如果有更改)对象的深层克隆,这再次需要遍历每个单个属性以制作完全独立的副本以与下一次进行比较。

You can think of a deep watch on an object with n properties as being the equivalent of n shallow/reference watches. 您可以将对具有n个属性的对象的深度监视视为等同于n个浅表/参考监视。

I have a feeling that may be a scarily large number in your situation. 我觉得在您的情况下人数可能很多。

I think the problem is that your watch statement is incorrect. 我认为问题在于您的监视语句不正确。 The second parameter to $watch must be a function. $ watch的第二个参数必须是一个函数。 The following should work: 以下应该工作:

$scope.$watch('ctrl.parameters', ctrl.Update.bind(ctrl), true);

Note the use of bind to ensure the this parameter is set appropriately. 请注意使用bind来确保正确设置this参数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM