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.
Related
I'm working on a unit test in Jest for a component. One of the pieces of functionality I want to verify is that when a child component (which implements ControlValueAccessor) rendered in the component modifies its ngModel binding and triggers the (ngModelChange) event that the parent does the appropriate thing.
For brevity, here's the relevant code of the parent, containing a reference to the child, <child-component>:
<div>I am the parent component</div>
<child-component
[(ngModel)]="myModel"
(ngModelChange)="doAThing($event)"
>
</child-component>
In the test I've tried a test like this:
it('Binds to the child properly', () => {
// Bunch of set up where I use Angular's TestBed utility to get a reference to the component
// and the testing fixture
spyOn(component, 'doAThing');
component.myModel = 'new data';
fixture.detectChanges();
expect(component.doAThing).toHaveBeenCalled();
});
But the spy never detects any calls. Now this kind of makes sense because I'm modifying the model directly on the parent which would pass down to the child but shouldn't come back up to the parent-- you'd end up in a recursive loop in that case.
So then the question is: how do I cause this <child-component> to fire off its (ngModelChange) event within a Jest test for the parent component?
My only thought from reading the documentation is to stub out the child component and somehow get a reference to it and manually cause the event to fire. But that seems like a lot of work. Is there an easier path?
Appreciate any thoughts on how best to test this functionality.
It turns out I was on the right track when I posted about stubbing out the child like is described in the Angular docs.
I basically had to stub out the child at the top of the parent test like so:
#Component({
selector: 'child-component',
template: '',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: ChildComponent,
multi: true
}
]
})
class ChildComponent implements ControlValueAccessor {
_model: any;
get model() {
return this._model;
}
set model(val: any) {
this._model = val || null;
this.propagateChange(this._model);
}
propagateChange = (_: any) => {};
writeValue(value: any): void {
this._model = value;
}
registerOnChange(fn: any): void {
this.propagateChange = changed => {
return fn.call(this, changed);
};
}
registerOnTouched(): void {}
}
The important thing to note is it does have to implement the ControlValueAccessor so that we can bind to the (ngModelChange) event. This includes the providers bit, otherwise Angular won't set it up properly.
Then the test ended up looking something like this:
it('Binds to the child properly', () => {
const childDebugElement = fixture.debugElement.query(
By.directive(ChildComponent)
);
spyOn(component, 'doAThing');
childDebugElement.componentInstance.model = 'new data';
fixture.detectChanges();
expect(component.doAThing).toHaveBeenCalled();
});
We can use the debugElement.query on the fixture that we set up using Angular's TestBed utility to get a reference to the component we need. Since we provided a stub we can get a reference to the actual component we provided via childDebugElement.componentInstance. Then it's just a matter of making sure a call to propagateChanges happens. In this case that's via the setter on model.
I'm not thrilled that it requires writing a whole bunch of code for a stub that's only a proxy for the actual component in the parent. But then I guess that's what you get when you basically want an integration test.
EDIT 2: This appears to be my general problem, and solution (using setTimeout so Angular's lifecycle can happen). I'll either close this or post an answer to my own question when I can.
See EDIT for a simpler repro that doesn't involve Subjects/Observables but is essentially the same problem.
I have a parent component that's responsible for fetching data from a service.
export class ParentComponent implements OnInit {
public mySubject: Subject<Foo[]> = new Subject<Foo[]>();
public buttonClicked = false;
private currentValues: Foo[] = null;
constructor(private SomeService myService) { }
this.myService.get().subscribe(values => {
this.mySubject.next(values); // Does NOT work when a child component is hidden, as expected.
this.currentValues = values; // Keep value so we can manually fire later.
});
private buttonClickHandler() {
this.buttonClicked = true;
this.mySubject.next(this.currentValues);
}
}
This data is subscribed to in the HTML by a child component. This component is hidden by default via *ngIf, and only becomes visible on a button click:
<app-child-component [values]="mySubject.asObservable()" *ngif="buttonClicked" />
In the parent component above you see I'm trying to pass the current available data to the child by invoking next() when the component is made visible in some way:
this.mySubject.next(this.currentValues);
This does not work when initially un-hiding the component via *ngIf. If I click the button a second time, which then calls next() again, then it works as expected. But when Angular is in the current context of un-hiding something, observables aren't getting their data. (This also happens when things are unhidden by other means, but the result is the same: If in the same method, the subject/data passing does not work; the component has to already be visible as of the method call.)
I'm guessing the binding to the observable is not happening until after *ngIf shows the child component, after the method call resolves. Is there some place I can hook into that I can then pass child data down?
EDIT for clarification: I don't believe this is an issue of Subject vs. BehaviorSubject. I'm not having issue passing the data. The issue is that the data-passing (confirmed via console.log()) is not occurring at all in the first place. It's not that the child component is receiving a null value. The subscription just isn't firing to the child.
I found I can reproduce this in a simpler fashion too: Trying to select an element in the DOM of *ngIf HTML reveals undefined if I make *ngIf's value true within the same Angular method.
<div *ngIf="buttonClicked">
<div id="someElement">Foo</div>
</div>
public someMethod(): void {
this.buttonClicked = true;
const container = document.getElementById('someElement'); // DOES NOT WORK if this.buttonClicked was false at the start of this method!
}
You going to need to use a BehaviourSubject instead of Subject, which emits the previously set value initially.
What is the difference between Subject and BehaviorSubject?
I have a problem working with two components (pop ups) in which i have to send data from a chlid component to another one (parent) who doesn't have an event to extract this data.
logically i have to find a sort of function that makes the parent listen to the changes made in the child.
The Changes have to appear in the same time in both components.
Could any one help ?
The answer is in your question. You need an Output property, which is the Angular generalization of a JS event.
In your child component:
class ChildComponent {
#Input() someProperty: string;
#Output() dataChanged = new EventEmitter<string>();
whenSomethingHappensInChild() {
this.dataChanged.emit('something');
}
}
In your parent template:
...
<app-child [someProperty]="'someValue'" (dataChanged)="doSomething($event)"></app-child>
...
And in you Parent code:
class ParentComponent {
...
doSomething(theValue: string) {
// TA-DAA! you have the value.
}
...
}
Please, do yourself a favor and READ THE DOCS, or, better, a book ;)
In particular:
https://angular.io/guide/architecture-components has a full overview of the basics of binding, which this problem falls into.
Have a nice day.
Yes, you can use a shared BehaviorSubject to push values and both components have to subscribe to get this changes
Problem Solved: I used the #Host() tag to get the current instance of the Parent component and access the methode that changes it's attributes.
Here is what you should do.
First:
You should declate your parent component in the child
parent:ParentComponent;
Second :
you should pass your current parent instance to your new declaration in the constructor
constructor(#Host() currentParent:ParentComponent){
this.parent=currentParent
}
Third:
Now try just to access the methods and attributes in the parent components
changeParentAttribute(){
this.parent.iAmInTheParent();
}
I hope you find this helpful
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>
How do you properly pass a function from a parent to a child component when the function takes in parameters?
In the ngOnInit, how to scope a function like:
addToList(id) {
this.store.dispatch(this.listActions.addToList(id));
}
ngOnInit, which is wrong right now.
ngOnInit() {
this.addToList = this.addToList.bind(this, id);
}
In my parent component, I have the addToCart(id) function.
I want to pass that function to my child component, which has a list of items, and on clicking the ADD button on an item, I want to callback addToCart(item_id) to the parent.
#Maarek's answer is a good one, and is the 'right' way to do it, probably. What I am presenting here is a simpler means of communicating specifically from the Child to the Parent.
What you proposed in the original post was to have the Parent send a callback method to the Child, so the Child can call it with data when appropriate. To accomplish this specific task (data from Child to Parent on some action in the Child) using Events is appropriate, using the EventEmitter from inside the Child. See this API reference which has an example: https://angular.io/docs/ts/latest/api/core/index/EventEmitter-class.html and this Plunker I made as a demo: https://embed.plnkr.co/T1wFqVOhMXgX6NRfTuiC/
In the child, you have code like this:
import { Component, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'item',
template: `
<div class="item">
<button type="button" (click)="addItem()">Add</button>
<p>{{id}}
</div>
`
})
export class ItemComponent {
#Input() id: string;
//key line here: this emitter can be bound to by parent to get notifications
#Output() add: EventEmitter<string> = new EventEmitter<string>();
constructor() { }
addItem() {
//then when the button is clicked, emit events to the parent.
this.add.emit(this.id);
}
}
The Parent would call create the component like this:
<item id="1" (add)="addToList($event)"></item>
Where addToList() is a function on the Parent that does the work your callback was intended to do. The $event is the data passed from the child (the id).
There's not a lot of detail here, but from what I'm gathering I think what you will want is an injectable service (demonstrated here: https://angular.io/docs/ts/latest/tutorial/toh-pt4.html) to handle the data objects being shared between the components. Rather than type a bunch of code in here (which is better shown at that page in the tutorial) I'll describe what I think you're trying to do and how I'd go about doing it.
The entire store data model can be handled via a service (store.service.ts maybe). Which will have your CRUD functions exposed for the different properties of the store model. The list you are adding to here should have a public getter that returns an observable of the list in the service as well as a public function for adding and deleting from the list. Something like this:
#Injectable
export class StoreService {
private _storeList:BehaviorSubject<Array<any>> = new BehaviorSubject<Array<any>>([]);
/*I'm sure the store has other properties, set them up here. I'd suggest
breaking any arrays out of the general object (unless you want to use
pipes which are awesome but keeping it simple here) but if the store has
a lot of general properties (name, address, whatever) they can be stored
in a single BehaviorSubject of type any.
*/
constructor(){}
get StoreList() { return this._storeList.asObservable() }
public addToList(id) {
let curVal = this._storeList.getValue();
curVal.push(id);
this._storeList.next(curVal);
}
}
You would then inject this service into the constructor of both the parent and the child constructor(private _storeService:StoreService){} (and any other components that need it). The child could then subscribe to the list: get List() { return this._storeService.StoreList } and the parent can call the add function to add to the list. One thing to note, when you add this to your template as an *ngFor, make sure to pass the value through the async pipe. *ngFor="List | async" or your may tear your hair out trying to figure out why you're getting errors.
This article helped me a lot with this as well (although I might suggest avoiding immutable at first until you're comfortable with Angular 2 completely): http://blog.angular-university.io/how-to-build-angular2-apps-using-rxjs-observable-data-services-pitfalls-to-avoid/