javascript AngularJS:$watch 未在更改时触发(包含页面的 $scope 问题)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16158474/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 03:30:38  来源:igfitidea点击:

AngularJS : $watch not triggered on change ($scope issue with include page)

javascriptangularjs

提问by Bye

Html:

网址:

Filter: 
<input type="radio" ng-model="Type" value="Rear"> Rear
<input type="radio" ng-model="Type" value="Front"> Front
<br>
Select:
<name-value-select entry="entry" field="axleType" options="filterTypes"></name-value-select>

Directive:

指示:

  .directive('nameValueSelect', function () {
    return {
    restrict: "E",
    scope: {
        entry: "=",
        field: "@",
        options: "=",
        onInputChange: "&"
    },
    controller: function ($scope) {

        $scope.onChange = function () {
            console.log("selected changed");
            $scope.entry.type = $scope.option.value;
            $scope.onInputChange();
        };

        var getItemForValue = function (value) {
            var item = null;
            $scope.options.forEach(function (_option) {
                if (_option.value == value) {
                    item = _option;
                }
            });
            return item;
        };

        $scope.$watch("entry", function () {
            console.log("entry changed");
            $scope.option = getItemForValue($scope.entry[$scope.field]);
        }, true);

    },
    template: '<select ng-model="option" ng-options="o.Description for o in options" ng-change="onChange()"><option value="" selected="selected">Please Select </option></select>'

};  })

Controller:

控制器:

$scope.Description = '';
$scope.entry = {Description: ''};
$scope.Type = 'Front';
$scope.entry.type = '';


$scope.$watch('Type', function (Type) {
    $scope.filterTypes = [];
    $scope.axleTypes = new API.GetAxleTypes(function () {
        angular.forEach($scope.axleTypes, function(axleType) {
            if (axleType.Type == Type) {
                this.push(axleType);
            }
            // if(!$scope.$$phase) $scope.$digest(); // tried this, does not have any affect
    }, $scope.filterTypes); // '}, $scope.filterTypes, true)'--> did nothing for me here     
}); 
    $scope.filterTypes.sort(function (a, b) {
        return a.Description.localeCompare(b.Description);
    });
}); // '}, true)' --> did nothing for me here

Problem:

问题:

The custom select control does initially populate with items of Type 'Front', and would also populate with 'Rear' if I made Rear the default.

自定义选择控件最初填充类型为“Front”的项目,如果我将 Rear 设为默认值,也会填充“Rear”。

However, when toggling between radio buttons, the watch function is not called and the select does not populate with items meeting newly selected filter type.

但是,在单选按钮之间切换时,不会调用 watch 函数,并且 select 不会填充符合新选择的过滤器类型的项目。

////////////////////////////////////////////////////// Narrowed Down the Problem://///////////////////////////////////////////////////

////////////////////////////////////////////////// //// 缩小问题范围://////////////////////////////////////// ///////////

