I was going through an Angular code in one of my existing projects and found below snippet.
We are using Angular material datatable to render the view on the page
export class Component implements OnInit,AfterViewInit{
private dataSource: MatTableDataSource<Product> = null;
#ViewChild(MatPaginator) paginator: MatPaginator;
columnsToDisplay = ['productId','productname'];
constructor(private _service : DataService) { }
ngOnInit() {
this._service.getProducts().subscribe(
((data : Product[]) => this.dataSource = new MatTableDataSource(data)),
() => console.log('THIS IS ERROR')
);
setTimeout(() => this.dataSource.paginator = this.paginator);
//this.dataSource.paginator = this.paginator;
}
ngAfterViewInit() {
this.dataSource.paginator = this.paginator;
}
}
My question is :
1) Since this.service.getData() returns an Observable and subscribe will be called asynchronously whenever the HttpResponse is available ,
does operation inside setTimeout function will be called ONLY AFTER the subscribe method is called ?
2) I have seen that ngAfterViewInit method also contains exactly the same code as in setTimeout method in ngOnInit method
3) But when this method is called (ngAfterViewInit) , this.products is still NULL indicating that subscribe is not yet called '
4) Is that the reason setTimeout is called inside ngOnInit method ?
5)If this is the case , what is the use of ngAfterViewInit method ?
1) It depends. the subscription execute the code only when the action is done. So, when the this.service.getData() has finished its job. The setTimeout do the job after a delay. If the subscription need less time than the setTimeout, it will be executed first.
2) Maybe you were trying to notice when the function is executed?
3) the AfterViewInit is fired multiple times. You can check like this if(!!something) and then execute some code.
4) you should ALWAYS avoid to use settimeout (just use it for debug purposes).
EDIT:
ngOnInit() {
this._service.getProducts().subscribe(
((data : Product[]) => this.dataSource = new MatTableDataSource(data)),
() => console.log('THIS IS ERROR')
);
setTimeout(() => this.dataSource.paginator = this.paginator);
//this.dataSource.paginator = this.paginator;
}
`
Let's simply this code a bit:
ngOnInit() {
this.service.doStuff()
.subscribe(result => {
this.functionA();
},
err => {
//Do other stuff in case of an error
});
this.functionB();
}
functionA(){
console.log("Hello,");
}
functionB(){
console.log("world!");
}
The output of this code will be:
world!Hello,
But why?
That's because of the observable pattern.
You can imagine that as you walking with two people: one that know english, one that doesn't. So even if you say "How are you?" first to the guy who doesn't know english, he will need time to understand what did you say and answer you. At the same time, the other guy (that know english very well) answer you instantly.
The example of functionA and functionB is the same. FunctionA is executed only when the subscription has catch something. That's why it isn't fired first. You can see that putting a debug point here:
ngOnInit() {
this.service.doStuff()
.subscribe(result => {
---> this.functionA();
},
err => {
//Do other stuff in case of an error
});
---> this.functionB();
}
hope to have explained well this.
Now let's move on, let's use the timeout:
ngOnInit() {
this.service.doStuff()
.subscribe(result => {
this.functionA();
},
err => {
//Do other stuff in case of an error
});
settimeout(() => {
this.functionB();
}, 500);
}
Which function will be executed first?
Spoiler: You can't know that.
If you are wondering why, it's easy: You know exactly that the functionB will be called after 500ms, but you can't know how much time will use the subscription to be ready. So if you are lucky, and your subscription usually need about 500ms to complete, you can try to reload the page several time, sometimes you will see Hello, world!, sometimes you will see world!Hello,.
To answer in a better way at your questions: I don't really know why did you put the code like this, literally no idea.
The ngAfterViewInit is a life-cycle called after the ngOnInit, and execute the logic after Angular has fully initialized a component's view.
I will try to simplify the description:
setTimeout puts the inside function in the end of the javascript queue so while javascript process is running it will pop up from the stack and call the operation. anything in the queue will get called only if the stack is empty. so setTimeout tells javascript to hold the this code till you finish your work.
subscribe and observable: observable is async data structure so once you subscribe to it you can never now how much time it will take to call the subscribe method. in other words, subscribe will get called only if as example http response returns.
Back to your question: you can not know when your setTimeout code get called but theoretically it will get called before subscribe (javascript engine faster than http response).
If you need to initialize some data table structure only after you get the data from the http request you should put it inside the subscribe method and no need for setTimeout.
ngAfterViewInit is used by angular to tell the developer that in this stage your view is ready and you can as an example use elementRef.
ngOnInit is used by angular to tell the developer that all the inputs and directives ...etc.
1. no , setTimeout will be called only once and before subscribe as its outside of its context.
2. because of Asynchronous update , if we update properties asynchronously the values will not be updated when the verification loop is running and we get no error.
3. the ViewChild is available only after ngAfterViewInit. it populates the children when creating a view and so they are available earlier.
4. ngOnInit lifecycle hook is triggered before the DOM update operation and will give no error. ngOnInit lifecycle hook is triggered after the bindings have been processed . ngAfterViewInit is invoked when the view is initially rendered i.e. it's called after a component's view, and its children's views, are created.
5. ngAfterViewInit() should be called after a component's view, and its children's views, are created, most importantly children's ngAfterViewInit()s are called before the parent's ngAfterViewInit().
Related
Button show triggered on ngIf in template. After this code the button is not shown.
public navBackVisible = false
ngOnInit() {
this.router.events
//.pipe(filter(event => event instanceof NavigationEnd))
//.pipe(pairwise())
.subscribe(
() => this.navBackVisible = true); // here true
console.log(this.navBackVisible); // here false
}
template:
<button *ngIf = "navBackVisible"><icon>arrow_back</icon></button>
UPD:
this is all code
ChangeDetection.Default
router emmits events
function inside subscribe is called
function inside subscribe fires before ngIf
But this.http.get(url).subscribe(() => this.navBackVisible = true) works fine
everything works if I write the same logic in the constructor of some service and call it from this component
Pairwise (https://rxjs.dev/api/operators/pairwise), only emitting value(s) if and only if after at least one value has been emitted, and router events here is only emitting 1 value and then completed.
You can not use pairwise() and filter(event => event instanceof NavigationEnd) at the same time. Choose one or the other because pairwise emits previous and current value in pairs. If you filter out the last router event (which is NaviationEnd event) your pairwise never emits and you never fall into function to turn this.navBackVisible = true. At least, that's my understanding of this (if I am mistaken please correct me).
Use:
this.router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.subscribe(() => {
console.log('i am here');
this.navBackVisible = true;
});
As a result, ngOnInit called the getter variable from the service-class. And in the service in the constructor, I used the same logic. Everything works. A little later I changed the variable to sessionStorage (it fits my logic). I still don't know what it was. If this happens to someone, then this is a good step towards finding a problem. This solution satisfied my problem, but the question is still open, I will follow the answers and answer your clarifications.
I have recently done a few API tests for a new job. Just receiving data and passing it through. Although I have completed the tasks and it works functionally, the people I walk through it with are not huge fans of componentDidMount.
They do not suggest an alternative? Anyone know why this could be? Is it due to it being async?
The new modern way to do it is: useEffect
First some code (from the docs):
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
At the end-of-the-day, the componentDidMount purpose is to execute something(the side effect) because the component was mounted(the reason or event).
So you can specify array of dependencies (or causes) for re-running like so:
useEffect(() => {
// ....
}, [someVar]);
so if someVar changed, the function will re-run.
Special use cases are; omitting this argument, will cause it to run once, on-mount event. and specify empty array will cause it to run on each re-render.
For the componentWillUnmount:
Just return a function from the inner function like so:
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
So I have a checkbox that is tied to a model:
<mat-checkbox [(ngModel)]="element[column]" (click)="updateRow(element, $event)"></mat-checkbox>
The click event is supposed to call the server and perform a write in the db.
So, for this, I thought that using concatMap was enough since it waits before making the next call to avoid exhausting the database.
updateRow(row, $event): void {
Observable.from([{ row: row }])
.concatMap(i => this._definitionService.updateDefinition(i) //async work
.subscribe(result => console.log('row updated'), error => {
this.snack(error);
});
}
The problem is that it does not wait since every event change is a new call, and performing too many changes exhaust the database.
What would be the solution for this? I thought about tying the events using fromEvent like this:
Observable.fromEvent($event.target, 'click')
.subscribe(i => {
console.log(i);
});
But, it only works when clicking twice on the checkbox.
What would be the correct approach?
Thanks
The reason why it's calling many requests is because every time you click the element you call updateRow that creates new instance of Observable.from. So concatMap won't help you because you when you make 10 clicks it subscribes to 10 different Observables with 10 different concatMaps.
Probably the easiest way to achieve what you want is creating an intermediate Subject and pushing item into it every time you call updateRow:
const s = new Subject();
s.concatMap(i => this._definitionService.updateDefinition(i))
.subscribe(
result => console.log('row updated'),
error => this.snack(error),
);
...
updateRow(row, $event): void {
s.next({ row });
}
Now you'll have just one subscription and pushing multiple item into Subject s (with next()) will execute them in order one after another.
In a few places I have needed to call setTimeout, eg:
setTimeout(() => this.isSaving[index] = false, 500);
When my component is destroyed, will that timeout continue to emit? In other words do I need to capture the returned observable, like this:
this.subTimeout = setTimeout(() => this.isSaving[index] = false, 500);
and then unsub in my destroy hook:
ngOnDestroy() {
this.subTimeout.unsubscribe();
}
This gets laborious if I have to initiate several setTimeouts in my component. Is there an easier way to destroy them all? Like maybe with takeUntil(destroy$)?
do I need to unsubscribe from setTimeout calls?...When my component is destroyed, will that timeout continue to emit?
You don't need to worry about the callback executing repeatedly. setTimeout executes just once then it's dead. It's generally good practice to account for the possibility that the time will run out and the callback execute only after the component has been destroyed though. In my own applications, I throw in an array all subscriptions that need to be undone, and I have a standard batch unsubscribe job in ngOnDestroy. The same can be done with your timeouts:
// component property
timeOutIDs:number[] = [];
...
// triggering a timeout and capturing the id
this.timeOutIDs.push(
setTimeout(() => this.isSaving[index] = false, 500)
);
...
// inside ngOnDestroy
this.timeoutIDs.forEach(id => clearTimeout(id));
With this approach you won't need multiple variables to store different timeout ids, and you can be sure that all your timeouts will be cleared properly if you always push the return value of setTimeout in your ids array.
Additional note: You should always cancel setInterval calls and always unsubscribe from open-ended subscriptions though.
It depends on what's going on inside setTimeout callbacks, but generally they should be unsubscribed. There's no way how they could be magically be unsubscribed. Their callbacks will be fired any way and may cause errors or undesirable side effects.
It is a good practice to make timeout be assigned somewhere, at least for the purpose of testing. The thing that is supposed to be done in ngOnDestroy, this.subTimeout.unsubscribe() presumes that a timeout is performed through RxJS:
this.subTimeout = Observable.timer(500).subscribe(() => {
this.isSaving[index] = false;
});
The way it can be improved depends on what purpose these timeouts serve.
I have a function stopRecording() that I'd like to be called when a timer runs out, or when someone presses a stop button. The problem is that when it is called when the timer runs out (the first half of the render function) it is called continuously, despite me bracketing it in an if clause. When it is called as a button event (in the return half of the render function) then it works fine.
Note my console logs. When I open the console in Chrome and let the timer run out, the console logs I marked as successful in my code body runs, but NOT ones that I commented with //!!!. I also get the following error continuously: Invariant Violation: setState(...): Cannot update during an existing state transition (such as withinrender). Render methods should be a pure function of props and state.
//...
stopRecording: function() {
if (this.state.recording){
console.log("this will log as expected")
this.setState({recordingStatus:"",
recording:false})
console.log("this will NOT log as expected'") //!!!
this.props.emit("recEmit")
}
}
render: function() {
var timeBar;
var countdown = "0";
var timeBarFill = "#FF9090"
if (this.state.recording){
countdown = new Date()-this.state.startTime
timeBarFill = "#FF3830";
if (countdown > this.state.maxRecLength){
console.log('this will log as expected')
countdown=0
this.stopRecording()
console.log('this will NOT log as expected') //!!!
};
}
//...
return(
//...
<button type="button" id="button" onClick={this.stopRecording}><b>Stop</b></button>
//...
)
You should never call setState inside render(): https://github.com/facebook/react/issues/5591#issuecomment-161678219
As render should be a pure function of the component's props and state, which means that it should not have any side effects (like changing its own state).
Also, you can't guarantee that React will call your component's render() method when your countdown is about to expire. Consider using setTimeout in component's life cycle methods.
I think that this is due to how states work in react. This article explains it pretty well. I suggest to read it but I can some it up for you:
setState is usually called asynchronously.
if setState is not triggered by an event that React can keep track of, such as onClick, it is called synchronously.
This means that when you are using onClick everything goes fine because your call of setState in stopRecording does not block and the function finishes before a re render is called. When a timer triggers it this happens synchronously, the state changes and render is called again.
Now, I still do not understand how it can run continuously, since it should have set the state.recording variable to false and I don't see anything that turns it back to true.
Also, be careful to use states just for variables that are truly states: change with time. The maxRecordinLength does not seem to be a state variable, and same for startTime.
EDIT:
after I saw the update I realized that the main issue here is changing a state inside of the render method. I posted this link in a comment here but I think it is worth explaining.
Basically, you can solve your issue by calling a setTimer function in the componentDidMount function of react-- more on this here.
Something like:
componentDidMount: function(){
setTimer(this.myFunction, this.props.maxRecLength);
},
And you myFunction would look like this:
myFunction: function(){
this.setState({timeElapsed: true});
},
Then you can use this.state.timeElapsed in your render function, and whatever is in there will be displayed after the maxRecLength is reached.