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>
Related
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
}
service.ts
clicked: string = '';
clickEvent(item){
this.clicked = ( this.clicked.length > 0 && this.clicked == item ) ? '' : item;
}
parent.component
constructor(private qqs: qqService) {}
clicked = this.qqs.clicked;
parent.html
<app-child [clicked]="clicked"></app-ou>
child.component
constructor(private qqs: qqService) {}
#Input() clicked;
click(item){
this.qqs.clickEvent(item);
}
child.html
<div class="a" (click)="click('some string')"></div>
If user click at child.html
child.component click() will work and pass some string to service
and service will change variable clicked
than parent. component get a new variable from service
and pass to all child by #Input()
Now my problem is a parent. component can't get a new variable from service
How to fix that?
With reference to Angular docs for component interacion
The child component exposes an EventEmitter property with which it emits events when something happens.
The parent binds to that event property and reacts to those events.
The child's EventEmitter property is an output property, typically adorned with an #Output decoration as seen in this ParentComponent
Here are the codes:
parent.component
constructor(private qqs: qqService) {}
onClicked(item) {
console.log(item)
}
parent.html
<app-child [clicked]="clicked" (onclicked)="onClicked($event)"></app-ou>
child.component
import { Component, EventEmitter, Input, Output } from '#angular/core';
constructor(private qqs: qqService) {}
#Input() clicked;
#Output() onclicked = new EventEmitter<any>();
click(item){
this.clicked.emit(agreed);
}
child.html
<div class="a" (click)="click('some string')"></div>
Problem with your code are following:
Service: whenever clickEvent it changes reference of clicked.
parent.component: you have saved reference of qqs.clicked in clicked(assuming clicked in your code refers to this.clicked), but whenever next time clickEvent will be called, the reference of qqs.clicked will be changed and inside parent clicked will have old reference.
Solution is either expose whole qqs to template by making it public in parent.component and in parent.html replace [clicked]="clicked" to [clicked]="qqs.clicked".
This will fix you problem, but i noted few more things:
In your parent.html <app-child [clicked]="clicked"></app-ou> starting tag is not matching end tag.
when you are already injecting Service in child too. what is need to pass value of clicked using property biding.
I have two components, parent.component.ts and child.component.ts, the template, parent.component.html likes below:
<div>
<child-component [status]="'good'" (click)="changeToBad()"></child-component>
</div>
I bind a constant string good intentionally to demonstrate my concern. So, initially, the good string is passed to the child component. But if I change its value in child.component.ts, like this.status = 'bad';, what will happen?
I think the input binding of parent is not synced with the child, since from now on they have different status. If I query console.log(this.status), it will say bad. If I want to keep them in sync, I have to use some output bindings.
How I can do to make sure the input binding still work after the programmatically change. Say, it changes to bad for one tick, but it changes back to good (automatically) since the binding from parent.
The parent basically doesn't listen to the change in the child component, unless the child component use the parent as an input, like
<child-component [parent]="parent"></child-component>
and in the parent component
this.parent = this
Otherwise, you can create an Output for the status in the children component and the parent component can listen to it
#Output() public statusEvt = new EventEmitter<boolean>();
and
<child-component (statusEvt)="updateStatus($event)"></child-component>
so in the parent component, you can add this function
public updateStatus(evt: boolean) {
this.status = evt;
}
when you want to emit the change, in the child component, just call
this.statusEvt.emit(this.status)
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() .
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.