29 votes

$postLink d'un composant/directive angulaire s'exécutant trop tôt

Mise à jour : J'ai mis une prime sur cette question. Je ne cherche pas de hacks ou de solutions de contournement. Je cherche un moyen officiel d'accéder au dom dans un composant angulaire, et une explication sur la raison pour laquelle le comportement que je vois ($postLink fonctionnant trop tôt) semble être en contradiction avec la documentation officielle.
La documentation officielle indique ( aquí ):

$postLink() - Appelé après que l'élément de ce contrôleur et ses enfants aient été liés. Similaire à la fonction post-link, ce hook peut être utilisé pour mettre en place des gestionnaires d'événements DOM et effectuer des manipulations directes du DOM.

Question originale : J'ai un exemple du problème ici -> http://plnkr.co/edit/rMm9FOwImFRziNG4o0sg?p=preview

J'utilise un composant angulaire et je veux modifier le dom dans la fonction post link, mais cela ne fonctionne pas, il semble que la fonction s'exécute trop tôt, avant que le modèle soit réellement prêt dans le dom après tout le traitement angulaire.

Dans la page html, j'ai ceci :

<my-grid grid-id="'foo'"></my-grid>

Le composant est défini comme suit :

appModule.component('myGrid',{
    controller: gridController,
    bindings: {
        "gridId": "<",
    },
    templateUrl: 'gridTemplate'
});

Dans le modèle de composant, j'ai ceci :

<table id='{{$ctrl.gridId}}'>
...

(La reliure elle-même fonctionne, il n'y a aucun doute. Finalement, dans le html l'id de la table est 'foo' comme prévu).

Dans le contrôleur, j'ai quelque chose comme ceci :

function gridController($scope, $compile, $attrs) {
    console.log ("grid id is: " + this.gridId); // 'foo'

    this.$postLink = function() {
        var elem = document.getElementById(this.gridId);
        // do something with elem, but elem is null
    }
}

Ce que je constate en déboguant, c'est que lorsque la fonction $postLink est exécutée, la table est dans le dom mais son attribut id est toujours {{$ctrl.gridId}} au lieu de foo donc document.getElementById() ne trouve rien. Cela semble en contradiction avec la documentation.
Qu'est-ce que je rate ? Existe-t-il un autre moyen d'accéder au domaine dans le composant ?

Mise à jour 2 : Aujourd'hui, j'ai réalisé que le même problème se produit avec la fonction de lien ordinaire des directives, il n'est pas limité aux composants. Apparemment, j'ai mal compris le sens de l'expression "faire une manipulation directe du DOM" - la fonction de lien s'exécute sur un élément qui est détaché du DOM, donc l'utilisation de la commande document avec des sélecteurs est inutile.

19voto

Cosmin Ababei Points 6050

La documentation concernant $postLink() est correcte. Elle est appelée après que l'élément de son contrôleur et ses enfants aient été liés. Cela ne signifie pas que vous verrez le résultat d'une directive immédiatement. Peut-être appelle-t-elle $http et insérer le résultat dès qu'il arrive. Il s'agit peut-être d'enregistrer un observateur qui, à son tour, définit le résultat, comme le font la plupart des directives intégrées d'Angular .

Le problème sous-jacent dans votre cas est que vous voulez effectuer des manipulations DOM après que les interpolations ont été compilées ou, mieux encore, après que leur observateur enregistré ait eu le temps de courir une fois.

Malheureusement, il n'y a pas de manière officielle pour y parvenir. Néanmoins, il existe des moyens d'y parvenir, mais vous ne les trouverez pas dans la documentation.

Deux façons populaires de exécution d'une fonction après la compilation des interpolations sont :

  • en utilisant $timeout sans délai (car il est par défaut à 0) : $timeout(function() { /* Your code goes here */ });

  • en utilisant .ready() qui est fourni par jqLite d'Angular

Dans votre cas, il est préférable d'utiliser un observateur pour exécuter une fonction une fois que l'élément avec l'ID donné existe :

var deregistrationFn = $scope.$watch(() => {
    return document.getElementById(this.gridId);
}, (newValue) => {
    if (newValue !== null) {
        deregistrationFn();

        // Your code goes here
    }
});

