Bidirectional data binding on a component input property - data-binding

I am trying to make something work on angular2 and I am unable to find something about this behavior.
I have an application that implements a custom component like this one :
import {Component,Input} from 'angular2/core'
#Component({
selector:'my-comp',
template:`<input type="text" style="text-align:center; [(ngModel)]="inputText"> <p>{{inputText}}</p>`
})
export class MyComp{
#Input() inputText : string;
}
And I am trying to do a bidirectional databinding on my inputText variable from my component like this:
<my-comp [(inputText)]="testString"></my-comp>
Where the testString is a variable defined in the MyApp.ts which contains a string. I want my testString variable to be modified when my inputText is modified by the user.
Here is a Plunker with a simple sample code : https://plnkr.co/edit/zQiCQ3hxSSjCmhWJMJph?p=preview
Is there a way to make this works simply ? Do I have to implements an Angular2 class on my custom components and overload functions in order to make this works like an ngModel ? Do i necessarily have to create a inputTextChanged variable of EventEmitter type that emit my data when it's changed and do something like this :
<my-comp [inputText]="testString" (inputTextChanged)="testString = $event;"></my-comp>
Thank you in advance.

This is explained in the Template Syntax doc, in the Two-Way Binding with NgModel section:
<input [(ngModel)]="currentHero.firstName">
Internally, Angular maps the term, ngModel, to an ngModel input property and an ngModelChange output property. That’s a specific example of a more general pattern in which it matches [(x)] to an x input property for Property Binding and an xChange output property for Event Binding.
We can write our own two-way binding directive/component that follows this pattern if we're ever in the mood to do so.
Note also that [(x)] is just syntactic sugar for a property binding and an event binding:
[x]="someParentProperty" (xChange)="someParentProperty=$event"
In your case, you want
<my-comp [(inputText)]="testString"></my-comp>
so your component must have an inputText input property and an inputTextChange output property (which is an EventEmitter).
export class MyComp {
#Input() inputText: string;
#Output() inputTextChange: EventEmitter<string> = new EventEmitter();
}
To notify the parent of changes, whenever your component changes the value of inputText, emit an event:
inputTextChange.emit(newValue);
In your scenario, the MyComp component binds input property inputText using the [(x)] format to ngModel, so you used event binding (ngModelChange) to be notified of changes, and in that event handler you notified the parent component of the change.
In other scenarios where ngModel isn't used, the important thing is to emit() an event whenever the value of property inputText changes in the MyComp component.

I'll combine #pixelbits and #Günter Zöchbauer answers and comments to make a clear answer on my question if someone in the future is searching for this.
To make bidirectional data binding works on custom variables you need to creates your component based on the following.
MyComp.ts file :
import {Component,Input,Output,EventEmitter} from 'angular2/core'
#Component({
selector:'my-comp',
templateUrl:`<input type="text" style="text-align:center;"
[ngModel]="inputText" (ngModelChange)="inputText=$event;inputTextChange.emit($event);">`
})
export class MyComp{
#Input() inputText : string;
#Output() inputTextChange = new EventEmitter();
}
MyApp.ts file:
import {Component} from 'angular2/core'
import {MyComp} from './MyComp'
#Component({
selector:'my-app',
templateUrl:`<h1>Bidirectionnal Binding test </h1>
<my-comp [(inputText)]="testString"></my-comp><p>
<b>My Test String :</b> {{testString}}</p>`,
directives:[MyComp]
})
export class MyApp{
testString : string;
constructor(){
this.testString = "This is a test string";
}
}
There the bidirectional data binding to the inputText variable works correctly.
You can comment the answer for a more beautiful or simpler way to implement this code.

Your Plunker already contains the EventEmitter. The #Output() annotation is missing. To change the value call inputTextChanged.emit(newValue) (this also changes the value on inputText)

What I do is use a property, so when I change the data the change is emitted automatically
private _data: AnyType;
#Input() get data(): AnyType{
return this._data;
}
set data(value: AnyType) {
this._data = value;
this.dataChange.emit(this._data);
}
#Output() dataChange: EventEmitter<AnyType> = new EventEmitter();
In html you will bind the property using [(data)]
<comp [(data)]="getData()"></comp>

