Separate two-way data binding in Angular - javascript

I have a custom table component that expects a model for some row selection actions that can be two-way bound like so:
<my-table [(selected)]="selectedRows"></my-table>
Optionally, I can also simply pass an item via one-way data binding if I don't care about the changes that happen to that model:
<my-table [selected]="selectedRows"></my-table>
If I want to not have a two-way bound data item, and instead want to have a data item I pass down to the table component via one-way data binding, and a handler/event emitter so that the syntax ends up not to different than this:
<my-table [selected]="selectedRows" (selected)="handleSelectedChanged($event)"></my-table>
Is it possible with no, or minimal changes to the my-table component? Or do I have to create an #Output parameter on the my-table component to which I pass handleSelectedChanged($event)?
Thank you!

No you don't need to do any changes inside the my-table component table. Only when you want to use custom event to be emitted use (selectedChange) instead of (selected) that's it. I hope you already had Input binding selected and Output binding selectedChange in a place inside my-table component. Also selected change property binding is completely optional.
<my-table
[selected]="selectedRows"
(selectedChange)="handleSelectedChanged($event)">
</my-table>
If you're wondering how two way binding stuff needed to have Change suffix on event, because that's by design. For understanding it more clearly you can have a look at angular ngModel directive as well.
<input [ngModel]="myModel" (ngModelChange)="myModel = $event" />
// You can also do assignment by calling function
<input [ngModel]="myModel" (ngModelChange)="inpuChanged($event)" />
can be written as
<input [(ngModel)]="myModel" />

parent.component.html
<my-table [selected2]="selectedRows" (selected)="handleSelectedChanged($event)"></my-table>
parent.component.ts
handleSelectedChanged(event){
// console.log(event)
this.selectedRows = event
}
my-table.component.ts
#Input() selected2: any;
#Output() selected: EventEmitter<any> = new EventEmitter();
OnCLCICK(){
this.selected.emit({'key':'value'})
}
or :--
user.service.ts
#Output() selected: EventEmitter<any> = new EventEmitter();
setselected(data) {
this.selected.emit(data);
}
getselected() {
return this.selected;
}
my-table.component.ts
#Input() selected2: any;
constructor(
public user: Userservice
){
}
onclick(){
this.user.setselected({'key','val'})
}
parent.component.html
<my-table [selected2]="selectedRows"></my-table>
parent.componnet.ts
constructor(
public user: Userservice
){
}
ngOnInit(){
this.userService.getselected().subscribe((data) => {
this.getData(data);
});
}
getData(data){
console.log(data)
}

Your my-table.component.ts already has an #Input() and #Output() implemented i.e through selected and selectedChange() .
For a custom two way data binding , angular expects the event emitter and the variable to be something like this :
#Input() date : date ;
#Output() dateChange : EventEmitter<Date> = new EventEmitter<Date>();
So when you use [(date)] , you have a two way data binding created .
if you don't want to use two way data binding you can omit the () in the [(date)] and just use [date] , and it will still behave like a normal parent child component communication .
If you want to listen to the changes and do some action to the date variable value , then you can use (dateChange) event emitter and bind it with a function so that you can listen for the changes .
Now to answer your question whether you have to create a new #Output() in my-table.component.ts , well you don't have to create any thing or add any event emitters to bind your handleSelectedChanged($event) as an Event Emitter is implemented through selectedChange() . All you have to do now is :
<my-table [selected]="selectedRows" (selectedChange)='handleSelectedChanged($event)'></my-table>
So now you have selectedRows as an input and selectedChange as an output that emits an event if anything changes in selected and the event is passed through handleSelectedChanged() .

Related

angular 2 equivalent of vuejs v-bind

