Use Binded data on the logic part on LIT - data-binding

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.

Related

Clarity Datagrid column input filter losing focus on first keypress after moving to next page in paginated grid

Using a clarity datagrid version 2.3
Seeing an issue where if the user starts typing into the input field of datagrid column filter, the filter input focuses out automatically as soon as a key is pressed.
Since the datagrid is paginated and server driven, this causes the API to get fired as soon as a
key is pressed after the debounce time.
The automatic focus out of the input field cause the filter to only have a single character and the API gets triggered since the debouce is only 800.
Have looked at clarity github for any reported issues, doesn't look like its reported or anyone having similar issue.
Expected behavior should be the input focus out should not happend until the user moves the cursor away or presses enter, which is when the debounce should kickin after which the api should be called.
HTML:
<clr-datagrid
(clrDgRefresh)= refreshDataGrid($event)>
...
</clr-datagrid>
TS Component:
debouncer = new Subject<any>();
ngOnInit() {
this.debouncer.asObservable().pipe(
debounceTime(800)
).subscribe(state => {
// do something here.. like call an API to filter the grid.
})
}
refreshDataGrid(state) {
this.debouncer.next(state);
}
Any help is appreciated.
Currently I'm hacking my component, to make sure the focus is not lost on the input field until done so by the user.
refreshDataGrid(state) {
const isClrFilterInputField = document.querySelector('.datagrid-filter .clr-input');
if (isClrFilterInputField instanceof HTMLElement) {
isClrFilterInputField.focus();
}
this.debouncer.next(state);
}
This is still not a clean answer, but as far as I have searched, this seems like an issue with clarity datagrid itself, until I hear from someone with a cleaner answer.
Most likely the upgrade version might have this fixed.
Yet to check that.
Unfortunately I think we designed the datagrid to emit the changes on each filter value change with debouncing intended to be done on the app side as consumers see fit.
That said, it is possible to accomplish what you describe. I've implmented a quick and dirty guard based on events but there may be better ways. I'll add code snippets here and a link to the working stackblitz at the end.
You are on the right track with the debouncer. But we don't need to debounce with time, we only need to 'debounce' on certain events.
Instead of debouncing with time, what if we debounce with an #HostListener for clicks on the filter input? (I'll leave it as an exercise for you to implement a HostListener for the focusin event since focusin bubble's up and blur does not). To do that we need:
A Hostlistener that can hear keydown.enter event on the filter input
A guard to prevent requests
A property to store the datagrid state as user enters text
In general the code needs to:
Fetch data when component inits but not after unless directed
Keep track of state events that get emitted from the datagrid
listen to keydown.enter events (and any other events like the filter input focusout - becuase it bubbles up, unlike blur)
Check that the event was generated on a datagrid filter input
dismiss the guard
make the request
re-enlist the guard
Here is a rough attempt that does that:
export class DatagridFullDemo {
refreshGuard = true; // init to true to get first run data
debouncer = new Subject<any>(); // this is now an enter key debouncer
datagridState: ClrDatagridStateInterface; // a place to store datagrid state as it is emitted
ngOnInit() {
// subscribe to the debouncer and pass the state to the doRefresh function
this.debouncer.asObservable().subscribe(state => {
this.doRefresh(state);
});
}
// a private function that takes a datagrid state
private doRefresh(state: ClrDatagridStateInterface) {
// Guard against refreshes ad only run them when true
if (this.refreshGuard) {
this.loading = true;
const filters: { [prop: string]: any[] } = {};
console.log("refresh called");
if (state.filters) {
for (const filter of state.filters) {
const { property, value } = <{ property: string; value: string }>(
filter
);
filters[property] = [value];
}
}
this.inventory
.filter(filters)
.sort(<{ by: string; reverse: boolean }>state.sort)
.fetch(state.page.from, state.page.size)
.then((result: FetchResult) => {
this.users = result.users;
this.total = result.length;
this.loading = false;
this.selectedUser = this.users[1];
// Set the guard back to false to prevent requests
this.refreshGuard = false;
});
}
}
// Listen to keydown.enter events
#HostListener("document:keydown.enter", ["$event"]) enterKeydownHandler(
event: KeyboardEvent
) {
// Use a host listener that checks the event element parent to make sure its a datagrid filter
const eventSource: HTMLElement = event.srcElement as HTMLElement;
const parentElement = eventSource.parentElement as HTMLElement;
if (parentElement.classList.contains("datagrid-filter")) {
// tell our guard its ok to refresh
this.refreshGuard = true;
// pass the latest state to the debouncer to make the request
this.debouncer.next(this.datagridState);
}
}
refresh(state: ClrDatagridStateInterface) {
this.datagridState = state;
this.debouncer.next(state);
}
}
Here is a working stackblitz: https://stackblitz.com/edit/so-60980488

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();
}

Bidirectional data binding on a component input property

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>

Making Superclass Fields Observable

