2013-06-05 15 views
13

Ho creato una direttiva con un'associazione utilizzando "scope". In alcuni casi, voglio associare un oggetto costante. Ad esempio, con HTML:AngularJS: come associare un oggetto costante a una direttiva

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

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

Anche se questo funziona, ma provoca anche un errore JavaScript:

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

(Fiddle demonstrating the problem)

Qual è il modo corretto legare un oggetto costante senza causare l'errore?

risposta

10

Ecco la soluzione mi è venuta, in base alla risposta di @ sh0ber:

implementare una funzione personalizzata link. Se l'attributo è JSON valido, allora è un valore costante, quindi lo valutiamo solo una volta. Altrimenti, guarda e aggiorna il valore normalmente (in altre parole, prova a comportarti come un binding =). scope deve essere impostato su true per assicurarsi che il valore assegnato abbia effetto solo su questa istanza della direttiva.

(Example on 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>' 
    }; 
}); 
2

Questo perché se si utilizza il tipo di collegamento del campo scope del tipo =, il valore dell'attributo viene osservato per le modifiche, ma è stato testato per l'uguaglianza di riferimento (con !==) anziché verificato profondamente per l'uguaglianza. Specificando l'oggetto letterale in-line si creerà angolare la creazione del nuovo oggetto ogni volta che si accede all'atributo per ottenere il suo valore, quindi quando l'angolare esegue il dirty-checking, confrontando il vecchio valore con quello corrente si segnala sempre la modifica.

Un modo per superare questo sarebbe modificare fonte di angolare come descritto qui:

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

In caso contrario, è possibile creare il vostro oggetto nel controller e fare riferimento per nome nell'attributo dell'elemento:

HTML

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

JS

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

Ancora un altro il modo è creare l'oggetto nell'elementodell'elemento principaledirettiva e in seguito fare riferimento a esso per nome (ma questo è meno leggibile):

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

Hai trovato che l'errore perché angolare sta valutando l'espressione ogni volta. '=' è per i nomi delle variabili.

Ecco due modi alternativi per ottenere lo stesso risultato senza l'errore.

prima soluzione:

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> 

seconda soluzione:

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/

+1

Grazie per la risposta. Sfortunatamente, la seconda soluzione non è possibile poiché i dati reali che sto usando sono profondamente annidati. Il primo caso è possibile ma alquanto disordinato poiché ci sono molte istanze della direttiva che vengono utilizzate con valori costanti (vengono generate lato server). –

0

non mi piace particolarmente usando eval(), ma se si vuole veramente ottenere questo a lavorare con la HTML fornito:

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

Un'altra opzione:

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

Ho avuto lo stesso problema, ho risolto analizzando il JSON nella fase di compilazione:

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

L'unico inconveniente è che lo $scope non viene inizialmente popolato quando il controller viene eseguito per la prima volta.

Ecco uno JSFiddle con questo codice.

Problemi correlati