Subcription outside OnInit - javascript

I have two child components and i want pass data between them, to do this i use service.
In service i have
headerObject = new Subject<HeaderObject>();
In first component i have method
onClick() {
this.listImage = this.imageService.getImages();
let headers = new HeaderObject();
headers.file = this.listImage[0].data;
this.documentService.getOcrDocument(headers).subscribe(res => {
headers = res;
this.ocrService.headerObject.next(headers);
})
In second component
headerSubcribed: HeaderObject;
private headerSubscription: Subscription;
ngOnInit() {
this.headerSubscription =
this.ocrService.headerObject.subscribe((ocrHeader: HeaderObject) => {
this.headerSubcribed = ocrHeader;
}
But ngOnInit() only once after render view i want to move this code to outside ngOnit and exeute it but i don't know how ? Some tips how execute that code? I set breakpoint at next() method that emit value but later method subscribe isn't execute? What could I do wrong?

There are 2 parts which you observable/subscribe
Observing something and notifying.
In your service, you have a Subject type variable and you are pushing values to it by calling next(). when this method called, it notifies all the observers means who ever is subscribing it.
listening to the observer and do action
When you listen to an observer, you get an update when they change the value. It does not matter if you put your subscription in onNginit or constructor, if the code is there, it will notify you when there is a change into it.
You can also explore BevavourSubject if it fits to your situation.

Related

Trigger a function once 2 inputs are set and then afterwards if either of the value changes in Angular

I have a component with 2 inputs (or mor) and I want to:
Trigger a method X the first time, when both value are set and exists
Trigger a method X each time, if either one of the both value changes
<some-cmp [itemid]="activeItemId$ | async" [userId]="activeUserId$ | async"></some-cmp>
Both values can change at any time, so I figured using rxjs to build a stream lets me control everything. My current solution seems a bit hacky and is difficult to test. I use 2 BehaviourSubjects and combineLatest with a debounceTime.
#Input() set itemId (id){this.itemId$.next(id)};
#Input() set userId (id){this.userId$.next(id)};
itemId$ = new BehaviourSubject$(null);
userId$ = new BehaviourSubbject$(null);
ngOnInt(){
combineLatest([
this.itemId$.pipe(filter(item=>item!===null)),
this.userId$.pipe(filter(item=>item!===null))
]).pipe(
debounceTime(10),
switchMap(...)
).subscribe(...)
}
So my question are
Is there a more elegant way to achieve this behavior?
Is there a way to avoid the debounceTime, which makes testing difficult?
The debounceTime is used in case both value do arrive at the same time and I don't want combineLatest to trigger the method twice.
You are right in using combineLatest, it will only emit the first time after each source has emitted once and then will emit any time any source emits.
Is there a way to avoid the debounceTime. [It] is used in case both value do arrive at the same time and I don't want combineLatest to trigger the method twice.
Maybe debounceTime isn't necessary due to the initial behavior of combineLatest; it will not emit the first time until all sources emit. However if you typically receive subsequent emissions from both sources that occur within a short timeframe, use of debounceTime may be an appropriate optimization.
Is there a more elegant way to achieve this behavior?
I think your code is fine. However, it may not be necessary to use BehaviorSubject since you aren't really using the default value. You could use plain Subject or ReplaySubject(1).
You could assign the result of your combineLatest to another variable and subscribe to that inside ngOnInit or use the async pipe in the template:
#Input() set itemId (id){ this.itemId$.next(id) };
#Input() set userId (id){ this.userId$.next(id) };
itemId$ = new Subject<string>();
userId$ = new Subject<string>();
data$ = combineLatest([
this.itemId$.pipe(filter(i => !!i)),
this.userId$.pipe(filter(i => !!i))
]).pipe(
debounceTime(10),
switchMap(...)
);
ngOnInit() {
this.data$.subscribe(...);
}
Angular provides ngOnChanges hook which can be used in this scenario. It'll trigger ngOnChanges method whenever there's a change in any of the inputs of the component.
Below is an example of how this can be achieved:
export class SomeComponent implements OnChanges {
#Input() itemId: any;
#Input() userId: any;
ngOnChanges(changes: SimpleChanges) {
const change = changes.itemId || changes.userId;
if (change && change.currentValue !== change.previousValue) {
this.doSomething();
}
}
private doSomething() {
// Your logic goes here
}
}
Your HTML will now look clean and you can get rid of async as well:
<some-cmp [itemid]="itemId" [userId]="userId"></some-cmp>

How to get access to nativeElements on ngOnInit?

Suppose in my angular code i have access to a html element by viewChild('someDiv') or constructor(private elem: ElementRef){}.Whenever my angular component gets loaded i want access to that elements some property so i can store it in variable.Ex- i want to get the elements width as soon as that component gets loaded so my code is
#ViewChild('someDiv') someDiv: ElementRef;
someDivWidth;
ngOnInit(){
someDivWidth = this.someDiv.nativeElement.clientWidth;
}
and my html code
<div #someDiv>some text</div>
But this is giving error like nativeElement is undefined.So how can i solve this?My basic need is as soon as this component loads i want to set that div's width in variable without triggering
any function.
Update:
So using constructor(private elem: ElementRef){} isn't giving any error and i can get access to width by
ngOnInit(){
this.someDivWidth = this.elem.nativeElement.querySelector('.someCssSelector');
}
But suppose i have some products data which i am fetching from backend and storing in an array in ngOnInit lifecycle hook and in my template creating multiple div's depending upon the products data array using lifecycle hook.In that case if i want to get all the product div's currently i am doing like
products = [];
items;
ngOnInit(){
this.productService.getProducts().subscribe(products => {
this.products = products
})
this.itmes = this.elem.nativeElement.querySelectorAll('.each-product');
console.log(this.items)
}
this is giving me a empty nodeList.I thought at the time of setting this.items my products don't gets loaded from backend thats why its giving me empty array or nodeList but putting that last two lines in subscribe() like
ngOnInit(){
this.productService.getProducts().subscribe(products => {
this.products = products
this.itmes = this.elem.nativeElement.querySelectorAll('.each-product');
console.log(this.items)
})
}
and my html code is
<div>
<div *ngFor='let product of products' class='each-product'>...</div>
</div>
is still giving me an empty array or nodelist.What to do??
You should do it in the ngAfterViewInit method of the angular life cycle.
ngAfterViewInit(){
this.someDivWidth = this.elem.nativeElement.querySelector('.someCssSelector');
}
Angular uses its algorithms to detect changes in data and respond to the view.If you want to opreate the DOM after the request was completed, you may not be able to get it because the view hasn't been updated yet, and you can understand that Angular also needs time to update the view.
Offer two solutions
Use ChangeDetectorRef to perform change detection manually
use setTimeout
stackblitz-demo
ngOnInit lifeCycle is invoked once when component instantiated and called right after the properties checked for the first time, and before its children checked.
If You want to be sure that view has been loaded (#ViewChild depends on view to be rendered) you should use ngAfterViewInit which is called after component view and it's children view are created and ready.
UPDATE: If you need to access viewChild in ngOnInit you have to set { static: true }

Share and update Observable data in Angular 2

I have 2 components - object-list and search-filter in my app. Also I have a fetch-service with getObjects(page, filter) method. I want to implement a scenario where object-list fetches all data via getObjects method and then search-filter will apply some filters to getObjects and object-list should automatically update. Here is some code:
FetchService
objects: Observable<any>;
getObjects(page: number, filter): void {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
const offset = (page - 1)* this.pageSize;
let url = this.objectsBaseUrl;
//apply filter
if (filter) {
url = `${url}?filter=${JSON.stringify(filter)}`
}
this.objects = this.http.get(url)
.map((response: Response) => {
return response.json() || {}
})
.catch(this.handleError);
}
ObjectListComponent
constructor(private fetch: FetchService) {}
ngOnInit(): void {
this.fetch.getObjects(this.initialPage);
this.objects = this.fetch.objects; // I need an Observable object to use with async pipe with *ngFor
}
getPage(page: number) {
this.fetch.getObjects(page); //here this.objects variable probably should be update because this methods replaces this.fetch.objects
}
SearchFilter
constructor(private fetch: FetchService) {}
apply() {
//some filter calculations
this.fetch.getObjects(1, this.filter);
}
I don't use subscribe to objects because I put it to async pipe in *ngFor. But as far as I'm concerned async pipe uses subscribe inside. The problem is that the list updates only once when ngOnInit fires. What's going wrong?
The SearchFilter component should NOT be in charge of fetching the data.
Here's how I would do it:
1) Create 3 components
A parent component, aka "smart component", to handle all the logic of listening to filter changes and fetching the data with FetchService. The parent component has two children displayed in its template:
First child: SearchFilterComponent is a "dumb component" (or presentational component). Its only job is to emit an #Output event every time the filters change, passing the latest filter values to its parent.
Second child: ObjectListComponent is also a "dumb component". Its only job is to display the list of objects provided by its parent component via an #Input property.
2) Implement the following workflow
When filters change in SearchFilterComponent, emit an #Output event passing the latest filter values, e.g. this.filtersChanged.emit(filters).
In the parent component, listen to filter changes with <search-filter (filtersChanged)="getObjects($event)">. The getObjects(filters) method will re-fetch the objects on every filter change; it should store the fetched objects in a property of the parent component, let's call it results.
Then, the parent component passes results to its child component ObjectListComponent via an #Input property called objects: <object-list [objects]="results">. The object list should refresh automatically every time the input changes.
Hopefully my explanations will make sense. Read up on #Input and #Output on the angular.io site if you're not familiar with those. I don't have time to create a Plunkr, so please post questions if anythings seems unclear.

Run function on observable subscribe

I'm attempting to write a custom component that can bind to an observable being passed in through an input and show/hide elements based on the state of the observable. What I'd like to be able to do is something like:
#Input() observable: Observable<any>;
ngOnInit() {
this.observable.onSubscribe(() => {
// show element, run logic on start;
});
this.observable.onCompleteOrNext(() => {
// hide element, run logic on end;
});
}
After pouring over the rxjs documentation, I've found that with let I could do something like:
this.observable.let((o: Observable) => {
// run logic.
return o;
});
But this seems like a bit of a hack, and I also can't figure out how to then run something when the observable completes. I expect the observables to be async, such as an HTTP request, but this component needs to handle it either way.
For the observable completing, I assumed I would be able to do something like the below with the do function:
this.observable.do(() => {
// run logic when observable completes.
// not getting called.
});
But, unless the do function is defined on the observable creation, this is not getting called.
I'm aware Angular2 allows binding the view directly to observables, but I also need the ability to run logic based on the observable, not just show/hide view elements.
My googlefoo is failing me and the rxjs documentation isn't being very enlightening, but I feel like this should be a fairly easy thing to do. Perhaps I am approaching it wrong.
You could provide hook methods within the child component:
export class ChildComponent {
onSubscribe(){}
onNext(){}
onComplete(){}
}
In the parent component, you can use ViewChild to get a reference to the ChildComponent, then subscribe to the observable and call the hook methods at key points:
once you've subscribed
when the observable emits
when the observable completes
.
export class ParentComponent {
#ViewChild('child') child:ChildComponent;
...
this.observable.subscribe(
next => this.child.onNext(),
err => {},
() => this.child.onComplete()
);
this.child.onSubscribe()
}
Live demo

Angular 2 transfer ajax call response to another component

I just started playing with angular 2 and i've ran into a small problem, that i ve searched for in various forms and also angulars documentation.
I've managed to make a service that makes a call and then i want in a component when i press a button to load another component with dynamicload component and have access to the ajax result.
The problem is that I can t figure out how to do that..
The question is how can I make the result accesible in other components using Observables or Promises method.
If I understood correctly your question, you are looking a way to insert a data from request to another nested component.
I hope this image will clarify for you the data flow for this case.
Your Root component is calling a service method which returns for you promise object.
Then you map needed data from response to the component model inside Root Component constructor.
And your Child component should be subscribed for the model which you was preparing in previous step.
ngOnInit() {
this.dataService.getSomeData()
.subscribe((data: IData) => {
this.data = data;
});
}
Just a short example above how to set model in the root component from the promise object to the local model.
New research:
There is another way to fill your components by data from api's. You can use EventEmitter to emit event from service, and then, you can subscribe for this event inside you created components, so they will get a data, each time there will be called the service. Here is nice example of this strategy in the first answer. Service Events
Hope it will help you, let me know if you will need additional info!
Just create a service, then inject the service where you want.
Here it's an example how to share a service ajax data across many components without making the request twice :
https://stackoverflow.com/a/36413003/2681823
the Service:
#Injectable()
export class DataService {
constructor(private http: Http) { }
private _dataObs = new ReplaySubject<request>(1);
getData(forceRefresh?: boolean) {
// On Error the Subject will be Stoped and Unsubscribed, if so, create another one
this._dataObs = this._dataObs.isUnsubscribed ? new ReplaySubject(1) : this._dataObs;
// If the Subject was NOT subscribed before OR if forceRefresh is requested
if (!this._dataObs.observers.length || forceRefresh) {
this.http.get('http://jsonplaceholder.typicode.com/posts/2')
.subscribe(
requestData => {
this._dataObs.next(requestData);
},
error => this._dataObs.error(error));
}
return this._dataObs;
}
}
the Component:
#Component({
selector: 'child',
template : `<button (click)="makeRequest()" class="btn">Click me!</button>`
})
export class Child {
constructor(private _dataService: DataService) { }
makeRequest() {
this._dataService.getData().subscribe(
requestData => {
console.log('ChildComponent', requestData);
}
}
}
A full working example/plunker can be found here : http://plnkr.co/edit/TR7cAqNATuygDAfj4wno?p=preview

Categories

Resources