Search code examples
angularangular-directive

Click Binding from Component Template to Update HTML Class Angular2


I'm having difficulties figuring out how to pass updated values from a click binding from one template to another. Essentially, I'm creating a component repository application where a user would be able to view a component and update options for it on the page. Most of these options are shareable between components, so I have created separate files to store these commonly used options.

With the example below, the default col-xs-4 class is being added to the component as intended, but the click event does not update the class, even though the event itself works, as logging it updates the columns value within the class. It seems that I need some sort of event listener for it to update, but I'm not sure how to handle that.


content-tout.html

<div class="row">
    <div [ng-class]="columnAmount" class="content-tout">
        Tout
    </div>
</div>

<hr />

<h2>Options</h2>
<p>Use the following buttons to update the values in the component above.</p>
<options></options>

content-tout.ts

import { Component, View, NgClass } from 'angular2/angular2';

// this is option for changing column class from the ng-class in the template
import { ColumnsOption } from '../../shared/options/columns';


@Component ({
    selector: 'contentPlaceholder',
    bindings: [ColumnsOption]
})

@View ({
    templateUrl: '/app/components/content-tout/content-tout.html',
    directives: [ColumnsOption, NgClass]
})


export class ContentTout {
    private columnAmount: string;

    constructor(private columnsOption:ColumnsOption) {
        this.columnAmount = this.columnsOption.columns;
    }
}

columns.ts

import { Component, View, NgFor } from 'angular2/angular2';

@Component ({
    selector: 'options'
})

@View ({
    template: `
        <hr />
        <h3>Columns / Width</h3>
        <nav>
            <button *ng-for="#column of columnsCollection" (click)="changeColumns(column)" class="btn btn-primary-outline" type="button">
                {{ column }}
            </button>
        </nav>
    `,
    directives: [ NgFor ]
})


export class ColumnsOption {

    private columnsCollection: Array<string> = [
        'col-xs-1', 'col-xs-2', 'col-xs-3', 'col-xs-4', 'col-xs-5', 'col-xs-6', 'col-xs-7', 'col-xs-8', 'col-xs-9', 'col-xs-10', 'col-xs-11', 'col-xs-12'
    ];
    public columns: string = "col-xs-4";


    private changeColumns(column: string) {
        this.columns = column;
    }
}

I have done much searching with click bindings in Angular2 and have only come across resources where the the example is within the same view as the binding. I'm hoping this modular approach is possible, so if anyone has any resources for me to read up on, or a particular display from the API preview, I'd very much appreciate it.


Solution

  • Like you said, the example in my comment it's not that different. I'm just using @Host() to get a reference to the parent component in the child.

    This is the parent and we only add a method that will modify its own column property which will be called by the child's method with the same name (just for convenience, you can call it as you wish)

    @Component({
      template : `
         <div [class]="columns">{{columns}}</div>
         <columns></columns>
      `,
      directives : [Columns]
    })
    export class App {
      columns = "col-xs-4";
      changeColumn(column) {
        this.columns = column;
      }
    }
    

    Then in the child component the only difference, again, like you said is this code snippet

    constructor(@Host() @Inject(forwardRef(() => App)) app: App) {
        this.app = app;
    }
    
    changeColumns(column) {
        this.app.changeColumn(column);
    }
    

    In the constructor I save the reference to the parent component and then in the child's method changeColumns I call the parent's method changeColumns.

    Now, probably you were asking What in the world is Inject [1] and forwardRef [2] and why do I need them?. The answer is pretty simple : The child component doesn't know of its parent (App) since it doesn't exist when it's calling it. So we need them to get a reference to a class that will exist. [3]

    But maybe you want to avoid using them, how? Change the order of the classes. See this little example

    (Child calls Parent but it doesn't exist yet = Fail)
    class Child  {}
    class Parent {}
    
    // So move parent to be above of its Child
    class Parent {}
    class Child {}
    

    In the latter case you can save a few keystrokes by calling the parent as simple as follows

    constructor(@Host() app: App) {
        this.app = app;
    }
    

    Adding the plnkr in the example to be part of the answer : example plnkr

    I hope I was clear enough :)

    Reference

    [1] Documentation for @Inject()

    [2] Documentation for forwardRef

    [3] Classes in javascript aren't hoisted