user1620696
user1620696

Reputation: 11375

How to turn this functionality into something angular compliant?

I'm converting one Bootstrap + jQuery theme into Angular JS and I'm having some troubles to do so. One thing is this kind of functionality provided on the JS file of the theme:

/* Sidebar Navigation functionality */
var handleNav = function() {

    // Animation Speed, change the values for different results
    var upSpeed     = 250;
    var downSpeed   = 250;

    // Get all vital links
    var allTopLinks     = $('.sidebar-nav a');
    var menuLinks       = $('.sidebar-nav-menu');
    var submenuLinks    = $('.sidebar-nav-submenu');

    // Primary Accordion functionality
    menuLinks.click(function(){
        var link = $(this);

        if (link.parent().hasClass('active') !== true) {
            if (link.hasClass('open')) {
                link.removeClass('open').next().slideUp(upSpeed);

                // Resize #page-content to fill empty space if exists
                setTimeout(resizePageContent, upSpeed);
            }
            else {
                $('.sidebar-nav-menu.open').removeClass('open').next().slideUp(upSpeed);
                link.addClass('open').next().slideDown(downSpeed);

                // Resize #page-content to fill empty space if exists
                setTimeout(resizePageContent, ((upSpeed > downSpeed) ? upSpeed : downSpeed));
            }
        }

        return false;
    });

    // Submenu Accordion functionality
    submenuLinks.click(function(){
        var link = $(this);

        if (link.parent().hasClass('active') !== true) {
            if (link.hasClass('open')) {
                link.removeClass('open').next().slideUp(upSpeed);

                // Resize #page-content to fill empty space if exists
                setTimeout(resizePageContent, upSpeed);
            }
            else {
                link.closest('ul').find('.sidebar-nav-submenu.open').removeClass('open').next().slideUp(upSpeed);
                link.addClass('open').next().slideDown(downSpeed);

                // Resize #page-content to fill empty space if exists
                setTimeout(resizePageContent, ((upSpeed > downSpeed) ? upSpeed : downSpeed));
            }
        }

        return false;
    });
};

This is a function that runs when the app starts and is responsible for attaching functionallity on the sidebar. Basically this simply attaches handles to the click event of the links on the sidebar to add the dropdown effect and to resize the main container if needed.

That's fine, however, I don't see how to make this something "angular compliant". I mean, I could run this on the view loaded event, but it doesn't seem like a good idea. I imagine I need to use directives, but in this case I don't see how. This isn't something applied to one element, is in truth a bunch of things applied to a bunch of elements.

More than that, it interferes with disconnected elements: this also resizes the main container, so this invokes some functionality on some other element of the page.

How this specific functionality could be converted to something angular compliant?

EDIT : My idea here was to create two directives menu-link and submenu-link and then apply this directive on each of the links themselves. The code in the directives would be the same presented here. This doesn't seem practical however, since I would need to put the directive over and over again.

Upvotes: 0

Views: 167

Answers (1)

shaunhusain
shaunhusain

Reputation: 19748

Sounds like your probably doing too much work... have you seen http://angular-ui.github.io/bootstrap/ also as stated in the comment, put together a http://plnkr.co showing your attempt so far and you can get a solution no doubt (also if you use IRC join the #angularjs room and ask for help)

EDIT

Hiya, w/r/t the comments, np, and yup what you'll want to do is likely going to involve a few pieces. You can communicate between directives if they have a direct parent child relationship or the directives are on the same element you can use the "require" property when defining your directive so it can require the controller from another directive be injected into it's link function (see $compile documentation regarding "require" if interested).

https://docs.angularjs.org/api/ng/service/$compile

https://docs.angularjs.org/guide/directive

There are otherwise basically two other ways you can go about communicating between disparate elements, I prefer the first. The first way is to use a service that is injected into both components that need to share some information and the data is defined in the service so when that service (singleton) is injected into various controllers they have a shared data store and some functions they can work with to communicate between things. So for your example you have the left nav which I assume you want to track clicks on and do something in the panel that's on the right. You can just store the information you need about the state of things within a service.

The other way you might go about doing this is using events with $emit, $on, and $broadcast but I think this only really fits the bill if you want a directive to be usable within a "widget like" architecture where the widgets just emit events and respond to events broadcast on them in various ways. This can be problematic with regard to the parent/child relationship of the scopes though and can cause crazy infinite loops through events triggering events etc. etc.

Generally in Angular you will apply the directives to the elements you want them to effect. The directive definition may have a service injected into it that provides it with more information or that data can be passed through the scope of the directive (if you isolate it) and you can then inject the service into a controller to get the data you want into the directives scope... sorry I know this is abstract so let me know if you want more clarification somewhere.

Another way to view this is that you want to create a "view-model" which resides in your service since you want it to be persistent across various controllers.

Here's a couple of examples I've posted: Angular $http vs service vs ngResource

Don't take the above link as being literally what your trying to do but just an example of a service that has some data that gets updated somehow and is injected into a service to be used in the view. From the controller the data is assigned to a property of the scope so it can be accessed in the view. The thing that causes the data to be updated doesn't really matter so long as it calls $rootScope.$apply() after changing the data so the view gets updated.

Check the script.js here for comments on what's going on http://plnkr.co/edit/ABQsAxz1bNi34ehmPRsF?p=preview

To note in that last plunkr the method of setting up watch in a controller to watch the service is not recommended since it makes testing more difficult and is just unnecessary, see the first post as well where I had decided that was no longer a good idea and changed to using angular.copy.

Upvotes: 2

Related Questions