6 votes

Charger une page partielle avec Angular et compiler le contrôleur

Dans une application à grande échelle, notre application web peut être organisée en pages partielles séparées afin d'augmenter la modularité de notre application. Dans certains cas, la compilation d'une page partielle chargée par XHR ou une requête Ajax en utilisant Angular $http.get ou JQuery $.load introduira une erreur.

En utilisant mon scénario comme exemple, j'utilise exactement le framework PHP Kohana pour pouvoir contrôler la modularité de mon application web au niveau du serveur. Comme d'habitude, tous les modèles et les pages sont séparés en vue, laissant tout le HTML, JS et CSS ensemble sur la couche de présentation.

Cela me donnera une grande flexibilité pour mettre en œuvre la pile Javascript MVW/MVC sur le traitement côté client, car mon application web dépend fortement des requêtes AJAX pour récupérer les données de l'application dorsale. Dans mon scénario, j'utilise AngularJS et voici un pseudo simple sur la façon dont les données du modèle sont présentées au client.

Modèle Kohana > Contrôleur Kohana > Vue Kohana > XHR > JQuery \Angular > DOM

L'une des parties de mon application qui m'a vraiment donné un coup et m'a fait boire quelques bouteilles de boisson métabolique pour résoudre l'application. J'ai un dialogue modal et la page partielle est chargée par XHR depuis le serveur et attachée au DOM sélectionné.

Le problème est que lorsqu'Angular essaie de compiler la page partielle, lorsqu'il trouve la directive ng-controller, il cherche la fonction faisant référence à la directive traitée. Des erreurs sont produites lorsque le contrôleur n'est pas trouvé car il n'a pas encore été évalué par le DOM parser. Mais si vous pré-définissez la fonction quelque part dans votre application juste avant de charger la page partielle, tout est OK. Voici l'exemple de la façon dont j'ai configuré un service de dialogue qui sera appelé à partir de la directive de lien lorsque je clique sur le lien en question.

var dialogService = angular.module('dialog.service', []);
dialogService.factory('Dialog', function($http,$compile){
    var dialogService = {};
    dialogService.load = function(url, scope){
        $("#dialog:ui-dialog").dialog( "destroy" );
        $("#dialog").attr('title','Atlantis');

        $http.get(url).success(function (data) {
            html = $compile(data)(scope);
            $('#dialog-content').html(html);

            $("#dialog").dialog({
                width: '600px',
                buttons: {
                    "Ok": function() {
                        $( this ).dialog( "close" );
                        return true;
                    },
                },
                close: function(){
                    if (typeof (onClose) == 'function') { onClose(); }
                },
            });
        });
    }

    return dialogService;
});

Après quelques recherches, j'ai trouvé une solution et je la partage avec les autres sur ma réponse pour les autres débutants comme moi. (désolé pour mon anglais).

5voto

wajatimur Points 492

Il n'y a rien de mal à utiliser AngularJS dans cette configuration, d'autres gourous de JS connaissent peut-être déjà la solution et sont très occupés à la partager avec nous tout en inventant d'autres outils ou frameworks de développement web. Ce n'est pas grave, continuez à le faire. Ce n'est peut-être pas une solution idéale ou ultime, mais n'hésitez pas à nous faire part de vos améliorations ou de vos conseils !

Pour surmonter ce problème, nous avons besoin d'une stratégie à mettre en place, laissez-moi commencer par un exemple de code pour que notre cerveau puisse digérer les informations qui circulent. Le code ci-dessous est le placeholder où je crée le dialogue modal en utilisant JQuery et le contenu Ajax sera inséré.

<div ng-app="asng" id="dialog" title="" style="display:none">
     <div id="dialog-content"></div>
</div>

Comme connaissance de base, nous devons comprendre comment fonctionne le DOM parser. Nous pourrions penser que le DOMP (DOM Parser) est multi-thread et que c'est la raison pour laquelle nous pouvons charger plusieurs ressources externes en parallèle. En fait, le DOMP est un thread unique qui analyse l'index des éléments du DOM de haut en bas. Voici l'exemple d'une page partielle que je vais charger dans l'élément DIV #dialog-content.

<script language="JavaScript" type="text/javascript">
    function Transaction ($scope,$http){
        $scope.items = [{"country":"VN","quantity":"100"}];
        $scope.country_name = $scope.items;
    }
</script>

<style>
</style>

<div id="transaction-panel" class="user" data-ng-controller="Transaction">
        <form id="{{ form_name }}" action="">
        Country : [[ items.country ]] </br>
        Total : [[ items.quantity ]]
    </form>
</div>

En fait ces partiels donnent toujours une erreur, bien que nous ayons mis le bloc script juste avant l'élément avec la directive ng-controller. En fait, ce n'est pas vraiment le cas, la partie que nous devons aborder est comment le service de compilation d'AngularJS compile le DOM partiel. Revenons à la partie de ma question ci-dessus et inspectons où se trouve la ligne où nous faisons la compilation.

html = $compile(data)(scope);
$('#dialog-content').html(html);

La première ligne ci-dessus compilera le DOM dans une variable de données, et l'insérera dans le DOM racine malheureusement la première ligne affichera une erreur : Controller Transaction not found.

Cela se produit parce que, le Bloc script dans votre page partielle n'est pas encore évalué par l'analyseur DOMP car il n'est pas inséré dans le DOMP racine. Maintenant vous voyez la lumière OK, donc nous devons changer la stratégie de compilation un peu, en insérant le nouveau DOM et ensuite nous allons analyser le DOM inséré regardez l'exemple ci-dessous:-

html = $('#dialog-content').html(data);
$compile(html)(scope);

Solution mince et simple, il m'a fallu quelques matinées de cognement pour résoudre ce problème, simplement parce que j'ignorais le concept simple du DOM parsing.

0voto

Si je comprends ce que vous essayez de faire, voici un exemple simple.

Je voulais envoyer un message via AJAX à un formulaire Django, puis remplacer le contenu du formulaire dans la page par le balisage renvoyé. Le balisage renvoyé comprend un ng-controller, que je dois exécuter lors de son chargement :

.controller('MyForm', function($element, $compile, $scope){
    var scope = $scope;
    var $theForm = $element;
    var $formBlock = $element.find('.the_form');  // is replaced by the form response
    $element.find('.submit_the_form').click(function(){
        // submit the form and replace contents of $formBlock
        $.post($theForm.attr('action'), $theForm.serialize(), function(response){
            var newstuff = $formBlock.html(response);
            $compile(newstuff)(scope); // loads the angular stuff in the new markup
        });
    });
})

Je pense que la ligne qui vous intéresse est $compile(newstuff)(scope) ;

EDIT : Bon sang, j'ai essayé ça avec d'autres balises ce matin et ça n'a pas marché, sans que je comprenne pourquoi. Il s'est avéré que si je n'avais pas un champ avec ng-model assigné, dans le nouveau markup, alors la $compile ne s'exécute pas. Ajouté :

<input type="hidden" name="dummy" value="0" ng-model="dummy"/>

...et maintenant ça compile.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X