Custom function for onPush change detection in Angular 2 - javascript

{ id:123, version 1, ...}
I am writing an Angular 2 component that uses the onPush change detection strategy. I'd like to know if there is a way to use custom logic with onPush when determining if an object has changed. My understanding is that onPush checks the object reference an so only updates when a new instance of an object is sent.
My app tracks its own versioning for objects which means that every object has an id and version number. I periodically refresh data from the server which means that a new object is instantiated even though the data has not actually changed. This means there are cases with the default onPush that would see a new instantiated object and think it has to update. In actuality the id and version number are the same so even though the the object reference has changed I can skip updating the component.
I'd like to be able to provide a custom function to onPush that essentially uses a function like this to do change checking.
(obj1, obj2) => obj1.id === obj2.id && obj1.version === obj2.version
Is it possible to customize the onPush logic, or is there another way to achieve this so that I don't need to unnecessarily update the component when the object reference changes but the data does not.

I don't think that it's possible to create a custom changeDetectionStrategy . But if you do track the change on your objects by yourself you can do something (probably) even more optimized.
You can inject the ChangeDetectorRef and tell Angular that your component shouldn't be watched anymore (which is pretty cool and powerful !).
constructor(private _cd: ChangeDetectorRef)
ngOnInit() {
this._cd.detach();
}
ngOnChanges() {
// check if your objects have changed here
// if they've changed, tells Angular to check
// the bindings manually
this._cd.detectChanges();
}

Related

Sibling component does not receive emitted changes