Enfin, à mon avis, je pense que chaque fois qu'il faut attendre que les interpolations soient compilées, ou que certaines directives insèrent leur valeur, vous ne suivez pas la façon dont Angular construit les choses . Dans votre cas, pourquoi ne pas créer un nouveau composant, myGridTable qui exige myGrid comme parent, et y ajouter la logique appropriée. De cette façon, la responsabilité de chaque composant est bien mieux définie et il est plus facile de tester les choses.

10voto

Steven Lambert Points 3836

Le malentendu réside dans la façon dont le cycle angulaire $digest fonctionne en conjonction avec les événements du cycle de vie du contrôleur (et les événements du cycle de vie de la directive).

Chacun des événements du cycle de vie du contrôleur/directeur est appelé à l'intérieur d'un $apply. De ce fait, le DOM n'est mis à jour pour refléter les changements qu'après l'appel de l'événement $apply. Le cycle $digest est terminé .

La fonction post-link s'exécute une fois que tout a été lié, mais elle est à la fin de cette fonction que le DOM est mis à jour pour refléter toute liaison, étant donné qu'il s'agit de la dernière fonction exécutée à l'intérieur du cycle $digest. Cela signifie qu'essayer d'interroger le DOM pour tout ce qui est affecté par une liaison ne fonctionnera pas (comme un fichier ng-repeat (ou dans votre cas, une valeur liée). Mais comme l'indique la documentation, la liaison du DOM a déjà été exécutée, donc le modèle initial a été créé et peut être manipulé.

Puisque vous devez interroger le DOM après que les liaisons ont été appliquées, vous devez exécuter votre code après que le cycle actuel de $digest a été complété. Cela signifie utiliser un $timeout avec un retard de 0, qui sera exécuté juste après la fin du cycle actuel de $digest .

function gridController($scope, $compile, $attrs, $timeout) {
    console.log ("grid id is: " + this.gridId); // 'foo'

    this.$postLink = function() {
        $timeout(function() {
            var elem = document.getElementById(this.gridId);
            // do something with elem now that the DOM has had it's bindings applied
        });
    }
}

2voto

Il semble pertinent de comprendre les scénarios d'un composant angulaire avant de donner une solution. Et ce que je vois dans votre exemple, c'est que le modèle DOM est chargé dans un fichier de type isolé portée. Répétez une isolé portée

Donc,

Selon la doc officielle [1.5.6] sur les composants https://docs.angularjs.org/guide/component

Quand ne pas utiliser les composants :

  • pour les directives qui reposent sur la manipulation du DOM, l'ajout de récepteurs d'événements, etc, parce que les fonctions de compilation et de liaison ne sont pas disponibles.
  • lorsque vous avez besoin d'options avancées de définition de directive comme la priorité, le terminal, le multi-élément.
  • lorsque vous souhaitez que la directive soit déclenchée par un attribut ou une classe CSS, plutôt que par un élément.

Si vous relisez la section "Comparaison entre la définition d'une directive et la définition d'un composant" de la doc, vous verrez que

  • feature------------directive-----------component
  • bindings------------No-------------------Yes (se lie au contrôleur)
  • bindToController----Yes (default : false)-No (utiliser les bindings à la place)
  • compile function----Yes------------------No
  • contrôleur----------Yes------------------Yes (fonction par défaut() {})
  • controllerAs--------Yes------------------(default : false) Oui (default : $ctrl)
  • link functions------Yes------------------No

Maintenant

Les composants ne doivent jamais modifier des données ou des DOM qui ne sont pas dans leur domaine de compétence. propre portée . Normalement, en Angular, il est possible de modifier les données n'importe où. dans l'application à travers portée l'héritage et les montres. Cela est pratique, mais peut également entraîner des problèmes lorsqu'il n'est pas clair quelle partie de l'application est responsable de la modification des données. C'est pourquoi pourquoi les directives relatives aux composants utilisent un isoler de sorte qu'une classe entière de manipulation de la portée n'est pas possible. REF : ng-doc 1.5.6

Mais quand vous voyez qu'il y a un crochet $postLink() disponible, vous vous demandez peut-être pourquoi ?

Cependant, si vous parcourez la description de ce crochet, vous trouverez ...

Note que les éléments enfants qui contiennent templateUrl Les directives n'auront pas été compilé y lié à puisqu'ils attendent le chargement asynchrone de leur modèle et que leur propre compilation et liaison a été suspendue jusqu'à ce que cela se produise.

Veuillez vous référer aux réponses de Steven Lambert ci-dessous ...

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