Die AngularJS-Direktive wird bei Änderungen der Bereichsvariablen nicht aktualisiert

68

Ich habe versucht, eine kleine Direktive zu schreiben, um ihren Inhalt mit einer anderen Vorlagendatei zu versehen.

Dieser Code:

<layout name="Default">My cool content</layout>

Sollte diese Ausgabe haben:

<div class="layoutDefault">My cool content</div>

Weil das Layout "Standard" diesen Code hat:

<div class="layoutDefault">{{content}}</div>

Hier der Code der Richtlinie:

app.directive('layout', function($http, $compile){
return {
    restrict: 'E',
    link: function(scope, element, attributes) {
        var layoutName = (angular.isDefined(attributes.name)) ? attributes.name : 'Default';
        $http.get(scope.constants.pathLayouts + layoutName + '.html')
            .success(function(layout){
                var regexp = /^([\s\S]*?){{content}}([\s\S]*)$/g;
                var result = regexp.exec(layout);

                var templateWithLayout = result[1] + element.html() + result[2];
                element.html($compile(templateWithLayout)(scope));
            });
    }
}

});

Mein Problem:

Wenn ich Bereichsvariablen in der Vorlage verwende (in der Layoutvorlage oder innerhalb des Layout-Tags), z. {{whatever}}es funktioniert nur anfangs. Wenn ich die whateverVariable aktualisiere, wird die Direktive nicht mehr aktualisiert. Die gesamte Link-Funktion wird nur einmal ausgelöst.

Ich denke, dass AngularJS nicht weiß, dass diese Direktive Bereichsvariablen verwendet und daher nicht aktualisiert wird. Aber ich habe keine Ahnung, wie ich dieses Verhalten beheben kann.

Armin
quelle
2
Keine der aktuellen Antworten scheint die Frage zu beantworten, warum (bei Verwendung $compile) die Uhr nicht automatisch eingerichtet wird. Wie Sie sagen, ist es zunächst gebunden ...
Davin Tryon
Ich habe eine andere Lösung gefunden, um Template und ng-transclude zu verwenden. Das funktioniert gut - immer. Das einzige Problem ist, dass ich nicht weiß, wie ich die Layout-Vorlage selbst konfigurierbar machen soll. Wenn ich ng-include mit einer Bereichsfunktion verwende, um den Vorlagenpfad abzurufen, wird der Fehler ngTransclude: orphan angezeigt.
Armin
1
Okay, ich habe eine Lösung gefunden, um templateUrl dynamisch zu ändern. Siehe meine eigene Antwort unten.
Armin

Antworten:

85

Sie sollten eine gebundene Bereichsvariable erstellen und ihre Änderungen beobachten:

return {
   restrict: 'E',
   scope: {
     name: '='
   },
   link: function(scope) {
     scope.$watch('name', function() {
        // all the code here...
     });
   }
};
Quark
quelle
6
Stiltipp: Der richtige Variablenname sollte sein scopeund nicht $scope. Innerhalb der linkFunktion ist der Bereich eine einfache Variable.
Adelriosantiago
41

Ich brauchte auch eine Lösung für dieses Problem und habe die Antworten in diesem Thread verwendet, um Folgendes zu finden:

.directive('tpReport', ['$parse', '$http', '$compile', '$templateCache', function($parse, $http, $compile, $templateCache)
    {
        var getTemplateUrl = function(type)
        {
            var templateUrl = '';

            switch (type)
            {
                case 1: // Table
                    templateUrl = 'modules/tpReport/directives/table-report.tpl.html';
                    break;
                case 0:
                    templateUrl = 'modules/tpReport/directives/default.tpl.html';
                    break;
                default:
                    templateUrl = '';
                    console.log("Type not defined for tpReport");
                    break;
            }

            return templateUrl;
        };

        var linker = function (scope, element, attrs)
        {

            scope.$watch('data', function(){
                var templateUrl = getTemplateUrl(scope.data[0].typeID);
                var data = $templateCache.get(templateUrl);
                element.html(data);
                $compile(element.contents())(scope);

            });



        };

        return {
            controller: 'tpReportCtrl',
            template: '<div>{{data}}</div>',
            // Remove all existing content of the directive.
            transclude: true,
            restrict: "E",
            scope: {
                data: '='
            },
            link: linker
        };
    }])
    ;

In Ihr HTML aufnehmen:

<tp-report data='data'></tp-report>

Diese Anweisung wird zum dynamischen Laden von Berichtsvorlagen verwendet, die auf dem vom Server abgerufenen Dataset basieren.

Es setzt eine Überwachung auf die Eigenschaft scope.data und lädt bei jeder Aktualisierung (wenn der Benutzer ein neues Dataset vom Server anfordert) die entsprechende Anweisung, um die Daten anzuzeigen.

Roy Milder
quelle
Die $ watch () Funktion !! immer zu retten. !!
JayKandari
17

Sie müssen Angular mitteilen, dass Ihre Direktive eine Bereichsvariable verwendet:

Sie müssen eine Eigenschaft des Gültigkeitsbereichs an Ihre Direktive binden:

return {
    restrict: 'E',
    scope: {
      whatever: '='
    },
   ...
}

und dann ist $watches:

  $scope.$watch('whatever', function(value) {
    // do something with the new value
  });

Weitere Informationen finden Sie in der Angular-Dokumentation zu Anweisungen .

Paul Mougel
quelle
8

Ich habe eine viel bessere Lösung gefunden:

app.directive('layout', function(){
    var settings = {
        restrict: 'E',
        transclude: true,
        templateUrl: function(element, attributes){
            var layoutName = (angular.isDefined(attributes.name)) ? attributes.name : 'Default';
            return constants.pathLayouts + layoutName + '.html';
        }
    }
    return settings;
});

