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.
Related
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().
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.
If I have a service containing an Observable and I have the following code...
this.collapse.newChild.subscribe(name => {
if (name === "helpArea") {
this.collapse.process("helpArea", true);
}
// I would like to detach now
});
Is it possible to detach without canceling the original subscription? If I call unsubscribe then when I call this.collapse.newChild.next("thing") it throws an error Error: object unsubscribed.
So I don't think unsubscribe is what I am looking for, so is there a way to detach a single observer?
As a work around for now I am using...
this.collapse.newChild.map(name => name === "helpArea").first().subscribe(...)
Peter answer is correct from a design/practice PoV, but beware also that you get this error because you unsubscribe the subject.
What you want to stop is the subscription instead:
var subscription = this.collapse.newChild.subscribe(...)
// NOT this
// this.collapse.newChild.unsubscribe()
// this
subscription.unsuscribe()
This will kill/stop not the subject, but only your current subscription to it.
I don't think your "workaround" is at all bad. It even seems idiomatic: the way that Rx wants you to do it. But you can write it a bit shorter, using the form of first that takes a predicate.
this.collapse.newChild
.first(name => name === "helpArea")
.subscribe(...);
In general, the mindset to get into with Rx is to as much as possible use combinators to manipulate streams to produce exactly the values that you need, and then subscribe with a simple handler that applies the final, effectful, step: updating the UI, calling a service etc. This will lead to cleaner, more expressive code, that is easier to read and easier to re-use.
Another nice thing about this approach for your example is that there is no need to unsubscribe. RX will do that for you after the first value because it knows there won't be more. So it's much cleaner than doing it yourself.
Calling unsubscribe() on a Subscription object unsubscribes only this particular observer. So a question is how did you unsubscribed at the first place because using first() sends complete notification which causes unsubscription anyway.
Btw, you can always unsubscribe inside your own subscriber's callback:
var subscription = this.collapse.newChild.subscribe(name => {
if (name === "helpArea") {
this.collapse.process("helpArea", true);
}
subscription.unsubscribe();
});
I have an observable subscription inside a function that reiterates on certain events:
ko.computed(function() {
alert('computed fired');
self.obs2.subscribe(function() {
alert('subscribe fired');
});
return self.obs1();
});
I noticed that when that function runs, the code inside the subscription isn't executed, but when the subscription finally fires, the code inside it runs as many times as the reiterating function ran before it fired.
It was too complex to reproduce in JSfiddle, so I settled for a simpler, but similar example using a subscription inside a computed observable:
http://jsfiddle.net/norbiu/7hGNb/
Clicking on 'Edit Obs2' a few times will cause the alert to fire each time
Clicking on 'Edit Obs1' a few times will cause the first alert to fire, not the one inside the subscription
Clicking on 'Edit Obs2' once will cause the alert inside the subscription to run multiple times.
Is there a way to make the subscription run just once without having to move it outside the reiterating?
You will subscribe on your obs2 as many times are your computed evaluates.
I have no idea what do you want to achieve with subscribing on a different property inside a computed. I'm quite sure that there is a more proper solution exist to your use case...
However one possible workaround is to store the returned subscription object when calling subscribe and if there is an already stored subscription exists dispose it before subscribing again on your obs2:
self.subscription = null;
ko.computed(function() {
alert('computed fired');
if (self.subscription)
self.subscription.dispose();
self.subscription = self.obs2.subscribe(function() {
alert('subscribe fired');
});
return self.obs1();
});
Demo JSFiddle.
I am writing a webpage with the following structure:
One section (table A) depends on another section (table B);
Another section (table B) has elements that require recalculation on each update. The calculation is handled by external tools, and will cause an event when finished.
In order to guarantee correctness, the table need to be updated only after the other table is fully updated (i.e., done with computation). However, I don't know how to effectively achieve this, and I could not find any wait facility within JavaScript.
For now, I am using the following method:
Declare a global variable updated and make it false;
After the first table received input, I make an empty while loop until updated is true;
Add an listener, once the calculation is done and the event received, set updated to true.
This seems unintuitive to me but I cannot think of any other way of doing it. Is there any good ways to do this?
Thanks for any inputs!
In 2022, it's useful to have an event listener that fires off a Promise (which can be used in promise-chains, or async/await code). A clean way to make one:
function getPromiseFromEvent(item, event) {
return new Promise((resolve) => {
const listener = () => {
item.removeEventListener(event, listener);
resolve();
}
item.addEventListener(event, listener);
})
}
async function waitForButtonClick() {
const div = document.querySelector("div")
const button = document.querySelector("button")
div.innerText = "Waiting for you to press the button"
await getPromiseFromEvent(button, "click")
div.innerText = "The button was pressed!"
}
waitForButtonClick()
<button>ClickMe</button>
<div></div>
Add an listener, once the calculation is done and the event received, set updated to true.
Instead of setting updated to true, and then waiting for updated to be true- just do whatever you want to do in the listener.
myEventBus.addListener(function () {
// do whatever
updateTable();
alert('table updated!');
});
Doing empty while loops is a bad idea. Not only do you burn CPU cycles, but Javacript is single threaded so you will loop forever without giving anyone a chance to change the variable.
What you can do is rewrite the table that has other people depending on it to "fire an event itself". There are many ways to do this, but basicaly you just want it to call a "continuation' function instead of blindily returning. This function can be predefined or you can pass it as a parameter somewhere.
//this is just illustrative
//Your actual code will be probably very different from this.
function update_part(){
//do something
signal_finished_part()
}
var parts_done = 0;
function signal_finished_part(){
parts_done ++;
if(parts_done >= 5){
signal_all_parts_done();
}
}
function signal_all_parts_done()
{
//do something to table A
}
You could write a callback function for whatever triggers the update. To avoid messy callbacks, you could use promises too, and update parts of the table depending on the data retrieved in the update operation. Open to suggestions.