6

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?)

2
  • do in your directive controller: vm.onClickTab({tab: tab}); Commented May 17, 2016 at 13:51
  • @devqon unfortunatly that does not work. This is what I get Object {tab: undefined}. Commented May 17, 2016 at 13:55

3 Answers 3

18

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.
Sign up to request clarification or add additional context in comments.

2 Comments

Alternatively, pass a callback in via a one-way binding. See blog.krawaller.se/posts/dissecting-bindings-in-angularjs or discussion.
AWESOME! The template should contain the names of the keys your passing in the object! Thank you very much!
0

Looking at your Tabulate component html I wonder what is the tab parameter you are sending. Is it a local property of your controller? it seems not because there is no vm prefix to it (or any other name you've defined). Your code seems legit, it is the parameter origin that is not clear, therefore undefined. Give us a hint on its origin for further analysis.

12 Comments

That is what I am trying to figure out... In the angular component documentation, they are using this form... but I too don't understand where that value comes from.
Let me see if I understood you correctly - when clicking on tabulation element, you want to pass the tab id (or other unique value) to the callback function, correct? If that's the case you have to pass some value, either an index value or an Id for this tab. The parent controller will use this Id to select the right tab from its tab array (if that is the design you've implemented).
Yes, you understand the goal. And this is what the code is currently doing. If you look at the vm.onClick method in the TabulationController, you'll that it is calling the callback function and passing the tab object. The problem is that the parent controller get's undefined.
I also tried passing only the function without calling it, thinking that I would then call it inside my tabulation controller but that didn't work out either...
As far as I can see there is no tab object. Only a template and a controller. The only thing that can help the parent understand which tab was clicked is to send a unique id(number, string, etc).
|
0

I had a similar problem and found this solution. I'm not sure if it's the best one but it works.

In the parent controller I call my component

<contact button-action="vm.select(targetContact)"/>

And define my function

function select(contact) {...}

In my contact component I define the binding:

bindings: {  buttonAction: '&' }

And call the function

 <button type="button" ng-click="$ctrl.buttonAction()">Click me</button>

When I click on my component button, select function is called passing the targetContact

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.