Related

Use Binded data on the logic part on LIT

I have been looking for information about variable binning. The problem is that I don't want to use those variables inside my HTML render, but in the component logic (i want to fetch some data). How do I declare it?
constructor(){
super();
this.url = "https://....";
}
render() {
return html`
<parent-component .url ="${this.url}"></parent-component>
`;
child component:
constructor() {
super();
fetch(this.url)
.then(response => response.json())
.then(data =>{this.ArrayData = data.results
this.requestUpdate()
})
}
Thank you very much
To bind data, declare a reactive property for properties that can trigger the reactive update cycle when changed, re-rendering the component.
From your example I'm not exactly sure when you want to trigger the fetch in the child component. I'll assume you want to fetch the data once with this.url passed from the parent as a property binding.
The issue I see in the code sample is that this.url will be undefined in the constructor of the child component, as the outer element needs to render and set the property .url on the child. Moving the logic in the constructor to the firstUpdated lifecycle callback should fix the issue, as this.url will now be defined.
An additional change I included in the code sample, is making this.ArrayData a reactive property, allowing the removal of this.requestUpdate.
Minimal example of fix in the Lit Playground.

why getting the focus trigs databinding in angular 5?

Developing a multiculture Angular5 app I've structured a class which contains a dictionary (custom ts class) to hold the translations. When the user changes the culture, anything related to it must change. It works but.... too much.
Since I use to console.log everytime the method which gets the right sentence according to the selected culture is called, I noticied that if I just click on a textbox, Angular updates everything and, incomprehensibly, if I click on nothing soon after just to leave the focus, again angular updates everything! I know there must be something related with ChangeDetectionStrategy but I tried to solve without any success.
When the app grows up, if the browser has to reload everything everytime... what a problem! The app looks like this:
a MainController.ts holds anything can be useful to the components so it is passed in every component constructor.
MainController looks like this:
#Injectable()
export class MainController extends BaseClass {
private currentCulture: string;
private platformLocalizedSentencies: KeyedCollection<LocalizedString>;
the class LocalizedString looks like this:
#Injectable()
export class LocalizedString extends BaseClass {
public DefaultText: string;
public IdTranslationIndex: number;
public Translations: KeyedCollection<string>;
constructor() {
super();
this.Translations = new KeyedCollection<string>();
}
public GetTranslation(culture: string) {
console.log('getting translation for ' + this.DefaultText + ' in culture ' + culture);
if (!this.Translations.ContainsKey(culture)) {
return '*' + this.DefaultText;
} else {
return this.Translations.Item(culture);
}
}
}
Now, there's a component (culture-selector.component.ts) which shows flags (changeDetection: ChangeDetectionStrategy.OnPush) and when the user select a flag this happens:
onCultureClick(menuItem: string) {
console.log(menuItem + ' clicked.');
this.mainController.CurrentCulture = menuItem; // this must be the thing which unleash the databinding on the other components I think and I hope
this.updateSelectedCultureUI(); // doesn't do anything special, just sets the right flag and culture name on the top of the control
this.mainController.trace(TraceType.Info, 'Culture ' + this.cultureName + ' clicked');
}
and the component where I noticied the hell wild by the databinding, the login component:
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css'],
})
export class LoginComponent extends BaseClass implements OnInit, AfterViewInit {
constructor(public mainController: MainController) {
super();
}
ngOnInit() {
this.usernamePlaceholder = this.mainController.PlatformLocalizedSentencies.Item('Username');
Now, "usernamePlaceholder" is used into the html like this:
<mat-form-field style="width: 100%;">
<input matInput [placeholder]="usernamePlaceholder.GetTranslation(mainController.CurrentCulture)" [(ngModel)]="loginInput.username" (keypress)="eventHandler($event.keyCode, 'txtUsername')" #txtUsername>
</mat-form-field>
in the following picture you can see into the console-window what happens if I just click into the username textbox.... I cleared the console before clicking. What is going on? I spent two days trying to undestand... sorry it's my first angular App.. I should have started with something easier :)
You can see username translation is get by ClassLibray.ts while the other sentencies by mainController.ts... this is because I tried different ways but same result... in mainController the code is almost the same:
.... and the method to retrieve the correct string
public GetPlatformSentence(key: string) {
console.log('getting translation for ' + key + ' in culture ' + this.CurrentCulture);
if (!this.PlatformLocalizedSentencies.ContainsKey(key)) {
return '[NOTRANSLATION]';
}
if (!this.PlatformLocalizedSentencies.Item(key).Translations.ContainsKey(this.CurrentCulture)) {
return '*' + this.PlatformLocalizedSentencies.Item(key).DefaultText;
} else {
return this.PlatformLocalizedSentencies.Item(key).Translations.Item(this.CurrentCulture);
}
}
Thank you anyway
here is what happens
Ok guys, I've tried anything without any success. Angular binding is... too pedantic :-)
The best result I was able to achieve was the refresh at first user-interaction. So I had to choose between refreshing everything everytime (which was too much) or refreshing as soon as the user clicked somewhere forcing the binding refresh (that was what I've been able to get playing with ChangeDetectionStrategy settings and some additional code).
So I solved (just for multicuture immediate binding) using a messageService. Every component subscribes and asks to a common maicontroller to update anything related on culture when it changes. Of course I needed to write e little bit more code but the result in terms of user experience, responsiveness and CPU load is very very nice. Also the executed code is the same for every component so it is not so bad.
Of course it isn't what I expected but, it works better for me.

angular2 data binding between service and component properties

I need some clarification on binding between service and component properties and data binding in angular2
assume i have a service(singleton) and a component
export class Service {
name = "Luke";
object = {id:1};
getName(){return this.name};
getObject(){return this.object};
}
export class Component implements OnInit{
name:string;
object:any;
constructor(private _service:Service){}
ngOnInit():any{
//Is this 2 way binding?
this.name = this._service.name;
this.object = this._service.object;
//Is this copying?
this.name = this._service.getName();
this.object = this._service.getObject();
}
}
If you update elements by reference (if you update something into the object property), you will see the updates in the view:
export class Service {
(...)
updateObject() {
this.object.id = 2;
}
}
If you update elements by value (if you update something into the name property), you won't see the updates in the view:
export class Service {
(...)
updateName() {
this.name = 'Luke1';
}
}
See this plunkr: https://plnkr.co/edit/w7bS0fAVjOc3utnpD39b?p=preview.
Angular binding only works for bindings declared in the view (HTML).
If you want properties in your component being updated when values in a service change, you need to take care of it yourself.
Observables make this easy. See detect change of nested property for component input for an example.
If you want properties in a component updates as soon as a value in change in a service changes:
Import DoCheck from #angular/core and your service into the
component.
Call the service functions affecting the component property in ngDoCheck(){...}
The component view will be updated automatically as soon as any changes
Something like this in your component:
ngDoCheck() {
this.qty = this.cartService.getTotalQtyInCart();
}

Angular2 two-way data binding

I know Angular2 doesn't have two-way data binding but is there a way to mimick the two-way data binding behavior from Angular1.x?
Note - scroll down the answer for ng-model binding
You could actually do that, just that you need to invoke internal changelistener tick (similar to digest) to update binding in the zone, You can just add a (keyup) event for that. Similarly you could use directive bindings as well with properties dictionary of component settings.
Example:-
<input #label (keyup)>
<!-- variable #label represented as the element itself and accessible as property on controller instance
You can even bind keyup to a function or another another function and pass value from the label property-->
Display as:
<p>{{label.value}}</P>
Parent Component has a textbox and a label.
import { Component, bootstrap} from '#angular/core';
import {Display} from 'display';
#Component({
selector: 'my-app',
template: `<p><b>Parent Component:</b><p><input #label (keyup) (change)="handleChange(label.value)">
<p>{{label.value}}</P> <display [text]="label"></display></p></p>`,
directives: [Display]
})
class MainComponent {
label: any;
constructor() {
}
handleChange(label){
this.label = label;
console.log(this.label);
}
}
Now displaying it in child component as well:
#Component({
selector: 'edit',
template: `<p><b>Child Component:</b></p>{{text.value}}`
})
export class Edit {
#Input() text:any;
}
Demo
Update - ng-model for 2-way binding
Though Angular2 is one-time bound by default, ngModel sugar has been introduced to achieve 2-way binding. With that you could do for instance:
<input ngControl="name" [(ngModel)]="name">
Here usage of square brackets ([..]) suggests the property binding and round brackets ((..)) for event binding. Basically when you use ng-model, you are enabling both the bindings ngModel is more of an event. Behind the scenes it creates an observable event(with EventEmitter) to track the value changes in the bound element and update the bound property respectively.
For example:-
Include formDirectives:
import {FORM_DIRECTIVES} from '#angular/common';
and with form
<form (ngSubmit)="onSubmit()" let-f="form">
<input ngControl="name" [(ngModel)]="name">
<button>Click me and check console</button>
</form>
without form
<input [(ngModel)]="name">
<button (click)="onSubmit()">Click me and check console</button>
not necessary anymore
include formDirectives dependency in view annotation.
#Component({
template: .....,
directives: [FORM_DIRECTIVES]
})
Demo
Also read the nice write up from Victor Savkin on Two-way binding in angular2 by creating the ng-model event and how it works.
You can now simply do this by using ngModel using the following syntax:
<input [(ngModel)]="myProp" />
The combination of the square and round brackets means "two-way binding".
Please see the plunk here
Yes there is two-way binding in angular2. See here: https://angular.io/docs/ts/latest/guide/template-syntax.html#!#ngModel
So, how to use it in custom components?
What I like to do is something this:
private currentSelectedItem: MachineItem;
#Output() selectedItemChange: EventEmitter<MachineItem> = new EventEmitter<MachineItem>();
#Input() set selectedItem(machineItem: MachineItem) {
this.currentSelectedItem = machineItem;
this.selectedItemChange.emit(machineItem);
}
get selectedItem(): MachineItem {
return this.currentSelectedItem;
}
And use it like
<admin-item-list [(selectedItem)]="selectedItem"></admin-item-list>
You can also emit the new value where it is actually changed. But I find it quite convenient to do that gloabaly in a setter method and don't have to bother e.g. when I bind it directly to my view.
You can do this by attaching to the events on the input field and updating the internal value as is done in this example:
http://plnkr.co/edit/lOFzuWtUMq1hCnrm9tGA?p=preview
Create a component that has an internal attribute that holds the label this.label and a callback changeLabel that expects an event object
#Component({
selector: 'app',
templateUrl: 'bound.html'
})
class App {
label: string;
constructor() {
this.label = 'default label'
}
changeLabel(event) {
this.label = event.target.value;
}
}
bootstrap(App);
The create your template and attach the callback to the appropriate event (you could attach it to the keypress event but then you might need a timeout. I attached it to the change event for simplicity (which means you might need to tab off the input to see the update).
<label for="myinput">{{label}}</label>
<input id="myinput" type="text"/>
<p></p>You can change the label above by typing something below</p>
<label for="labeltext">New Label Text</label>
<input type="text" id="labeltext" (change)="changeLabel($event)"/>
There is another way to trick Angular2 into two-way binding. Don't pass a property but an object into the component. If you pass an object via one-way binding all of its properties are in fact two-way bound.
It makes the component less versatile as it needs to know the object but in many cases it's still useful.
I have a component that looks like this:
import { Component, Input } from "#angular/core";
import { NgSwitch, NgSwitchWhen, NgSwitchDefault } from "#angular/common";
export class Movie
{
public Title: string;
public Rating: number;
public Seen: boolean;
}
#Component
({
selector: "hh-image-checkbox",
template: `
<div [ngSwitch]="movie.Seen">
<div *ngSwitchWhen="true">
<img src="/Content/res/CheckTrue.png" (click)="onClick()">
</div>
<div *ngSwitchDefault>
<img src="/Content/res/CheckFalse.png" (click)="onClick()">
</div>
</div>
`,
directives: [NgSwitch, NgSwitchWhen, NgSwitchDefault]
})
export class ImageCheckboxComponent
{
#Input() movie: Movie;
public onClick()
{
this.movie.Seen = !this.movie.Seen;
}
}
It is invoked like this:
<hh-image-checkbox [movie]="movie"></hh-image-checkbox>
The movie object itself is one-way bound but all of its properties can be used for two-way binding.
Here is a simple plunker which demonstrates one way, two way and event driven approaches in action according to Angular2 2.0.0-beta.17
http://plnkr.co/eXZMoU
Two-way Event and property
<input [(ngModel)]="name" />
One way property
<input [value]="name" />
Event driven
<input (input)="name=$event.target.value">
We can dig Angular docs for more
[UPDATE 1/26/2020]
Since Angular2 beta libs are removed from project CDN ! above plnkr link doesn't work anymore.
Use below new plnkr Angular 6+ page , I ported previous page to NPMJS, new angular edition and new plnkr!
http://next.plnkr.co/edit/4okdOSgw3SMvdktR?preview
From the Docs:
Two-way binding ( [(...)] )
You often want to both display a data property and update that property when the user makes changes.
On the element side that takes a combination of setting a specific element property and listening for an element change event.
Angular offers a special two-way data binding syntax for this purpose, [(x)]. The [(x)] syntax combines the brackets of property binding, [x], with the parentheses of event binding, (x).
[( )] = BANANA IN A BOX
Visualize a banana in a box to remember that the parentheses go inside the brackets.
For more information, see
Angular Developer Guide - Template Syntax - Two-way binding
Angular #angular/forms API Reference - ngModel Directive
Its simple, try this;
<input [(ngModel)]="property" placeholder="property Value"/>
<h1>{{property}}</h1>

How do I make sure the text of an ActionScript TextInput is updated when the Object property defining that text is updated?

Not an easy question to decipher, so let me boil it down. I'm trying to convert an MXML component to an ActionScript Class. The component consists of a Form with a TextInput, TextArea, and two buttons - Save and Cancel, and a Validator for the TextInput, and other logic to handle events that occur. This component is currently extended by several other components.
Now, in the MXML component binding the TextInput text property to a property in an Object was very easy:
<mx:TextInput text="{_itemToEdit.name}" />
But in ActionScript, I'm creating the TextInput and setting the text property before the Object is set, and the TextInput is not being updated:
public var itemToEdit:Object = {};
private var nameInput:TextInput = new TextInput();
public function MyClass()
{
nameInput.text = itemToEdit.name;
}
How can I make sure that the TextInput text property is bound to the specified property in the Object?
Binding is all about firing change events. you'll need to modify your 'itemToEdit' class to be an EventDispatcher for this hack to work. here goes
//item to edit class
private var _name:String;
public function set name(value:String):void
{
_name = value;
dispatchEvent(new Event("NAME_CHANGED"));
}
//whatever the binding class is
private var _itemToEdit:ItemToEdit;
private var _textField:TextField;
public function set itemToEdit(value:ItemToEdit):void
{
if (_itemToEdit) removeEventListeners();
_itemToEdit = value;
if (_itemToEdit) addEventListeners();
}
private function addEventListeners():void
{
_itemToEdit.addEventListener("NAME_CHANGED", itemToEdit_nameChangedHandler);
itemToEditChangedHandler(null);
}
private function itemToEdit_nameChangedHandler(event:Event):void
{
_textField.text = _itemToEdit.name;
}
Obviously this was done just for speed, you'll need custom events and some better names etc, but this is the basic jist.
Apparently it's slightly more complex than a simple assignment to bind purely in AS, here's a couple tutorial/docs to show you how to pull it off.
http://cookbooks.adobe.com/index.cfm?event=showdetails&postId=6802
http://raghuonflex.wordpress.com/2007/08/30/binding-in-mxml-as/
Compile your MXML component with the -keep option. Examine the ActionScript code that was generated by mxmlc and do something similar.
You may also do it using the Proxy object - I blogged about it over here: http://flexblog.faratasystems.com/?p=433
If "itemToEdit" is a pure AS3 Object, then the binding probably doesn't work properly anyway. That is, it will work when the object is initially created, but any changes to "name" in the object won't be detected. (I could be wrong...haven't done extensive tests)
Anyway, your problem is easy to solve with getters/setters:
private var _itemToEdit:Object;
public function get itemToEdit():Object { return _itemToEdit; }
public function set itemToEdit(value:Objecy):void {
_itemToEdit = value;
nameInput.text = value.name;
}
Binding isn't necessary here.

Resources