Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS component binding a function instead of emitting an event

Tags:

angularjs

I am building a simple view:

<tabulation tabulation-data="vm.tabs"></tabulation>

<div ng-switch="vm.activeTab.id">
    <account-details ng-switch-when="details"></account-details>
    <account-history ng-switch-when="history"></account-history>
    <account-summary ng-switch-when="summary"></account-summary>
    <account-dashboard ng-switch-when="dashboard"></account-dashboard>
</div>

Essentially, as I have it working now, tabulation will $emit an event to the parent account controller, which will update the vm.activeTab property to toggle through the different tab content.

A colleague of mine told me it may be more elegant to use bindings (&) on the tabulation component, which will use a function passed by the parent account component...

Unfortunately, I don't seam to understand how it functions:

Parent account controller:

function PocDemoContainerController($scope) {
    var vm = this;

    vm.tabs = [{
        label: 'Details',
        id: 'details'
    },
    {
        label: 'History',
        id: 'history'
    },
    {
        label: 'Summary',
        id: 'summary'
    },
    {
        label: 'Dashboard',
        id: 'dashboard'
    }];

    vm.activeTab = vm.tabs[0];

    // this is the function that I want to pass to the tabulate component
    vm.onClickTab = function (tab) {
        vm.activeTab = tab;
    };

    ...
}

Tabulate component html:

<tabulation tabulation-data="vm.tabs" on-click-tab="vm.onClickTab(tab)">
<div class="tabulation">

    <nav class="container">
        <button class="tabulation__mobile-toggle"
                ng-class="{'tabulation__mobile-toggle--is-open': vm.mobileTabulationIsOpen}"
                ng-click="vm.toggleMobileTabulation()">{{vm.activeTab.label}}</button>

        <ul class="tabulation__container"
            ng-class="{'tabulation__container--is-open': vm.mobileTabulationIsOpen}">

            <li class="tabulation__item"
                ng-repeat="tab in vm.tabs"
                ng-class="{'tabulation--is-active': vm.isTabActive(tab)}">

                <a id={{tab.id}}
                   class="tabulation__link"
                   ng-click="vm.onClick(tab)">{{tab.label}}</a>
            </li>
        </ul>
    </nav>    
</div>
</tabulation>

Tabulate controller:

...

module.exports = {
    template: require('./tabulation.html'),
    controller: TabulationController,
    controllerAs: 'vm',
    bindings: {
        tabulationData: '<',
        onClickTab: '&' // this should send data up, right?
    }
};

Tabulation controller:

function TabulationController($scope) {
    var vm = this;

    ...

    vm.onClick = function (tab) {
        vm.onClickTab(tab); // This is the function from the parent I want to call
    };

    ...
}

TabulationController.$inject = [
    '$scope'
];

module.exports = TabulationController;

So, the tabulation controller can see and call vm.onClickTab but the parameter value that is being passed is not passed to the parent account component controller...

How do I achieve this? (is it even possible that way?)

like image 390
justinledouxweb Avatar asked May 17 '16 13:05

justinledouxweb


1 Answers

Alright! I finally found out how to do it, and it was not at all intuitive (the angular docs don't tell you this).

Based on the example above, my tabulation needs to tell the parent component which tab is now active so that it can update the view.

Here is how:

  • Declare a function on your parent component:
    • vm.onClickTab = function (tab) { ... };
  • Put the function on your child component.
    • <tabulation on-click-tab="vm.onClickTab(tab)"
    • IMPORTANT: If you are passing an argument, use the same name as the one you will define in your child component.
  • Inside your child component, declare a new function, and it is that function that should call the parent's callback.
    • vm.onClick = function (tab) { ... };
  • Now, here comes the part that is not mentioned anywhere: You have to call the parent's callback function using an object, with a property that uses the same name defined when passing that callback to the child component:

.

function TabulationController($scope) {
    ...
    vm.onClick = function (tab) {
        // The onClickTab() function requires an object with a property
        // of "tab" as it was defined above.
        vm.onClickTab({tab: tab});
    };
    ...
}
  • Now, when vm.onClick() is called, it calls the parent's callback with an object, passing it the argument, and the parent can now use that data to update the view.
like image 177
justinledouxweb Avatar answered Oct 17 '22 00:10

justinledouxweb