I am trying to create a custom 'radial-selector' element using Polymer in Dart.
My current goal is to make a 'children' field for the RadialSelector class that is observable, so that I can do a template loop through the children and filter through those that are only of a certain element type.
It is not clear how I can make the 'children' field observable without creating another variable that does not conflict with it in terms of namespace. For example, I could just create a "childs" field, make it observable, and then set it equal to this.children. But this seems awkward and boilerplate.
Is there a way for my RadialSelector class, which extends the PolymerElement class, to make its superclass 'children' field observable? Currently when I just write #observable children, Dart (understandably) thinks I'm just creating a new 'children' variable.
Thank you and let me know if there is something I could make clearer! :)
Here is the Dart Code:
import 'package:polymer/polymer.dart';
import 'dart:html';
#CustomTag('radial-selector')
class RadialSelector extends PolymerElement {
#published String img;
#observable children;
RadialSelector.created() : super.created() {
print("CONSTRUCTING RADIAL SELECTOR");
print(children);
}
filterByType(Type t, List<Element> elems) {
print("FILTER BY TYPE CALLED");
List<Element> newElems = new List<Element>();
for (var e in elems) {
if (e.runtimeType == t)
newElems.add(e);
}
return newElems;
}
}
Here is the corresponding html:
<link rel="import" href="packages/polymer_ui_elements/polymer_ui_icon_button/polymer_ui_icon_button.html">
<polymer-element name="radial-selector">
<template>
<style>
:host {
display: block;
margin: 100px
}
</style>
<script>console.log("HEY");</script>
<div id="stem">
<img src = "{{img}}" alt ="Radial stem"/>
<template repeat = "{{child in children | filterByType(ImageElement)}}">
<polymer-ui-icon-button src = {{child.src}}></polymer-ui-icon-button>
</template>
</div>
</template>
<script type="application/dart" src="radialselector.dart"></script>
</polymer-element>
There is no way you can do this for children.
You could make an #observable getter/setter that redirects to the super class' children
#observable
NodeList get children => super.children;
set children(NodeList val) => super.children = val; // won't work
This has two flaws.
the children list is final (haven't tried but I'm sure you can't assign a new list.
it doesn't help you at all to have an #observable children.
You would only be notified when another list gets assigned (which is not possible because the field is final)
What you need is an observable list to get notified about list element changes (add/remove/replace).
For this you would need to replace children by toObservable(children) which is not possible because you can't replace the children collection.
What you can do is to use a MutationObserver. see Angular Dart - execute a function right after ng-repeat has finished to get notified about child node changes.
You can add a new field
#observable List myChildren;
and use the mutation observer to update this field whenever the children change.
void mutationCallback(...) {
...
myChildren = children.toList();
}
This way each time your children change myChildren get's an entire new list assigned and the view gets notified about this change due to the #observable annotation.
You don't need to make the list observable because there are no single add/remove/replace operations to observe.

flex: How to respond to change of data inside a component

I've created a custom component based on Image component. I want to teach it to respond to change of binded variable. E.g. if main class has a variable balance, I want the component to change image in case balance = 100 one image, in case balance = 50 to another.
Can somebody help to understand how to do that
I am using flex3 so don't see propertyWatcner there. Also I am using component2 from main mxml file this way
<MyComp:MyIcon left="15" top="20" width="60" height="60"
id="tower" price="100" accountState="{accountMoney}"
click="drawBuildingShadow(event)" />
And inside component MyIcon I want to be able to react changes of binded accountState variable.
Without any code to go by (please include a sample of both components if you can), there are a number of ways you could approach this.
You could add a ChangeWatcher which is bound to the variable in question, and invokes a method when the property changes. This method could change the image, based on whatever conditions apply to the property.
It would look something like this:
Component 1:
[Bindable]
public var yourVariable:Number;
Component 2:
private var propertyWatcher:ChangeWatcher;
//In some initialization method -- this is JUST an example method for how to create the property watcher
public function init(): void {
...
propertyWatcher = ChangeWatcher.watch(component1, "yourVariable", onVariableUpdate);
}
//Define this as a new method to handle when the property changes
private function onVariableUpdate(event:PropertyChangeEvent):void {
if(event.newValue == 50) {
yourImage.source = newSource;
}
else if(event.newValue == 100) {
yourImage.source = otherSource;
}
}
Obviously, this is very truncated and shorthand, but hopefully it will help get you started.
Edit: ChangeWatchers do exist in Flex 3, but it sounds like you should go in a different direction. Since the code snippet you posted is a bit small, I'm going to make a few assumptions on how you might do this :)
As alxx mentioned in his comment, you can change the property accountState in your component from an actual property, to a setter/getter. This will allow you to do more extensive processing when accountState gets updated.
It should look something like this:
MyComp:
//Inside your script tag:
private var _accountState:Number;
[Bindable]
public function get accountState():Number {
return _accountState;
}
public function set accountState(state:Number):void {
_accountState = state;
switch(state) {
case 50:
yourIcon.source = "blahblahblah";
break;
case 100:
yourIcon.source = "blahblahblah2";
break;
//And so on
}
}
This won't change the code you posted: it should still work as you've written it. I haven't tested this, so it's possible I'm missing something, but hopefully this will help :)

Resources