23

In Angular2 (Beta 6) I have a component for a main menu.

<mainmenu></mainmenu>

I want to bind a boolean for wide or narrow. So I made it into this:

<mainmenu [(menuvisible)]="true"></mainmenu>

But what I want (I think) is to bind to a javascript class property (as I may have other things to bind but want to be tidy by using a single class in the component).

I get an error

EXCEPTION: Template parse errors: Invalid property name 'menumodel.visible' ("

][(menumodel.visible)]="menumodel.visible">

If I try the same with a single variable instead of a class I get:

Template parse errors: Parser Error: Unexpected token '='

However this (one way binding?) does seem to work (but I might want to trigger the menu to go wide/narrow from another component so felt this should be a two-way data bound property):

<menu [vis]="true"></menu>

This is a bit of my menu component:

@Component({
    selector: 'menu',
    templateUrl: './app/menu.html',
    providers: [HTTP_PROVIDERS, ApplicationService],
    directives: [ROUTER_DIRECTIVES, FORM_DIRECTIVES, NgClass, NgForm]
})
export class MenuComponent implements OnInit {

    mainmenu: MainMenuVM;

    constructor(private _applicationService: ApplicationService) {
        this.mainmenu = new MainMenuVM();
    }

    // ...ngOnInit, various functions

}

Here is my MainMenu View Model class

export class MainMenuVM {
    public visible: boolean;
    constructor(
    ) { this.visible = true; }
}

I'm trying to create a menu which has icons and text, but can go narrow to just show icons. I will emit this event upwards to a parent component to alter the position of the container next to the menu. Triggering a content container to maximised will trigger the menu to go narrow - I am not saying this is the best way, but I would like to resolve this particular question before going deeper.

Please note: I am not databinding to an input control here - just databinding to a component so I can then modify the UI.

This is from the Angular cheatsheet

<my-cmp [(title)]="name">   
Sets up two-way data binding. Equivalent to: <my-cmp [title]="name" (titleChange)="name=$event">

Thanks in advance!

UPDATE

Integrating the code from the accepted answer and adapting for my particular use case here the final working code:

app.html

...header html content

// This is what I started with
<!--<menu [menuvisible]="true" (menuvisibleChange)="menuvisible=$event"></menu>-->

// This is two way data binding
// 1. Banana-in-a-box is the input parameter
// 2. Banana-in-a-box is also the output parameter name (Angular appends it's usage with Change in code - to follow shortly)
// 3. Banana-in-a-box is the short hand way to declare the commented out code
// 4. First parameter (BIAB) refers to the child component, the second refers the variable it will store the result into.
// 5. If you just need an input use the remmed out code with just the first attribute / value
<menu [(menuvisible)]="menuvisible"></menu>

.. div content start 
<router-outlet></router-outlet>
.. div content end 

app.component.ts (root)

export class AppComponent implements OnInit{
   menuvisible: Boolean;
}

menu.component.ts (child of root)

export class MenuComponent implements OnInit {
    // Parameters - notice the appending of "Change"
    @Input() menuvisible: boolean;
    @Output() menuvisibleChange: EventEmitter<boolean> = new EventEmitter<boolean>();

    // Init
    ngOnInit() {
        // Populate menu - fetch application list       
        this.getApplications();

        // Initially we want to show/hide the menu depending on the input parameter
        (this.menuvisible === true) ? this.showMenu() : this.hideMenu();
    }

    //...more code
}

menu.html

<div id="menu" [ngClass]="menuStateClass" style="position: absolute; top:0px; left: 0px;z-index: 800; height: 100%; color: #fff; background-color: #282d32">
    <div style="margin-top: 35px; padding: 5px 0px 5px 0px;">

        <ul class="menuList" style="overflow-x: hidden;">
            <li>IsMenuVisible:{{menuvisible}}</li>
            <li style="border-bottom: 1px solid #3d4247"><a (click)="toggleMenu()"><i class="fa fa-bars menuIcon" style="color: white; font-size: 16px;"></i></a></li>
            <li *ngFor="#app of applications">
                <a [routerLink]="[app.routerLink]">
                    <i class="menuIcon" [ngClass]="app.icon" [style.color]="app.iconColour" style="color: white;"></i>
                    <span [hidden]="menuStateTextHidden">{{ app.name }}</span>
                </a>
            </li>
        </ul>

    </div>
</div>

Remember to import what you need e.g.

import {Component, EventEmitter, OnInit, Input, Output} from 'angular2/core';

Highly recommend this video on You Tube: Angular 2 Tutorial (2016) - Inputs and Outputs

