简体   繁体   中英

AngularJs : what is the best way to changing DOM elements when there are “global”?

I'm from JQuery, and my first steps with AngularJs are getting me a little bit confused...

My case :
I have a panel (#floatingpanel) that is hidden with css (top:-50px), out of the viewport.
I have a link (a.trigger), in the deepest of my app, and when you click on that link the "top" property of my panel goes from "-50px" to "0px" to make it visible (you click to toggle the visibility). The panel is just at this end of the main container of my page, but the link it's in hundreds of others div .

Let's have a look to the DOM structure :

<body>
  <div class="main-wrapper" ng-app="myapp">
    <div class="app">
      <div class="many-things">
        [...]
        <a href="#" class="trigger" ng-click="toggleMyPanelVisibility()">
          Click here to see the panel
        </a>
        [...]
      </div>
    </div><!-- .app -->

    <div id="floatingpanel">
        Hey ! I was hidden !
    </div>

  </div> <!-- #mainwrapper -->
</body>

In jQuery, It would be :

$('a.trigger').click( function(e) {
  e.preventDefault();
  var el = $('#floatingpanel');
  var top = el.css('top');
  if (top<0) el.css('top', 0);
   else  el.css('top', -50);
});

How can I make it with AngularJs ?

I would do it like that :

var app = angular.module('myapp');
app.run(["$rootScope", function($rootScope) {
    // Toggle du myaccountpanel
    $rootScope.floatingpanelVisibility = false;
    $rootScope.toggleMyPanelVisibility = function() {
        $rootScope.floatingpanelVisibility = !$rootScope.floatingpanelVisibility;
        var panel = angular.element( $('#floatingpanel') );
        var top = -50;
        if ($rootScope.floatingpanelVisibility)
            top = 0; 
        panel.css({'top': top });
    }

});

Ok, it works but... i guess my app.run will be overloaded very soon if I continue this way, no ? I also can do it with a controller on my "main-wrapper" but it's stupid, it's already my app scope !

How can I do it with a directive ?
What could be the "best way" to solve that mess ?

The two most common ways I've seen to do this in Angular is via rootscope messaging or via a service. The service is the cleaner/better of the two.

The accepted answer assumes everything is under the same controller, and that's expressly not the way to structure what the OP describes.

Imagine a page comprised of a bunch of widgets - either child views or directives, all handled by their own, independent controllers. That's the correct way to handle what the OP describes as "all that stuff" in Angular.

Now, you need a way to trigger an event in one that can be responded to by any of the others. If you define a service, say, "pageEventService" that exposes a function to initiate/trigger the event "togglePanel", as well as a property that holds the state - "panelIsVisible", you can reference that service wherever you need to trigger the event, and reference it in the Panel to respond to the value.

.controller('SomeControllerThatCanTriggerEvent', function(pageEventService) {
    var model = this;
    model.togglePanel = pageEventService.togglePanel;
});

somewhere in the markup controlled by this controller...

<a ng-click="model.togglePanel()">Toggle the Panel</a>

in the Panel controller...

.controller('PanelController', function(pageEventService) {
    var model = this;
    model.panelIsVisible = pageEventService.panelIsVisible;
});

and in the Panel markup...

<div id="TheAlmightyPanel" ng-show="model.panelIsVisible">... cool stuff here ...</div>

This is cleaner than rootscope messaging because you can be explicit about what pageEventService can do and about who references it. You can use it in views, child-views, and directives - wherever you need it.

What you are doing can just be done without touching DOM, using built in angular directives itself, here i am just using ng-show directive, if you want to put specific class to it then just use ng-class as well, and any animation can just be provided as css animations and with the inclusion of ngAnimate module it gets better you can use the classnames added to provide animation behavior.

Example:-

<div class="main-wrapper" ng-app="myapp">
   <div ng-controller="panelController as panel">
    <a href="#" class="trigger" ng-click="panel.toggle($event)">Click here to see the panel</a>
    <div  ng-show="panel.show" class='panel'> 
    <!-- <div  ng-class="{'show':panel.show}"> and define css rules for panel.show-->
        Hey ! I was hidden !
    </div>
   </div>
  </div>

in the controller panelController

angular.module('app').controller('panelController', function($scope){
    var vm = this;
    vm.show = false;
    this.toggle  = function($event){
       $event.preventDefault();
       vm.show = !vm.show;
    }
});

I am here just using controller As syntax you could use $scope as well if you would like and you can even convert this to a directive.

Edit based on your question update:

There is nothing bad in creating a controller and using built in angular directives as and when you need then rather than using jquery/manual dom manipulation when you really do not need to.

<body>
  <div class="main-wrapper" ng-app="myapp" ng-controller="mainController as main">
    <div class="app">
      <div class="many-things">
        [...]
        <a href="#" class="trigger" ng-click="main.togglePanel($event)">Click here to see the panel</a>
        [...]
      </div>
    </div><!-- .app -->

    <div id="floatingpanel" ng-show="panel.show">
        Hey ! I was hidden !
    </div>

  </div> <!-- #mainwrapper -->
</body>

And here is the bad answer probably you are onto since you seem to be looking at not creating a controller or redesign your views.

 .directive('togglePanel', function(jQuery){
      return {
         restrict: 'A',
         link:function(scope, elm, attr){
             elm.on('click', handleToggle);

             scope.$on('$destroy', function(){
                elm.off('click', handleToggle);
             });

             function handleToggle(e){
               var $target  = jQuery(attr.togglePanel);//Get the target
               //Do what you want to do with the target
             }
         }
      }

    }).constant('jQuery', window.jQuery);

and use it as:

 <a href="#" class="trigger" toggle-panel="#floatingPanel">Click here to see the panel</a>

Ok ! Thanks to @PSL, I've understood that it's not a problem if I setup a controller on my ng-app element.

<div class="main-wrapper" ng-app="myapp" ng-controller="mainController as main">

I thought it was a bad practice...
With that "mainController" I can easily interact with my 2 elements : with the link via an ng-click , and with the panel via an ng-class (or ng-show etc.) I cannot see a better way to do. If I avoid this "mainController" I could make a directive but to touch my panel it forces me to access DOM outside of the scope...

I definitely have to change my mind from the jquery way to the angular way ( this link can help ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM