Why is $element available/injected in controller?

2019-02-01 17:45发布

In AngularJS, I've noticed that a controller is injected with $element, which is a JQuery/JQLite wrapper of the element the controller is controlling. For example:

<body ng-controller="MainCtrl">

Then you can have access to the body element in the controller by injecting $element

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

This can be seen working at this Plunkr.

And seems to be confirmed as a deliberate feature in the docs for $compile

My questions are:

  • In the light of the various guides and tutorials that suggest you shouldn't access the DOM in a controller, why is this even possible?

  • Is there any non-hacky use case for this?

  • Are there any examples of this being used in available code somewhere?

Thanks.

4条回答
做个烂人
2楼-- · 2019-02-01 18:20

Actually, $element is injected because you specified it as a dependency in the argument list. If you remove it from the list, it will not be injected.

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

And as commented, there are case in which you need $element in the controller, although i can't think of any at the moment.

查看更多
兄弟一词,经得起流年.
3楼-- · 2019-02-01 18:22

Posting my comment as an answer because of the character limits in comments and due to feeling that contains part of the answer.

In the light of the various guides and tutorials that suggest you shouldn't access the DOM in a controller, why is this even possible?

As said before that people suggest to take a specific approach in your code does not require them to limit you.


Is there any non-hacky use case for this?

From the top of my head I cannot think of a benefit (reply on your comment) in most of the cases. One time that I used this approach was implementing a youtube iframe API directive. When someone stopped the player the element had to be deleted from the DOM.


Are there any examples of this being used in available code somewhere?

Here is some code for that, though it is from quite some time ago and I've removed some parts and is considered 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) {

        }
        }
    });

Feel free to correct me or offer better approaches. At that time this seemed legit. At least if we don't do mistakes we don't learn.

查看更多
一纸荒年 Trace。
4楼-- · 2019-02-01 18:28

Quick summary

A well written directive that is extendable and/or interacts with other directives will have a controller. That controller needs to access the DOM because it is where that directive's functionality is defined. Directives are effectively a different way to bind a controller/scope to an element on the page; the preferred way to add functionality to the DOM. From what I understand, the best practice is: don't use both a controller and a link function. So directive controllers need an $element.

Detail answers

In the light of the various guides and tutorials that suggest you shouldn't access the DOM in a controller, why is this even possible?

The guides are a little misleading once you dig into how it all works.

what the guides say:

Controllers handle defining functions and assign variables to be used by the view. And the right way to bind those functions and variables to the view is with a directive. That is my understanding of best practices, having worked with large and growing angular applications for the past year.

why it is confusing:

The tricky thing is that the directive basically binds a controller to the DOM. ng-model is a directive and has a controller that can be accessed from other directives. You will want to take advantage of this if you do things like add custom validation fanciness. This controller of the directive is supposed to manipulate the DOM. So a generic controller is actually is a super set of view controllers; a detail that the tutorials usually glaze over.

Is there any non-hacky use case for this?

'correct' ways of using $element:

Using it in a directive's controller for example.

Are there any examples of this being used in available code somewhere?

Examples:

Angular source code, though perhaps a little dense of a read, is good code and well commented. It may take a little bit to see what's going on, but usually quite informative.

NgModelController (complex example) https://github.com/angular/angular.js/blob/master/src/ng/directive/input.js https://github.com/angular/angular.js/blob/master/src/ng/directive/input.js#L1660

What could be a simple example, but uses a compile function instead, the eventDirectives (ng-click for example), https://github.com/angular/angular.js/blob/master/src/ng/directive/ngEventDirs.js#L3

查看更多
Juvenile、少年°
5楼-- · 2019-02-01 18:29

In the light of the various guides and tutorials that suggest you shouldn't access the DOM in a controller, why is this even possible?

Whether you inject $element or not, the controller's scope is bound on that element.

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

Angular revolves around directives. It's what glues things together in the MVC. And if you think about it, ng-controller, is a directive itself.

Is there any non-hacky use case for this?

I guess this can come in handy when you're using a single controller for multiple 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'
    }
})

Each directive will have a new instance of the assigned controller, but basically share it's properties, dependencies.

Are there any examples of this being used in available code somewhere?

I wrote a form handler controller once, for registration/login/contactus, etc.

查看更多
登录 后发表回答