为啥 $element 在控制器中可用/注入?

Posted

技术标签:

【中文标题】为啥 $element 在控制器中可用/注入?【英文标题】:Why is $element available/injected in controller?为什么 $element 在控制器中可用/注入? 【发布时间】:2013-12-29 03:37:56 【问题描述】:

在 AngularJS 中,我注意到一个控制器注入了$element,这是控制器控制的元素的 JQuery/JQLite 包装器。例如:

<body ng-controller="MainCtrl">

然后你可以通过注入$element来访问控制器中的body元素

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

这可以在this Plunkr 看到。

并且似乎在docs for $compile 中被确认为故意的功能

我的问题是:

鉴于各种指南和教程建议您不应在控制器中访问 DOM,为什么这甚至可能?

是否有任何非骇客的用例?

在某处的可用代码中是否有任何示例?

谢谢。

【问题讨论】:

建议并不意味着限制。如果有人不需要链接函数或编译阶段来访问元素怎么办? 我更喜欢在我的指令控制器函数中使用 $element 并且经常完全省略链接函数。 我在非指令控制器中使用过一次,因为我需要在不同的视图之间共享一个画布元素(以免丢失创建的 WebGL 上下文)。为此创建一个“单例”指令只是为了访问element 对我来说似乎不是正确的事情。 @Jimmy 将访问 $element 的代码放在控制器中而不是在链接函数或编译阶段的方法有什么好处? @MichalCharemza 真的应该使用控制器或链接功能,而不是两者(混淆在两个地方存在相同类型的逻辑)。 ng-form, ng-model 是两个使用此类的角度指令。来自docs.angularjs.org/guide/directive - “最佳实践:当你想向其他指令公开 API 时使用控制器。否则使用链接。” 【参考方案1】:

实际上,$element 被注入是因为您在参数列表中将其指定为依赖项。 如果将其从列表中删除,则不会被注入。

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

正如评论的那样,在某些情况下您需要在控制器中使用 $element,尽管我目前想不出任何。

【讨论】:

我意识到我的问题的第一句话有点不清楚。当我的意思是“可以注入”时,我写了“被注入”。这个问题的目的是找出为什么 Angular 的开发人员可以做到这一点。【参考方案2】:

由于 cmets 中的字符限制以及包含部分答案的感觉,将我的评论作为答案发布。

鉴于各种指南和教程建议您不应在控制器中访问 DOM,为什么这甚至可能?

如前所述,人们建议在您的代码中采用特定方法并不要求他们限制您。


是否有任何非骇客的用例?

在大多数情况下,我想不出有什么好处(回复您的评论)。有一次我使用这种方法是实现一个 youtube iframe API 指令。 当有人停止播放器时,必须从 DOM 中删除该元素。


在某处的可用代码中是否有任何使用此示例的示例?

这里有一些代码,虽然它是很久以前的,我已经删除了一些部分并且被认为是 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) 

        
        
    );

请随时纠正我或提供更好的方法。在当时,这似乎是合法的。至少如果我们不犯错误,我们就不会学习。

【讨论】:

【参考方案3】:

鉴于各种指南和教程建议您不应在控制器中访问 DOM,为什么这甚至可能?

无论是否注入 $element,控制器的作用域都绑定在该元素上。

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

Angular 以指令为中心。这就是在 MVC 中将事物粘合在一起的原因。 如果你仔细想想,ng-controller 本身就是一个指令。

是否有任何非骇客的用例?

我想这在您将单个控制器用于多个指令时会派上用场。

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

每个指令都会有一个分配控制器的新实例,但基本上共享它的属性、依赖项。

在某处的可用代码中是否有任何使用此示例的示例?

我曾经写过一个表单处理控制器,用于注册/登录/联系等。

【讨论】:

我发现另一个非黑客用途是使用 d3 更新 SVG。类似d3.select($element).append("circle") @Jackie 据我所知,这种需求最好使用链接功能【参考方案4】:

快速总结

一个编写良好的可扩展指令和/或与其他指令交互的指令将具有一个控制器。该控制器需要访问 DOM,因为它是定义该指令功能的地方。指令实际上是将控制器/范围绑定到页面上的元素的另一种方式;向 DOM 添加功能的首选方式。据我了解,最佳做法是:不要同时使用控制器和链接功能。所以指令控制器需要一个$element

详细解答

鉴于各种指南和教程建议您不应在控制器中访问 DOM,为什么这甚至可能?

一旦你深入了解它是如何工作的,这些指南就会有点误导。

导游怎么说:

控制器处理定义函数并分配视图使用的变量。将这些函数和变量绑定到视图的正确方法是使用指令。这是我对最佳实践的理解,在过去的一年中,我使用过大型且不断增长的 Angular 应用程序。

为什么会让人困惑:

棘手的是该指令基本上将控制器绑定到 DOM。 ng-model 是一个指令,并且有一个可以从其他指令访问的控制器。如果您执行添加自定义验证功能之类的操作,您将希望利用这一点。该指令的控制器应该操作 DOM。 所以一个泛型控制器实际上是一个视图控制器的超集;教程通常会忽略的细节。

是否有任何非hacky用例?

“正确”使用$element的方法:

例如在指令的控制器中使用它。

在某处的可用代码中是否有任何使用此示例的示例?

示例:

Angular 源代码,虽然读起来可能有点密集,但代码很好,注释也很好。可能需要一点时间才能看到发生了什么,但通常信息量很大。

NgModelController(复杂示例) 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

这可能是一个简单的例子,但使用了一个编译函数,eventDirectives(例如ng-click), https://github.com/angular/angular.js/blob/master/src/ng/directive/ngEventDirs.js#L3

【讨论】:

以上是关于为啥 $element 在控制器中可用/注入?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的控制器方法不可用?

为啥在python中是ioc / di不常见

为啥所有的 Rails 助手总是对所有视图可用?有没有办法禁用它?

spring IOC控制反转和DI依赖注入

灵活调整CAN总线负载 ,精度控制1%以内,且高可用

灵活调整CAN总线负载 ,精度控制1%以内,且高可用