When moving this to a stand alone project, I discovered my code above works :-(

将其移至独立项目时,我发现上面的代码有效:-(

Here is the original select NOT as a custom directive:

这是原始的 select NOT 作为自定义指令:

    <select ng-model="$parent.selectedFrontAxle" ng-options="axleType.Description for axleType in axleTypes | filterByType: 'Front' | orderBy:'Description'"  id="frontAxles" class="formRequire" required>            
        <option value="" selected>Select Axle Type ..</option>  
    </select>

Here is the select control as a custom directive:

这是作为自定义指令的选择控件:

  <name-value-select   entry="entry" field="axleType" options="filterTypes"></name-value-select>

Clearly this is the issue: "ng-model="$parent.selectedFrontAxle"

显然这是问题所在:"ng-model="$parent.selectedFrontAxle"

If I take the directive "name-value-select" and move it to the parent page, in lieu of a include statement, it works.

如果我采用指令“名称-值-选择”并将其移动到父页面,而不是包含语句,它就可以工作。

But that is not what I want. So I need to leave it in the include page.

但这不是我想要的。所以我需要把它留在包含页面中。

Yet, if I take the ng-model="$parent.selectedFrontAxle"and add it to ANY element on the include page, nothing works.

然而,如果我采用ng-model="$parent.selectedFrontAxle"并将其添加到包含页面上的任何元素,则没有任何效果。

Any with no ng-model="$parent.selectedFrontAxle", still nothing works.

任何没有ng-model="$parent.selectedFrontAxle" 的,仍然没有任何效果。

I can see the issue is scope yet I do not see how to correct it.

我可以看到问题是范围,但我不知道如何纠正它。

Ughh!!! Still no solution.

呃!!!还是没有解决办法。

//
//
////////////////////////////////////////////////////////////// Mark Rajcok//////////////////////////////////////////////////////////////

//
//
////////////////////////////////////////////// /////////////// 马克·拉杰科克//////////////////////////////// /////////////////////////////

As always thanks for your help. Although, I do not think I made myself clear on point 2 when I said I had scenarios where I wanted the code to pre-filter and not the user. I figured it out though. So for anyone looking to abstract out controls via a directive, and, as in this case, where a filtering process is performed both by the user and in code, here is the plunker solution:

一如既往地感谢您的帮助。虽然,当我说我希望代码预过滤而不是用户预过滤时,我认为我没有在第 2 点上说清楚。不过我想通了。因此,对于任何希望通过指令抽象出控件的人来说,在这种情况下,过滤过程由用户和代码执行,这里是 plunker 解决方案:

http://plnkr.co/edit/tnXgPKADfr5Okvj8oV2S?p=preview

http://plnkr.co/edit/tnXgPKADfr5Okvj8oV2S?p=preview

回答by Mark Rajcok

In this plunker, I reworked the code to allow the user to select either "Rear" or "Front", and the select list will dynamically update. The selected value is available as $scope.selected.itemon the MainCtrl scope. (There was a lot of unnecessary code in the directive that I removed. onChange handlers aren't needed when ng-model is being used.)

这个 plunker 中,我重新编写了代码以允许用户选择“后”或“前”,并且选择列表将动态更新。所选值$scope.selected.item在 MainCtrl 范围内可用。(在我删除的指令中有很多不必要的代码。使用 ng-model 时不需要 onChange 处理程序。)

<name-value-select selected-item="selected.item" options="filterTypes">
</name-value-select>

app.directive('nameValueSelect', function () {
    return {
        restrict: "E",
        scope: {
            selectedItem: "=",
            options: '='
        },
        template: ...

Since ng-include creates a child scope, all primitive properties were converted to object properties, so that JavaScript prototypal inheritance will work as expected (i.e., when values are changed, $scope objects defined in the MainCtrl get changed rather than new primitive properties being created on local scopes).

由于 ng-include 创建了一个子作用域,所有原始属性都被转换为对象属性,因此 JavaScript 原型继承将按预期工作(即,当值更改时, MainCtrl 中定义的 $scope 对象会被更改,而不是新的原始属性被在本地范围内创建)。

I believe the plunker referenced above will satisfy your "point 1".

我相信上面提到的 plunker 会满足您的“第 1 点”。

I will now look at creating a plunker for "point 2"... (I'll add a comment when I update this answer).

我现在将考虑为“第 2 点”创建一个 plunker ......(我会在更新此答案时添加评论)。



This plunkerfilters the select list in the directive:

这个plunker过滤指令中的选择列表:

<name-value-select selected-item="selected.item" choice="choice.type"
 options="myTypes"></name-value-select>

app.directive('nameValueSelect', function () {
    return {
        restrict: "E",
        scope: {
            selectedItem: "=",
            choice:  "=",
            options: "=",
        },
        controller: function($scope)  {
          $scope.$watch('choice', function (selectedType) {
            $scope.selectedItem = '';  // clear selection
            $scope.filterTypes = [];
            angular.forEach($scope.options, function (type) {
              if (type.Type === selectedType) {
                this.push(type);
               }
            }, $scope.filterTypes);
            $scope.filterTypes.sort(function (a, b) {
              return a.Description.localeCompare(b.Description);
            });
          });
        },
        template: '<select ng-model="selectedItem" ' +
         'ng-options="o.Description for o in filterTypes">' +
         '<option value="" selected="selected">Please Select</option>' +
         '</select>'
    };
});

回答by jpmorin

I do not exactly understand your request, but I just assume that you want to dynamically change the content of a select according to the value a radio button group. The select is filtered and sorted and you may choose which field of the data to display.

我不完全理解您的要求,但我只是假设您想根据单选按钮组的值动态更改选择的内容。选择被过滤和排序,您可以选择要显示的数据字段。

You may have a look at this Plunkerwhich contains a demo with few modifications.

您可以查看这个Plunker,其中包含一个经过少量修改的演示。

HTML

HTML

Filter: 
<input type="radio" ng-model="filter.type" value="rear"> Rear
<input type="radio" ng-model="filter.type" value="front"> Front
<br />
Field:
<select ng-model="filter.field" ng-options="item for item in fields"></select>
<br />
Select:
<select ng-model="entry" ng-options="item[filter.field] for item in entries | filter:type=filter.type | orderBy:filter.field">
  <option value="">Default</option>
</select>
<br />
Selected: <span>{{entry}}</span>

<h2>Alternative</h2>
Filter: <span>{{filter}}</span>
<br />
<select ng-model="altentry" ng-options="item[filter.field] for item in altentries | orderBy:filter.field">
  <option value="">Default</option>
</select>
<br />
Selected: <span>{{altentry}}</span>

JS

JS

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

app.controller('MainCtrl', function($scope) {
  $scope.name = 'World';

  /* Default filter values */
  $scope.filter = {
    type: 'rear',
    field: 'name'
  };

  /* List of fields */
  $scope.fields = [
    "id", "type", "name"
  ];

  /* Data coming from an API */
  $scope.entries = [
    {"id":1, "type":"rear", "name":"Rear Part 01"},
    {"id":2, "type":"rear", "name":"Rear Part 03"},
    {"id":3, "type":"rear", "name":"Rear Part 05"},
    {"id":4, "type":"rear", "name":"Rear Part 02"},
    {"id":5, "type":"rear", "name":"Rear Part 04"},
    {"id":6, "type":"front", "name":"Front Part 01"},
    {"id":7, "type":"front", "name":"Front Part 03"},
    {"id":8, "type":"front", "name":"Front Part 05"},
    {"id":9, "type":"front", "name":"Front Part 02"},
    {"id":10, "type":"front", "name":"Front Part 04"},
  ];

  /* Simulate different ajax return */
  $scope.$watch('filter.type', function(newval, oldval) {
    if (newval || newval !== oldval) {
      $scope.altentries = [];
      angular.forEach($scope.entries, function(v,k) {
        if (v.type === $scope.filter.type) {
          $scope.altentries.push(v);
        }
      });
    }
  });

  /* Monitoring any selected value */
  $scope.$watch('entry', function(newval, oldval) {
    console.log('entry:', oldval, newval);
  }, true);

  $scope.$watch('altentry', function(newval, oldval) {
    console.log('altentry:', oldval, newval);
  }, true);

});