As title implies i'm looking for a way to bind an object with multiple properties to component #Inputs without having to explicitly write all of them
Let's say I have this object
let params = {
delta: 0.2,
theta: 2.3,
sigma: 'foo',
}
Instead of having to bind all of them individually like this
<my-component
[delta]="params.delta"
[theta]="params.theta"
[sigma]="params.sigma"/>
I would like bind all of them at once.
<my-component [someDirectiveIDontKnow]="params"/>
How can i do this?
Found a link to a previously asked question but couldn't get that to work properly.
Edit:
I'm not asking how to bind #Inputs. Imagine that the component I'm rendering has 40 #Inputs and I'm NOT allowed to rewrite it to just accept one #Input that could contain all the params.
So writing a template that uses this component gets really ugly and big.
....
<my-component
[param1]="params.param1"
[param2]="params.param2"
[param3]="params.param3"
[param4]="params.param4"
[param5]="params.param5"
[param6]="params.param6"
[param7]="params.param7"
[param8]="params.param8"
[param9]="params.param9"
[param10]="params.param10"
[param11]="params.param11"
[param12]="params.param12"
[param13]="params.param13"
[param14]="params.param14"
... and so on ....
/>
....
In my opinion, It would be best to define them all in a model
You would start with the following model
params.model.ts
import {SomeOtherModel} from './some-other.model'
export interface ParamsModel {
paramName1: string;
paramName2?: string;
paramName3?: number;
paramName4: SomeOtherModel;
}
Then in your component, you can force your input to take a specific model argument
my.component.ts
import {ParamsModel} from './params.model';
#Component({..})
class MyComponent {
#Input() params: ParamsModel;
}
app.component.html
<my-component params="paramsModel"></my-component>
app.component.ts
import {ParamsModel} from './params.model';
#Component({..})
class AppComponent implements OnInit {
paramsModel: ParamsModel;
ngOnInit(): void {
this.paramsModel = <ParamsModel>{someValue: someValue};
}
}
this way you have full code completion.
do note though! Angular does not deepwatch the contents, so changing the contents inside the Params object, will still have the same object ID in javascript, causing angular to not see the changes.
There are a few work-around for this
1: Bind every param (this is exactly what you do not want)
2: When changing contents of the model, destroy the instance and create a new instance everytime, you could do this by adding a constructor in the model and convert it olike this code
export class ParamsModel {
paramName1: string;
paramName2?: string;
paramName3?: number;
paramName4: SomeOtherModel;
constructor(config?: ParamsModel) {
Object.assign(this, config);
}
}
// first init
this.paramsModel = new ParamsModel(<ParamsModel>{someValue: someValue});
// updated init
this.paramsModel = new ParamsModel(this.paramsModel);
this.paramsModel.changedValue = changedValue; // (could also use an extend function on original params model)
3: Create an observer with events and trigger update events on the other side
4: use ngDoCheck to perform your own check if the contents changed
There is no generic directive to pass input properties in Angular. However, Angular supports binding any valid JavaScript type be it objects, arrays or primitives.
In the template
<my-component [params]="params"/>
In the class you have to use the #Input decorator to mark an object as an input. You can access it's value in any of the lifecycle hooks, some shown below. Note that params will not be set inside the constructor as view binding is performed after the class is instantiated.
class MyComponent {
#Input()
params: any
constructor() { } // <-- params not set
ngOnChanges() { } // <-- anytime params changes
ngOnInit() { } // <-- once when the component is mounted
}

two way data binding between components without service

I am newbie in angular 2+, I want to achieve two way data binding between components my need is to pass just one variable's value so creating service won't be good option looking for alternatives for the same.
If you want to pass values between two components you can use #Input and #Output event emitters else you can use sessionStorage or localStorage apart from services.
Follow This approach:
1)Import your component(from where you want value of variable) in second component.
2)Declare it in your constructor and then use its function or variable.
Component1.ts
import { component2name } from '../component2.ts';
export class Component1 implements OnInit {
component1: any;
constructor( private comp2: component2name ) { }
this.component1 = this.comp2.function-or-varaible;}
I hope this approach will help you out. If you have any question related to this approach please comment i will answered you.
To achieve two-way data binding between components, create #Input and #Output variable for the same property.
export class CustomComponent {
#Input() myvar:any;
#Output() myVarChange = EventEmitter<any>();
}
https://angular-2-training-book.rangle.io/handout/components/app_structure/two_way_data_binding.html

Detect changes of components in Angular 4

