14

我使用“范围”创建了一个带有绑定的指令。在某些情况下,我想绑定一个常量对象。例如,使用 HTML:

<div ng-controller="Ctrl">
    <greeting person="{firstName: 'Bob', lastName: 'Jones'}"></greeting>
</div>

和 JavaScript:

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

app.controller("Ctrl", function($scope) {

});

app.directive("greeting", function () {
    return {
        restrict: "E",
        replace: true,
        scope: {
            person: "="
        },
        template:
        '<p>Hello {{person.firstName}} {{person.lastName}}</p>'
    };
});

虽然这可行,但它也会导致 JavaScript 错误:

Error: 10 $digest() iterations reached. Aborting!

(小提琴演示问题)

绑定常量对象而不导致错误的正确方法是什么?

4

6 回答 6

11

这是我根据@sh0ber 的回答提出的解决方案:

实现自定义link功能。如果属性是有效的 JSON,那么它是一个常量值,所以我们只评估一次。否则,照常观察和更新值(换句话说,尝试表现得像一个=绑定)。scope需要设置为true确保分配的值仅影响指令的此实例。

(关于 jsFiddle 的示例)

HTML:

<div ng-controller="Ctrl">
    <greeting person='{"firstName": "Bob", "lastName": "Jones"}'></greeting>
    <greeting person="jim"></greeting>
</div>

JavaScript:

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

app.controller("Ctrl", function($scope) {
    $scope.jim = {firstName: 'Jim', lastName: "Bloggs"};
});

app.directive("greeting", function () {
    return {
        restrict: "E",
        replace: true,
        scope: true,
        link: function(scope, elements, attrs) {
            try {
                scope.person = JSON.parse(attrs.person);
            } catch (e) {
                scope.$watch(function() {
                    return scope.$parent.$eval(attrs.person);
                }, function(newValue, oldValue) {
                    scope.person = newValue;
                });
            }   
        },
        template: '<p>Hello {{person.firstName}} {{person.lastName}}</p>'
    };
});
于 2013-06-06T12:35:39.847 回答
7

您收到该错误是因为 Angular 每次都在评估表达式。'=' 用于变量名。

这里有两种替代方法可以在没有错误的情况下实现相同的想法。

第一个解决方案:

app.controller("Ctrl", function($scope) {
    $scope.person = {firstName: 'Bob', lastName: 'Jones'};
});

app.directive("greeting", function () {
    return {
        restrict: "E",
        replace: true,
        scope: {
            person: "="
        },
        template:
        '<p>Hello {{person.firstName}} {{person.lastName}}</p>'
    };
});

<greeting person="person"></greeting>

第二种解决方案:

app.directive("greeting2", function () {
    return {
        restrict: "E",
        replace: true,
        scope: {
            firstName: "@",
            lastName: "@"
        },
        template:
        '<p>Hello {{firstName}} {{lastName}}</p>'
    };
});

<greeting2 first-name="Bob" last-Name="Jones"></greeting2>

http://jsfiddle.net/7bNAd/82/

于 2013-06-05T19:09:26.360 回答
4

另外一个选项:

app.directive("greeting", function () {
    return {
        restrict: "E",
        link: function(scope,element,attrs){
            scope.person = scope.$eval(attrs.person);
        },
        template: '<p>Hello {{person.firstName}} {{person.lastName}}</p>'
    };
});
于 2013-06-05T19:51:54.760 回答
2

这是因为如果您使用=范围字段链接的类型,则会观察属性值的更改,但会测试引用相等性 (with !==) 而不是深入测试相等性。内联指定对象字面量将导致 Angular 在访问属性以获取其值时创建新对象——因此,当 Angular 进行脏检查时,将旧值与当前值进行比较总是发出更改的信号。

克服这个问题的一种方法是修改 angular 的源,如下所述:

https://github.com/mgonto/angular.js/commit/09d19353a2ba0de8edcf625aa7a21464be830f02

否则,您可以在控制器中创建对象并在元素属性中按名称引用它:

HTML

<div ng-controller="Ctrl">
    <greeting person="personObj"></greeting>
</div>

JS

app.controller("Ctrl", function($scope)
{
    $scope.personObj = { firstName : 'Bob', lastName : 'Jones' };
});

另一种方法是在父元素的ng-init指令中创建对象,然后按名称引用它(但这个可读性较差):

<div ng-controller="Ctrl" ng-init="personObj = { firstName : 'Bob', lastName : 'Jones' }">
    <greeting person="personObj"></greeting>
</div>
于 2013-06-05T19:04:27.450 回答
0

我不是特别喜欢使用eval(),但如果你真的想让它与你提供的 HTML 一起工作:

app.directive("greeting", function() {
    return {
        restrict: "E",
        compile: function(element, attrs) {
            eval("var person = " + attrs.person);
            var htmlText = '<p>Hello ' + person.firstName + ' ' + person.lastName + '</p>';
            element.replaceWith(htmlText);
        }
    };
});
于 2013-06-05T19:23:08.137 回答
0

我有同样的问题,我通过在编译步骤中解析 json 解决了它:

angular.module('foo', []).
directive('myDirective', function () {
    return {
        scope: {
            myData: '@'
        },
        controller: function ($scope, $timeout) {
            $timeout(function () {
                console.log($scope.myData);
            });
        },
        template: "{{myData | json}} a is  {{myData.a}} b is {{myData.b}}",
        compile: function (element, attrs) {
            attrs['myData'] = angular.fromJson(attrs['myData']);
        }
    };
});

一个缺点是$scope控制器第一次运行时最初没有填充。

这是带有此代码的JSFiddle 。

于 2014-02-18T19:40:42.003 回答