web-dev-qa-db-ja.com

AngularJS:定数オブジェクトをディレクティブにバインドする方法

「スコープ」を使用してバインディングを使用してディレクティブを作成しました。場合によっては、定数オブジェクトをバインドしたいことがあります。たとえば、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!

(問題を示すフィドル)

エラーを発生させずに定数オブジェクトをバインドする正しい方法は何ですか?

14

@ sh0berの回答に基づいて、私が思いついた解決策は次のとおりです。

カスタムlink関数を実装します。属性が有効なJSONである場合、それは定数値であるため、一度だけ評価します。それ以外の場合は、値を通常どおりに監視および更新します(つまり、=バインディングとして動作するようにしてください)。割り当てられた値がディレクティブのこのインスタンスにのみ影響するようにするには、scopetrueに設定する必要があります。

(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>'
    };
});
11

Angularは毎回式を評価しているため、このエラーが発生します。 '='は変数名用です。

エラーなしで同じ考えを実現するための2つの代替方法があります。

最初の解決策:

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>

2番目の解決策:

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/

7
martinpaulucci

別のオプション:

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>'
    };
});
4
Dan

これは、スコープフィールドリンクの=タイプを使用する場合、属性値は変更について監視されますが、同等性について深くテストされるのではなく、参照の同等性についてテストされます(!==を使用)。オブジェクトリテラルをインラインで指定すると、値を取得するために属性にアクセスするたびにangularが新しいオブジェクトを作成します。したがって、angularがダーティチェックを行う場合、古い値を現在の値と比較すると、常に変更が通知されます。

これを克服する1つの方法は、ここで説明するように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>
2
mirrormx

私は同じ問題を抱えていました、私はコンパイルステップで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']);
        }
    };
});

1つの欠点は、$scopeは、コントローラーが最初に実行されたときに最初に入力されません。

これが JSFiddle このコードです。

0
Peter Kovacs

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);
        }
    };
});
0
Mark Rajcok