I'm working in a component and i need to detect if there's any change inside the component, or if has been changes in the variables... The thing is that based on that, i will do something... Is it possible?
For example, this is my code:
public var1: boolean;
public var2: string
and this is my HTML
<component-boolean [(ngModel)]="var1"></component-boolean>
<component-string[(ngModel)]="var2"></component-string>
So i need to detect inside of my component, that there's have been changes based on the value of all the variables.
Of course, i need it to be dynamic and not to have to declare the change detection for every variable becouse i won't know the amount of variables.
You want two-way-binding with #Input and #Output. With #Input data flows to the child, whereas #Output emits events to parent.
We can create a two-way-binding combining these two, where #Output variable has the same name as the #Input variable + the suffix Change (important!)
So mark your child tags with:
<component-boolean [(bool)]="var1"></component-boolean>
<component-string [(str)]="var2"></component-string>
and in your component-boolean:
#Input() bool: boolean;
#Output() boolChange = new EventEmitter<boolean>();
and when you make changes to this value, just emit that change and parent will catch it!
this.bool = true;
this.boolChange.emit(this.bool)
Implement this in the similar manner for your other component.
If you do not want the two-way-binding, you can have a different name for the #Output and trigger event:
#Output() boolHasChanged = new EventEmitter<boolean>();
this.boolHasChanged.emit(this.bool)
Child tag:
<component-boolean [bool]="var1"
(boolHasChanged)="boolHasChanged($event)">
</component-boolean>
and parent TS:
boolHasChanged(bool: boolean) {
console.log(bool)
}
You have to Output an event if something changes inside your component:
#Output() booleanChanges: EventEmitter<string> = new EventEmitter();
If something changes just emit an event like that:
this.booleanChanges.next(whatever);
And then you can change your HTML like that:
<component-boolean [booleanChanges]="var1"></component-boolean>

Emit search input value to child components in Angular 2

In an Angular 2 app, I have an HTML search input whose value I want to send to 3 child components, only if the user stops typing for 300ms and he typed something different than what input contained. I've read some articles on the Internet and all of them mention the use of Subject, debounceTime and distinctUntilChanged operators. Following one of the tutorials, I ended up with the following code:
PARENT COMPONENT:
// Subject to emit an observable
public searchUpdated: Subject <string> = new Subject<string>();
#Output() searchChangeEmitter: EventEmitter <any> = new EventEmitter <any>();
constructor() {
[.......]
this.searchChangeEmitter = <any>this.searchUpdated.asObservable()
.debounceTime(300)
.distinctUntilChanged(); // accept only relevant chars
}
onSearchType(value : string) {
this.searchUpdated.next(value); // Emit the changed value to the subscribers
}
PARENT TEMPLATE:
<input class="input-group-field" type="search" id="inputSearch" name="inputSearch" (keyup)="onSearchType($event.target.value)">
I want to pass the emitted value to the child components but I'm feeling a little lost. I've tried this but it didn't work:
PARENT TEMPLATE
<app-object-tree [searchFilter]="searchChangeEmitter"></app-object-tree>
(And, in the child component 'app-object-tree', create the #Input 'searchFilter' and subscribe to it to try to get the value.)
It makes more sense to me to create a service, provide it in the parent component and subscribe to it on the child components, but I don't know how to call the service 'setter' function after applying the 'debounceTime' and 'distinctUntilChanged' operations.
Thanks a lot, and sorry if I didn't explain myself correctly, I'm still trying to tie all the Subject/Observable concepts in my mind.
Why do you need an observable or EventEmitter for this? I don't think it's possible to emit events TO children. But you can simply pass a variable. In parent controller:
searchValue: string;
private _searchSubject: Subject<string> = new Subject();
constructor() {
[.......]
this._searchSubject
.debounceTime(200)
.distinctUntilChanged()
.subscribe(result => {
this.searchValue = result;
});
}
onSearchType(value : string) {
this._searchSubject.next(value);
}
In parent template:
<input class="input-group-field" type="search" id="inputSearch" name="inputSearch" (keyup)="onSearchType($event.target.value)">
<app-object-tree [searchFilter]="searchValue"></app-object-tree>
The children's #Input value (searchFilter) will now update every time the searchValue in parent changes. There's no need to observe anything in children, it will just get updated.

How to provide data when emiting via EventEmitter in Angular 2?

According to this answer, I can define an event emitter to my output.
export class NavComponent {
#Output() poof: EventEmitter<any> = new EventEmitter();
onClick():void { this.poof.emit(null); }
}
Then, I can emit the event to the markup according to the below.
<navbar (poof)="catcher.boom()"></navbar>
<compa #catcher></compa>
I tried to provide an object instead of null using this.poof.emit({}); and altering the markup by catcher.boom(input). That didn't work and the console tells me that the input is undefined.
How can I provide an object using the setup above? Is there a wiser approach?
You need to use the implicit $event variable that provides the emitted value
<navbar (poof)="catcher.boom($event)"></navbar>

Categories