8
  • I find you question quite confusing. The error messages IMHO don't fit with the provided code. It's not clear to me what you actually want to do. <mainmenu [(menuvisible)]="true"></mainmenu> doesn't make much sense. Why would you want two-way-binding to true? This also doesn't make sense [(menumodel.visible)]="menumodel.visible". You can't have a property with a . and you can bind to a subproperty this way. Commented Feb 25, 2016 at 22:29
  • I have updated the question with a snippet from the Angular Cheat Sheet. It shows a component with two way data binding. So I am guessing I would need to do what I find in the cheat sheet coupled with your answer below. e.g. <mainmenu [(menuvisible)]="true"></mainmenu> ? Commented Feb 25, 2016 at 22:35
  • menuvisible and vis are single properties. menumodel was a class based variable, as was mainmenu. The code has been evolving as I've written this question apologies. Commented Feb 25, 2016 at 22:37
  • This is too confusing for me. IMHO you could remove all what you have tried and try to explain what you actually want to accomplish. Please add the a minimal implementation of the components you want to use and show how they should be related (which is parent, which is child, ...). Commented Feb 25, 2016 at 22:39
  • The question is literally how do I create a two way binding property on a component as written in Angular Cheat Sheet <my-cmp [(title)]="name"> Commented Feb 25, 2016 at 22:42

2 Answers 2

33

For two-way binding you need something like:

@Component({
    selector: 'menu',
    template: `
<button (click)="menuvisible = !menuvisible; menuvisibleChange.emit(menuvisible)">toggle</button>
<!-- or 
   <button (click)="toggleVisible()">toggle</button> -->
`,
    // HTTP_PROVIDERS should now be imports: [HttpModule] in @NgModule()
    providers: [/*HTTP_PROVIDERS*/, ApplicationService],
    // This should now be added to declarations and imports in @NgModule()
    // imports: [RouterModule, CommonModule, FormsModule]
    directives: [/*ROUTER_DIRECTIVES, FORM_DIRECTIVES, NgClass, NgForm*/]
})
export class MenuComponent implements OnInit {
    @Input() menuvisible:boolean;
    @Output() menuvisibleChange:EventEmitter<boolean> = new EventEmitter<boolean>();

    // toggleVisible() {
    //   this.menuvisible = !this.menuvisible;       
    //   this.menuvisibleChange.emit(this.menuvisible);
    // }
}

And use it like

@Component({
  selector: 'some-component',
  template: `
<menu [(menuvisible)]="menuVisibleInParent"></menu>
<div>visible: {{menuVisibleInParent}}</div>
`
  directives: [MenuComponent]
})
class SomeComponent {
  menuVisibleInParent: boolean;
}
Sign up to request clarification or add additional context in comments.

1 Comment

Should work almost unchanged. NgForm and NgClass don't need to be listed in directives anymore.
8

I've created a short plunkr.

ngModel Like Two-Way-Databinding for components

You have at least two possibilities to to create a two way databinding for components

V1: With ngModel Like Syntax, there you have to create a @Output property with the same name line the @Input property + "Change" at the end of the @Output property name

@Input() name : string;
@Output() nameChange = new EventEmitter<string>(); 

with V1 you can now bind to the Child Component with the ngModel Syntax

[(name)]="firstname"

V2. Just create one @Input and @Output property with the naming you prefer

@Input() age : string;
@Output() ageChanged = new EventEmitter<string>();

with V2 you have to create two attributes to get the two way databinding

[age]="alter" (ageChanged)="alter = $event"

Parent Component

import { Component } from '@angular/core';

@Component({
   selector: 'my-app',
   template: `<p>V1 Parentvalue Name: "{{firstname}}"<br/><input [(ngModel)]="firstname" > <br/><br/>
              V2 Parentvalue Age: "{{alter}}" <br/><input [(ngModel)]="alter"> <br/><br/>

              <my-child [(name)]="firstname" [age]="alter" (ageChanged)="alter = $event"></my-child></p>`
})
export class AppComponent { 
    firstname = 'Angular'; 
    alter = "18"; 
}

Child Component

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
   selector: 'my-child',
   template: `<p>V1 Childvalue Name: "{{name}}"<br/><input [(ngModel)]="name" (keyup)="onNameChanged()"> <br/><br/>
              <p>V2 Childvalue Age: "{{age}}"<br/><input [(ngModel)]="age"  (keyup)="onAgeChanged()"> <br/></p>`
 })
export class ChildComponent { 
     @Input() name : string;
     @Output() nameChange = new EventEmitter<string>();

     @Input() age : string;
     @Output() ageChanged = new EventEmitter<string>();

     public onNameChanged() {
         this.nameChange.emit(this.name);
     }

     public onAgeChanged() {
         this.ageChanged.emit(this.age);
     }
 }

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.