In my Angular 9 project I have 2 components which are siblings and the parent component. On change in component A, I emit a value and it's set in the parent component and calls a method in component B. The method in component B emits another value and it's set in the parent component. The on change in component A continues, but the emitted value from component B that is set in the parent component (which is an input in component A) is not changed. I don't know why it's not the input for component A does not change even though the parent updates the value.
Parent Component
setSomeNum(someNum: number) {
// this is run on someNumberEmitter in Component A
this.num = someNum;
if (this.viewChildComponentB) {
this.viewChildComponentB.remove(someNum);
}
}
setSomeOtherNum (someOtherNum: number) {
// this is run on someDiffNumEmitter in Component B
this.otherNum = someOtherNum
}
Component A
componentAOnChange(someNum: number) {
this.someNumberEmitter.emit(someNum);
// this.inputFromComponentB is the original value instead of the one emitted in Component B (this.someDiffNum)
this.someService.applyCalc(someNum, this.inputFromComponentB);
}
Component B
remove(someNum: number) {
this.someDiffNumEmitter.emit(this.someDiffNum);
this.someService.applyCalc(someNum, this.someDiffNum);
}
I'm using the OnPush change detection strategy, but nothing changed. How can the sibling component A run with the data changes from component B?
I'm not sure why you're using ViewChild there but if it is to update the child components manually when there's change then that should be a red flag something is being done wrong, if you have data that needs to be shared it should be shared across the board and update accordingly on the single source of data changes without having to manually update the rest of the places.
Now to your problem:
If you're using the OnPush change detection strategy you have to update your data in an immutable way or use Observables, otherwise the change detection won't trigger.
Some people will advice triggering change detection manually but I'd recommend avoiding that as the whole point of using OnPush is to avoid a whole page render unnecessarily.
A simple solution I like to use is to use a Subject or BehaviorSubject instead with the async pipe. This way it ensures smooth work with the OnPush change detection strategy because ChangeDetection will run when the Observable emits a new value, and the async pipe takes care of unsubscribing the Observable for you.
If I were to modify your current components, it'd look something like this:
Parent:
num$ = new Subject<number>();
otherNum$ = new Subject<number>();
setSomeNum(someNum: number) {
this.num$.next(someNum);
}
setSomeOtherNum (someOtherNum: number) {
// this is run on someDiffNumEmitter in Component B
this.otherNum$.next(someOtherNum)
}
Then in the HTML you can use the async pipe, like this:
<some-component [num]="num$ | async" [otherNum]="otherNum$ | async"></some-component>
(You could use the async pipe in the component itself, doesn't really matter).
And that's pretty much it. You should have a Subject as an Observable, then share it with child components, once the Observable is updated, the child components data will be updated as well.
One small caveat is that when using a Subject instead of a BehaviorSubject is to make sure to subscribe before emitting any values to the Subject, otherwise the data will not update. So for certain cases BehaviorSubject is a better fit.

Vue data not changing with Promise inside created [duplicate]

Basically, when I have a component, let’s call it “TransportComponenet.vue”, and in that component, I have a data() and My properties are carId, transportId. What vue does is makes getters and setters for these properties. Let’s say in this componenet’s view, I type {{carId + transportId}} and also {{carId * transportId}}.
As far As I know, Vue comes to my views, looks at them, and wherever I have getters ( {{carId+ transportId}} or {{carId * transportId}} ) are getters. So vue comes and registers them in component’s watcher. When I somewhere use setters such as this.carId = 5. Vue does the setter function for this property and reevalutes the functions (getters) that were saved before in the watcher. Is this the correct assumption?
What I don’t get is what relationship does exist between Dep class and Watcher class ? I know they both play the big role. I’d really honor the whole explanation “which thing goes where and when and why”.
Reactivity is automatic synchronization between the state and the DOM. That's what the view libraries like Vue and React try to do in their core. They do that in their own ways.
I see Vue's reactivity system as being two fold. One side of the coin is the DOM update mechanism. Let's look into that first.
Let's say you have a component with a template like:
<template>
<div>{{ foo }}</div>
</template>
<script>
export default {
data() {
return {foo: 'bar'};
}
}
</script>
This template gets converted into render function. This happens during build time using vue-loader. The render function for the template above looks something like:
function anonymous(
) {
with(this){return _c('div',[_v(_s(foo))])}
}
Render function runs on the browser and when executed returns a Vnode (virtual node). A virtual node is just a simple JavaScript object that represents the actual DOM node, a blueprint for the DOM node. The above render function when executed would return something like:
{
tag: 'div',
children: ['bar']
}
Vue then creates actual DOM node from this Vnode blueprint and places it into the DOM.
Later, let's say the foo's value changes and somehow the render function runs again. It will give a different Vnode. Vue then diffs the new Vnode with the old one and patches only the necessary changes required into the DOM.
This gives us a mechanism to update the DOM efficiently taking the latest state of things for a component. If every time the render function of a component gets called when any of its state (data, props etc) changes, we have the full reactivity system.
That's where the other side of Vue's reactivity coin comes in. And that is the reactive getters and setters.
This will be a good time to understand Object.defineProperty API if you are not already aware of that. Because Vue's reactivity system relies on this API.
TLDR; it allows us to override an object's property access and assignment with our own getter and setter functions.
When Vue instantiates your component, it walks through all the properties of your data and props and redefines them using Object.defineProperty.
What it actually does is, it defines getters and setters for each of the data and props properties. By doing so, it overrides the dot access (this.data.foo) and the assignment (this.data.foo = someNewValue) of that property. So whenever these two actions occur on that property, our overrides get invoked. So we have a hook to do something about them. We will get back to this in a bit.
Also, for each property a new Dep() class instance is created. It's called Dep because each data or props property can be a dependency to the component's render function.
But first, it's important to know that each component's render function gets invoked within a watcher. So a watcher has an associated component's render function with it. Watcher is used for other purposes as well, but when it is watching a component's render function, it is a render watcher. The watcher assigns itself as the current running watcher, somewhere accessible globally (in Dep.target static property), and then runs the component's render function.
Now we get back to the reactive getters and setters. When you run the render function, the state properties are accessed. E.g. this.data.foo. This invokes our getter override. When the getter is invoked, dep.depend() is called. This checks if there is a current running watcher assigned in Dep.target, and if so, it assigns that watcher as the subscriber of this dep object. It's called dep.depend() because we are making the watcher depend on the dep.
_______________ _______________
| | | |
| | subscribes to | |
| Watcher | --------------> | Dep |
| | | |
|_____________| |_____________|
Which is the same as
_______________ _______________
| | | |
| Component | subscribes to | it's |
| render | --------------> | state |
| function | | property |
|_____________| |_____________|
Later, when the state property gets updated, the setter is invoked and the associated dep object notifies its subscribers about the new value. The subscribers are the watchers which are render function aware and that's how the components render function gets invoked automatically when its state changes.
This makes the reactivity system complete. We have a way to call a component's render function whenever its state changes. And we have a way to efficiently update the DOM once that happens.
This way Vue has created a relationship between a state property and a render function. Vue knows exactly which render function to execute when a state property changes. This scales up really well and basically removes a category of performance optimization responsibility from the hands of developer. Devs don't need to worry about over rendering of components no matter how big the component tree. To prevent this, React e.g. provides PureComponent or shouldComponentUpdate. In Vue, this is just not necessary since Vue knows exactly which component to re-render when any state changes.
But now that we know how Vue makes things reactive, we can think of a way to optimize things a bit. Imagine you have a blog post component. You get some data from the backend and show them on the browser using Vue component. But there is no need for the blog data to be reactive because it most likely won't change. In such situation, we can tell Vue to skip making such data reactive by freezing the objects.
export default {
data: () => ({
list: {}
}),
async created() {
const list = await this.someHttpClient.get('/some-list');
this.list = Object.freeze(list);
}
};
Oject.freeze among other things disables the configurability of an object. You cannot redefine the properties of that object again using Object.defineProperty. So Vue skips the whole reactivity setup work for such objects.
Besides, going through the Vue source code yourself, there are two extremely good resources available on this topic:
Vue Mastery's Advanced component course
FrontendMaster's Advanced Vue.js Features from the Ground Up by Evan You
If you are curious about the internals of a simple virtual DOM implementation, check out the blog post by Jason Yu.
Building a Simple Virtual DOM from Scratch

How to induce reactivity when updating multiple props in an object using VueJS?

I was witnessing some odd behaviour while building my app where a part of the dom wasn't reacting properly to input. The mutations were being registered, the state was changing, but the prop in the DOM wasn't. I noticed that when I went back, edited one new blank line in the html, came back and it was now displaying the new props. But I would have to edit, save, the document then return to also see any new changes to the state.
So the state was being updated, but Vue wasn't reacting to the change. Here's why I think why: https://v2.vuejs.org/v2/guide/reactivity.html#For-Objects
Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive
Sometimes you may want to assign a number of properties to an existing object, for example using Object.assign() or _.extend(). However, new properties added to the object will not trigger changes. In such cases, create a fresh object with properties from both the original object and the mixin object
The Object in my state is an instance of js-libp2p. Periodically whenever the libp2p instance does something I need to update the object in my state. I was doing this by executing a mutation
syncNode(state, libp2p) {
state.p2pNode = libp2p
}
Where libp2p is the current instance of the object I'm trying to get the DOM to react to by changing state.p2pNode. I can't use $set, that is for single value edits, and I think .assign or .extend will not work either as I am trying to replace the entire object tree.
Why is there this limitation and is there a solution for this particular problem?
The only thing needed to reassign a Vuex state item that way is to have declared it beforehand.
It's irrelevant whether that item is an object or any other variable type, even if overwriting the entire value. This is not the same as the reactivity caveat situations where set is required because Vue can't detect an object property mutation, despite the fact that state is an object. This is unnecessary:
Vue.set(state, 'p2pNode', libp2p);
There must be some other problem if there is a component correctly using p2pNode that is not reacting to the reassignment. Confirm that you declared/initialized it in Vuex initial state:
state: {
p2pNode: null // or whatever initialization value makes the most sense
}
Here is a demo for proof. It's likely that the problem is that you haven't used the Vuex value in some reactive way.
I believe your issue is more complex than the basic rules about assignment of new properties. But the first half of this answer addresses the basics rules.
And to answer why Vue has some restrictions about how to correctly assign new properties to a reactive object, it likely has to do with performance and limitations of the language. Theoretically, Vue could constantly traverse its reactive objects searching for new properties, but performance would be probably be terrible.
For what it's worth, Vue 3's new compiler will supposedly able to handle this more easily. Until then, the docs you linked to supply the correct solution (see example below) for most cases.
var app = new Vue({
el: "#app",
data() {
return {
foo: {
person: {
firstName: "Evan"
}
}
};
},
methods: {
syncData() {
// Does not work
// this.foo.occupation = 'coder';
// Does work (foo is already reactive)
this.foo = {
person: {
firstName: "Evan"
},
occupation: 'Coder'
};
// Also works (better when you need to supply a
// bunch of new props but keep the old props too)
// this.foo = Object.assign({}, this.foo, {
// occupation: 'Coder',
// });
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
Hello {{foo.person.firstName}} {{foo.occupation}}!
<button #click="syncData">Load new data</button>
</div>
Update: Dan's answer was good - probably better than mine for most cases, since it accounts for Vuex. Given that your code is still not working when you use his solution, I suspect that p2pNode is sometimes mutating itself (Vuex expects all mutations in that object to go through an official commit). Given that it appears to have lifecycle hooks (e.g. libp2p.on('peer:connect'), I would not be surprised if this was the case. You may end up tearing your hair out trying to get perfect reactivity on a node that's quietly mutating itself in the background.
If this is the case, and libp2p provides no libp2p.on('update') hook through which you could inform Vuex of changes, then you might want to implement a sort of basic game state loop and simply tell Vue to recalculate everything every so often after a brief sleep. See https://stackoverflow.com/a/40586872/752916 and https://stackoverflow.com/a/39914235/752916. This is a bit of hack (an informed one, at least), but it might make your life a lot easier in the short run until you sort out this thorny bug, and there should be no flicker.
Just a thought, I don't know anything about libp2p but have you try to declare your variable in the data options that change on the update:
data: {
updated: ''
}
and then assigning it a value :
syncNode(state, libp2p) {
this.updated = state
state.p2pNode = libp2p
}

How to get component instance in data section in vuejs template?

I have a component that has complex rendering logic.
I try to carry out this logic to helper classes, for simplifying.
To do this, in the data section (for reactivity), I create class references as follows:
export default {
data: () => ({
state: new InitialState(this),
query: new QueryController(this)
})
}
As I understand it, at this point the context of this is not yet defined.
So, I have two questions.
1) Is there a way to pass the this component context in the data section (without lifecycle hooks)?
2) Is the approach with references to external classes of vuejs philosophy contrary?
Component instance is already available when data function runs, this is one of reasons why it has been forced to be a function.
Due to how lexical this works with arrow functions, it's incorrect to use them to access dynamic this. It should be:
data() {
return {
state: new InitialState(this),
query: new QueryController(this)
};
})
The problem with InitialState(this) is that the entire component instance is passed instead of relevant data, this breaks the principle of least privilege.
Despite Vue isn't focused on OOP, there's nothing wrong with using classes. One of possible pitfalls is that classes may not play well with Vue reactivity because it puts restrictions on the implementation. Another pitfall is that classes cannot be serialized to JSON and back without additional measures, this introduces limitations to how application state can be handled.
As I understand it, at this point the context of this is not yet defined.
Only because of the way you've written the code. The component instance does exist and is available. It is sometimes used to access the values of props for determining the initial values of data properties.
For example, here is an example from the documentation:
https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
export default {
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
}
The reason why your code doesn't work is because you are using an arrow function. If you change it to the following then this will be available:
export default {
data () {
return {
state: new InitialState(this),
query: new QueryController(this)
}
}
}
See also the note here:
https://v2.vuejs.org/v2/api/#data
Note that if you use an arrow function with the data property, this won’t be the component’s instance, but you can still access the instance as the function’s first argument
As to your other question about whether using classes like this is contrary to Vue...
I don't think the use of classes like this is encouraged but they can be made to work so long as you understand the limitations. If you have a clear understanding of how Vue reactivity works, especially the rewriting of properties, then it is possible to write classes like this and for them to work fine. The key is to ensure that any properties you want to be reactive are exposed as properties of the object so Vue can rewrite them.
If you don't need reactivity on these objects then don't put them in data. You'd be better off just creating properties within the created hook instead so the reactivity system doesn't waste time trying to add reactivity to them. So long as they are properties of the instance they will still be accessible in your templates, there's nothing special about using data from that perspective.
I think computed is a better way to do what you want
export default {
computed:{
state(){
return new InitialState(this);
},
query(){
return new QueryController(this);
}
}
}

Dynamically created component is not binding to the model on model change

My goal is on emitting of new value to the item$ property of the dynamically created component, restore component state to default state which is stored in object. But clearly component is not picking up the model changes.
I have tried to run the change detection on the component on each time new value is emitted. But still not picking up the value.
I have reproduced the problem here in https://stackblitz.com/edit/angular-tyhfku.
In above code HelloComponent is reusable component and HeroComponent is dynamic component which hosts HelloComponent (sorry for naming). On AppComponent I have a onclick method which emits new value on each click. Also you can change the state of each item on the DOM. But I'd like on each new value restore the state of the HeroComponent which I am trying to accomplish on Subscription but not working
It's not about Dynamically created components - try to change this to standard way and you still will have the same problem. It's about how you pass simple values to section/hello component, how you change them and how change detection mechanism in Angular works. After you change values in SectionComponent, from HeroComponent perespective nothing has really changed, Input values for SectionComponents are the same. Try to add another simple number Input property to SectionComponent, change this in HeroComponent and you will se that onChanges works fine:
https://stackblitz.com/edit/angular-za4mkd
I dont now what you want to achive, so I cant tell you right way to code this, but maybe you should consider using Control Value Accessor (if this has to be some sort of checkbox in form?).
Also you should remember that for objects (like data structures) Angular see changes only if reference changed (you can use spread operator to create a new instance of data structures).

Categories

Resources