Der einzige Nachteil, den ich derzeit sehe, ist die Tatsache, dass transkludierte Vorlagen ihren eigenen Umfang haben. Sie erhalten die Werte von ihren Eltern, aber anstatt den Wert im Elternteil zu ändern, wird der Wert in einem eigenen, neuen untergeordneten Bereich gespeichert. Um dies zu vermeiden, verwende ich jetzt $parent.whateveranstelle von whatever.

Beispiel:

<layout name="Default">
    <layout name="AnotherNestedLayout">
        <label>Whatever:</label>
        <input type="text" ng-model="$parent.whatever">
    </layout>
</layout>
Armin
quelle
2

Sie sollten Ihr Zielfernrohr im Auge behalten.

So können Sie es machen:

<layout layoutId="myScope"></layout>

Ihre Richtlinie sollte so aussehen

app.directive('layout', function($http, $compile){
    return {
        restrict: 'E',
        scope: {
            layoutId: "=layoutId"
        },
        link: function(scope, element, attributes) {
            var layoutName = (angular.isDefined(attributes.name)) ? attributes.name : 'Default';
            $http.get(scope.constants.pathLayouts + layoutName + '.html')
                .success(function(layout){
                    var regexp = /^([\s\S]*?){{content}}([\s\S]*)$/g;
                    var result = regexp.exec(layout);

                    var templateWithLayout = result[1] + element.html() + result[2];
                    element.html($compile(templateWithLayout)(scope));
        });
    }
}

$scope.$watch('myScope',function(){
        //Do Whatever you want
    },true)

Ebenso können Sie Modelle in Ihrer Direktive verwenden. Wenn das Modell also automatisch aktualisiert wird, aktualisiert Ihre Überwachungsmethode Ihre Direktive.

Aditya Sethi
quelle
Der Schlüssel ist, = für die Bereichsvariable zu verwenden und den dritten Parameter von $ watch auf true zu setzen. (Anmerkung @ auf der Bereichsvariablen funktioniert nicht)
Shih-Min Lee
2

Ich weiß, dass dies ein altes Thema ist, aber falls jemand dies wie ich findet:

Ich habe den folgenden Code verwendet, als ich meine Direktive zum Aktualisieren von Werten benötigte, als der "übergeordnete Bereich" aktualisiert wurde. Bitte korrigieren Sie mich auf jeden Fall, wenn ich etwas falsch mache, da ich immer noch eckig lerne, aber dies hat getan, was ich brauchte;

Richtlinie:

directive('dateRangePrint', function(){
    return {
        restrict: 'E',
        scope:{
        //still using the single dir binding
            From: '@rangeFrom',
            To: '@rangeTo',
            format: '@format'
        },
        controller: function($scope, $element){

            $scope.viewFrom = function(){
                    return formatDate($scope.From, $scope.format);
                }

            $scope.viewTo = function(){
                    return formatDate($scope.To, $scope.format);
                }

            function formatDate(date, format){
                format = format || 'DD-MM-YYYY';

                //do stuff to date...

                return date.format(format);
            }

        },
        replace: true,
        // note the parenthesis after scope var
        template: '<span>{{ viewFrom() }} - {{ viewTo() }}</span>'
    }
})
slappy-x
quelle
0

Wir können das versuchen

$scope.$apply(function() {
    $scope.step1 = true;
    //scope.list2.length = 0;
});

http://jsfiddle.net/Etb9d/

Build-1
quelle
2
Während dieses Code-Snippet die Frage lösen kann, hilft eine Erklärung wirklich dabei, die Qualität Ihres Beitrags zu verbessern. Denken Sie daran, dass Sie die Frage für Leser in Zukunft beantworten und diese Personen möglicherweise die Gründe für Ihren Codevorschlag nicht kennen.
Rosário Pereira Fernandes
0

Eine einfache Lösung ist es, den Umfang variabel zu machen Objekt . Dann greifen Sie mit auf den Inhalt zu {{ whatever-object.whatever-property }}. Die Variable wird nicht aktualisiert, da JavaScript den primitiven Typ als Wert übergibt . Während Objekte als Referenz übergeben werden , löst dies das Problem.

Yohe
quelle
-1

Ich bin mir nicht sicher, warum noch niemand vorgeschlagen hat bindToController, all diese hässlichen zu entfernen, scopes and $watches.wenn Sie verwendenAngular 1.4

Unten ist ein Beispiel-DOM:

<div ng-app="app">
    <div ng-controller="MainCtrl as vm">
        {{ vm.name }}
        <foo-directive name="vm.name"></foo-directive>
        <button ng-click="vm.changeScopeValue()">
        changeScopeValue
        </button>
    </div>
</div>

Folgt dem controllerCode:

angular.module('app', []);

// main.js
function MainCtrl() {
    this.name = 'Vinoth Initial';
    this.changeScopeValue = function(){
        this.name = "Vinoth has Changed"
    }
}

angular
    .module('app')
    .controller('MainCtrl', MainCtrl);

// foo.js
function FooDirCtrl() {
}

function fooDirective() {
    return {
        restrict: 'E',
        scope: {
            name: '='
        },
        controller: 'FooDirCtrl',
        controllerAs: 'vm',
        template:'<div><input ng-model="name"></div>',
        bindToController: true
    };
}

angular
    .module('app')
    .directive('fooDirective', fooDirective)
    .controller('FooDirCtrl', FooDirCtrl);

Eine Geige zum Herumspielen, hier ändern wir den Scope-Wert im controllerund automatisch im directive updates on scope change. http://jsfiddle.net/spechackers/1ywL3fnq/

Thalaivar
quelle
1
Sie verwenden in Ihrem Beispiel nicht einmal bindToController.
Suamere