web-dev-qa-db-fra.com

Pourquoi $ element est-il disponible / injecté dans le contrôleur?

Dans AngularJS, j'ai remarqué qu'un contrôleur est injecté avec $element, qui est un wrapper JQuery/JQLite de l'élément que le contrôleur contrôle. Par exemple:

<body ng-controller="MainCtrl">

Ensuite, vous pouvez avoir accès à l'élément body dans le contrôleur en injectant $element

app.controller('MainCtrl', function($scope, $element) { ...

Cela peut être vu fonctionner à ce Plunkr .

Et semble être confirmé comme une fonctionnalité délibérée dans le docs for $ compile

Mes questions sont:

  • À la lumière des différents guides et tutoriels qui suggèrent que vous ne devriez pas accéder au DOM dans un contrôleur, pourquoi est-ce même possible?

  • Existe-t-il un cas d'utilisation non hacky pour cela?

  • Existe-t-il des exemples de cette utilisation dans le code disponible quelque part?

Merci.

32
Michal Charemza

Résumé rapide

Une directive bien écrite qui est extensible et/ou interagit avec d'autres directives aura un contrôleur. Ce contrôleur doit accéder au DOM car c'est là que la fonctionnalité de cette directive est définie. Les directives sont en fait une manière différente de lier un contrôleur/portée à un élément de la page; la façon préférée d'ajouter des fonctionnalités au DOM. D'après ce que je comprends, la meilleure pratique est: n'utilisez pas à la fois un contrôleur et une fonction de liaison. Les contrôleurs de directive ont donc besoin d'un $element.

Réponses détaillées

À la lumière des différents guides et tutoriels qui suggèrent que vous ne devriez pas accéder au DOM dans un contrôleur, pourquoi est-ce même possible?

Les guides sont un peu trompeurs une fois que vous avez exploré comment tout cela fonctionne.

ce que disent les guides:

Les contrôleurs gèrent la définition des fonctions et affectent les variables à utiliser par la vue. Et la bonne façon de lier ces fonctions et variables à la vue est avec une directive. Telle est ma compréhension des meilleures pratiques, ayant travaillé avec des applications angular angulaires au cours de la dernière année).

pourquoi c'est déroutant:

La chose délicate est que la directive lie fondamentalement un contrôleur au DOM. ng-model est une directive et possède un contrôleur accessible à partir d'autres directives. Vous voudrez en profiter si vous faites des choses comme ajouter une fantaisie de validation personnalisée. Ce contrôleur de la directive est supposé le faire manipuler le DOM. Donc, un contrôleur générique est en fait un super ensemble de contrôleurs de vue; un détail sur lequel les didacticiels se glacent généralement.

Existe-t-il un cas d'utilisation non hacky pour cela?

façons "correctes" d'utiliser $element:

L'utiliser dans le contrôleur d'une directive par exemple.

Existe-t-il des exemples de cette utilisation dans le code disponible quelque part?

Exemples:

Le code source angulaire, bien que peut-être un peu dense d'une lecture, est un bon code et bien commenté. Cela peut prendre un peu de temps pour voir ce qui se passe, mais généralement assez instructif.

NgModelController (exemple complexe) https://github.com/angular/angular.js/blob/master/src/ng/directive/input.jshttps://github.com /angular/angular.js/blob/master/src/ng/directive/input.js#L166

Ce qui pourrait être un exemple simple, mais utilise une fonction de compilation à la place, eventDirectives (ng-click par exemple), https://github.com/angular/angular.js/blob/master/src/ng/directive/ngEventDirs.js#L

15
alockwood05

À la lumière des différents guides et tutoriels qui suggèrent que vous ne devriez pas accéder au DOM dans un contrôleur, pourquoi est-ce même possible?

Que vous injectiez ou non $ element, la portée du contrôleur est liée à cet élément.

angular.element('#element-with-controller').scope();

L'angulaire tourne autour des directives. C'est ce qui colle les choses ensemble dans le MVC. Et si vous y réfléchissez, ng-controller, est une directive elle-même.

Existe-t-il un cas d'utilisation non hacky pour cela?

Je suppose que cela peut être utile lorsque vous utilisez un seul contrôleur pour plusieurs directives.

.controller('MyController', function($scope, $element){
    $scope.doSomething = function(){
        // do something with $element...
    }
})
.directive('myDirective1', function(){
    return {
        controller: 'MyController'
    }
})
.directive('myDirective2', function(){
    return {
        controller: 'MyController'
    }
})

Chaque directive aura une nouvelle instance du contrôleur assigné, mais partagera essentiellement ses propriétés, ses dépendances.

Existe-t-il des exemples de cette utilisation dans le code disponible quelque part?

J'ai écrit une fois un contrôleur de gestionnaire de formulaire, pour l'inscription/la connexion/le contact, etc.

Publier mon commentaire comme réponse à cause des limites de caractères dans les commentaires et à cause du sentiment qui contient une partie de la réponse.

À la lumière des différents guides et tutoriels qui suggèrent que vous ne devriez pas accéder au DOM dans un contrôleur, pourquoi est-ce même possible?

Comme dit précédemment, les gens suggèrent d'adopter une approche spécifique dans votre code ne les obligeant pas à vous limiter.


Existe-t-il un cas d'utilisation non hacky pour cela?

Du haut de ma tête, je ne peux pas penser à un avantage (réponse sur votre commentaire) dans la plupart des cas. Une fois que j'ai utilisé cette approche, j'ai implémenté une directive API iframe youtube. Lorsque quelqu'un a arrêté le lecteur, l'élément a dû être supprimé du DOM.


Existe-t-il des exemples de cette utilisation dans le code disponible quelque part?

Voici un code pour cela, bien qu'il date d'il y a un certain temps et que j'ai supprimé certaines parties et qu'il soit considéré comme hacky?

angular.module('mainApp.youtube').directive('youtubePlayer', function($window,$element logging, ui,) {
    return {
        restrict: 'A', // only activate on element attribute
            scope: true, // New scope to use but rest inherit proto from parent
            compile: function(tElement, tAttrs) {
            // Load the Youtube js api
            var tag = document.createElement('script');
            tag.src = "https://www.youtube.com/iframe_api";
            var firstScriptTag = document.getElementsByTagName('script')[0];
            firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
        },
        controller: function($scope, $element, $attrs) {

        // This is called when the player is loaded from YT
        $window.onYouTubeIframeAPIReady = function() {
            $scope.player = new YT.Player('player', {
                    height: '250',
                    width: '400',
                    playerVars: {
                'autoplay': 0,
                        'controls': 1,
                        'autohide': 2
                    },
                    //videoId: $scope.live_track.video_id,
                    events: {
                'onReady': $scope.onPlayerReady,
                        'onStateChange': $scope.onPlayerStateChange,
                        'onError': $scope.onError
                    }
                });
            };  

            // When the player has been loaded and is ready to play etc
            $scope.onPlayerReady = function (event) {
                $scope.$apply(function(){
                    logging.info("Playa is ready");
                    logging.info($scope.player);
                    // Lets also broadcast a change state for the others to catch up
                    player_service.broadcast_change_state({"state": $scope.player.getPlayerState()});
                    // Should try to just load the track so that the users can press play on the playa
                });
            };



            // When the player has been loaded and is ready to play etc
            $scope.onError = function (event) {
                $scope.$apply(function(){
                    logging.info("Playa Encountered and ERROR");
                    logging.info(event)
                    });
            };

            $scope.start_playing = function (jukebox_id){
                logging.info('Yes I am starting...');

            };



            $scope.$on('handleStartPlaying', function(event, jukebox_id) {
                console.log('Got the message I ll play');
                $scope.start_playing(jukebox_id);
            });

            $scope.$on('handlePausePlaying', function() {
                console.log('Got the message I ll pause');
                $scope.player.pauseVideo();
            });

            $scope.$on('handleResumePlaying', function() {
                console.log('Got the message I ll resume');
                $scope.player.playVideo();
            });

            $scope.$on('handleStopPlaying', function() {
                console.log('Got the message I ll stop');
                $scope.player.stopVideo();
            });

            $scope.$on('HandleCloseframe', function() {
                console.log('Got the message I ll stop');
                $scope.player.stopVideo();
                //Should destroy obje etc
                // Look here
                $element.remove(); // blah blah blah
            });

        },
            ink: function(scope, Elm, attrs, ctrl) {

        }
        }
    });

N'hésitez pas à me corriger ou à proposer de meilleures approches. A cette époque, cela semblait légitime. Au moins, si nous ne commettons pas d'erreurs, nous n'apprenons pas.

4
Jimmy Kane

En fait, $ element est injecté car vous l'avez spécifié comme dépendance dans la liste d'arguments. Si vous le supprimez de la liste, il ne sera pas injecté.

http://plnkr.co/edit/CPHGM1awvTvpXMcjxMKM?p=preview

Et comme commenté, il y a des cas où vous avez besoin de $ element dans le contrôleur, bien que je ne puisse pas en penser pour le moment.

4
Lucius