I tried to do the following in my component which uses changeDetection: ChangeDetectionStrategy.OnPush,
#ViewChild('searchInput') input: ElementRef;
ngAfterViewInit() {
this.searchText$ = fromEvent<any>(this.input.nativeElement, 'keyup')
.pipe(
map(event => event.target.value),
startWith(''),
debounceTime(300),
distinctUntilChanged()
);
}
And in the template
<div *ngIf="searchText$ | async as searchText;">
results for "<b>{{searchText}}</b>"
</div>
It doesn't work, however if I remove the OnPush, it does. I am not too sure why since the async pipe is supposed to trigger the change detection.
Edit:
Following the answers, I have tried to replace what I have by the following:
this.searchText$ = interval(1000);
Without any #Input, the async pipe is marking my component for check and it works just fine. So I don't get why I haven't got the same behavior with the fromEvent
By default Whenever Angular kicks change detection, it goes through all components one by one and checks if something changes and updates its DOM if it's so. what happens when you change default change detection to ChangeDetection.OnPush?
Angular changes its behavior and there are only two ways to update component DOM.
#Input property reference changed
Manually called markForCheck()
If you do one of those, it will update DOM accordingly. in your case you don't use the first option, so you have to use the second one and call markForCheck(), anywhere. but there is one occasion, whenever you use async pipe, it will call this method for you.
The async pipe subscribes to an Observable or Promise and returns the
latest value it has emitted. When a new value is emitted, the async
pipe marks the component to be checked for changes. When the component
gets destroyed, the async pipe unsubscribes automatically to avoid
potential memory leaks.
so there is nothing magic here, it calls markForCheck() under the hood. but if it's so why doesn't your solution work? In order to answer this question let's dive in into the AsyncPipe itself. if we inspect the source code AsyncPipes transform function looks like this
transform(obj: Observable<any>|Promise<any>|null|undefined): any {
if (!this._obj) {
if (obj) {
this._subscribe(obj);
}
this._latestReturnedValue = this._latestValue;
return this._latestValue;
}
....// some extra code here not interesting
}
so if the value passed is not undefined, it will subscribe to that observable and act accordingly (call markForCheck(), whenever value emits)
Now it's the most crucial part
the first time Angular calls the transform method, it is undefined, because you initialize searchText$ inside ngAfterViewInit() callback (the View is already rendered, so it calls async pipe also). So when you initialize searchText$ field, the change detection already finished for this component, so it doesn't know that searchText$ has been defined, and subsequently it doesn't call AsyncPipe anymore, so the problem is that it never get's to AsyncPipe to subscribe on those changes, what you have to do is call markForCheck() only once after the initialization, Angular ran changeDetection again on that component, update the DOM and call AsyncPipe, which will subscribe to that observable
ngAfterViewInit() {
this.searchText$ =
fromEvent<any>(this.input.nativeElement, "keyup").pipe(
map((event) => event.target.value),
startWith(""),
debounceTime(300),
distinctUntilChanged()
);
this.cf.markForCheck();
}
The changeDetection: ChangeDetectionStrategy.OnPush allow to the component to not triggered the changeDetection all the time but just when an #Input() reference is updated. So if you do all your stuff in the same component, no #Input() reference are updated so the view is not updated.
I propose you to Create your dumb component with your template code above, but give it the searchText via an #Input(), and call your dumb component in your smart component
Smart component
<my-dumb-component [searchText]="searchText$ | async"></my-dumb-component>
Dumb component
#Input() searchText: SearchText
template
<div *ngIf="searchText">
results for "<b>{{searchText}}</b>"
</div>
This is because Angular is updates DOM interpolations before ngAfterViewInit and ngAfterViewChecked. I know this sounds confusing a bit. It's because of the first change detection cycle Angular does. Referring to Max Koretskyi's article about change detection algorithm of Angular, in a change detection cycle these happens sequentially:
sets ViewState.firstCheck to true if a view is checked for the first time and to false if it was already checked before
checks and updates input properties on a child component/directive
instance
updates child view change detection state (part of change detection
strategy implementation)
runs change detection for the embedded views (repeats the steps in
the list)
calls OnChanges lifecycle hook on a child component if bindings
changed
calls OnInit and ngDoCheck on a child component (OnInit is called
only during first check)
updates ContentChildren query list on a child view component
instance
calls AfterContentInit and AfterContentChecked lifecycle hooks on
child component instance (AfterContentInit is called only during
first check)
updates DOM interpolations for the current view if properties on
current view component instance changed
runs change detection for a child view (repeats the steps in this
list)
updates ViewChildren query list on the current view component
instance
calls AfterViewInit and AfterViewChecked lifecycle hooks on child
component instance (AfterViewInit is called only during first
check)
disables checks for the current view (part of change detection
strategy implementation)
As you see, Angular updates DOM interpolations (at step 9) after AfterContentInit and AfterContentChecked hooks are called, so if you call rxjs subscriptions in AfterContentInit or AfterContentChecked lifecycle hooks (or earlier, like OnInit etc.) your DOM will be updated because Angular updates DOM at step 10, and when you change something in ngAfterViewInit() and you are using OnPush, Angular won't update DOM because you are at step 12 on ngAfterViewInit() and Angular has already updated DOM before you change something!
There are workaround solutions to avoid this to subscribe it in ngAfterViewInit. First, you can call markForCheck() function, so you basically say by using it on the first cycle that "hey Angular, you updated DOM on step 9, but I have something to change at step 12, so please be careful, have a look at ngAfterViewInit I have still something to change". Or as a second solution, you can trigger a change detection manually again (by triggering and event handler or using detecthanges() function of ChangeDetectorRef) so that Angular repeats all these steps again, and when it reaches at step 9 again, Angular updates your DOM.
I have created a Stackblitz example that you can try these out. You can uncomment the lines of subscriptions placed in lifecycle hooks 1 by 1, so that you can see after which lifecycle hook Angular updates DOM. Or you can try triggering an event or triggering change detection cycle manually and see that Angular updates DOM on the next cycle.
Related
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.
Suppose we have a getter defined like this:
get sku() {
return this.form.get('sku').value
}
And we use it like this:
<mat-hint *ngIf="!sku">Example sku123</mat-hint>
Is there a better way of doing this from an Angular Change detection performance perspective?
We could use an Observable. Something like this I think (Roughing this out):
sku$ = this.form.get('sku').valueChanges().pipe(untilDestroyed(this))
<mat-hint *ngIf="!(sku|async)">Example sku123</mat-hint>
Is one better than the other?
IIUC the getter will be called whenever there is change detection, but if we use ChangeDetectionStrategy.OnPush with the Observable then will only receive notifications when the form updates?
using ChangeDetectionStrategy.OnPush will not apply the change to your sku in the view *ngIf because automatic change detection is deactivated now. Only the event will be triggered without changing the view. Therefore, you will have to explicitly invoke it using,
constructor(private cdRef: ChangeDetectorRef) {}
this.form.valueChanges
.subscribe(() => {
this.cdRef.markForCheck();
});
}
<mat-hint *ngIf="!sku">Example sku123</mat-hint>
Note: Performance will be increased by using ChangeDetectionStrategy.OnPush as it only runs the cycle once. But will get complicated when it comes to reactive forms. And also all the child components inside the parent component using ChangeDetectionStrategy.OnPush will be inherited and you will have to invoke them manually if needed.
Getter is fine for the performance, except if the function is expensive as it will be called at each cycle. In this case, a pipe is preferred as the result is cached.
Regarding ChangeDetectorRef, I already know that -
detectChanges actually triggers change detection while with -
markForCheck - The actual change detection for the component is not
scheduled but when it will happen in the future (either as part of the
current or next CD cycle)
Taken from here
Looking at markForCheck - if it's not scheduled, so when will it run? Obviously after Observables callback, async callbacks and setTimout and events.
The docs has a component with OnPush strategy
#Component({
selector: 'cmp',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `Number of ticks: {{numberOfTicks}}`
})
class Cmp {
numberOfTicks = 0;
constructor(private ref: ChangeDetectorRef) {
setInterval(() => {
this.numberOfTicks++;
// the following is required, otherwise the view will not be updated
this.ref.markForCheck();
}, 1000);
}
}
Question:
If it marks for checking its ancestors, for the next cycle, so who ran the current cycle? (Is it the prev setTimeout invocation?)
Because this code is showing each second being replaced by another - in other words each second there is a change detection (?!).
What is actually going on here via a POV of steps?
markForCheck, as you guessed, and as the name suggests, will tell the Angular to mark the component to be change-detectable for the next cycle.
When you're writing setInterval, this is what you're actually writing:
constructor(private ref: ChangeDetectorRef) {
setInterval(() => {
this.numberOfTicks++;
// the following is required, otherwise the view will not be updated
this.ref.markForCheck();
detectChangesFromZoneJS(); // this is a psudo code, but you can imagine something like this will happen, which is patched by NGZone
// Note that this is the last function that will always be called by Zone at the end of all the async events
}, 1000);
}
This is handled by ZoneJS.
Zone monkey patches all of the async events, and when they finish, Zone will notify Angular and then Angular. knows it's time to detect the changes ( update the view based on the latest model changes), but when you're component is OnPush is not going to detect the changes, unless there has special things happened inside the component, ( like a click event, or any of the #input's is changed).
So when you're deliberately saying markForCheck, you're basically saying: "I know you shouldn't detect the changes because it's OnPush only, but I'm telling you to detect it anyway"
So here is step by step:
Component is initialized, Angular will detect all the changes, your view is updated
You run a setInterval, inside it, you're mutating your model, Angular knows it shouldn't update the view because it's OnPush
the first interval's callback gets called, we're inside the first function, you're marking the component to be deliberately checked, we're at the end of the interval function ( still the first one interval).
Zone notify's Anguar that an Async event is just finished, time to detect the changes
Angular looks at the OnPush and wants to ignore it, but remembers that you've marked the component to be checked by force
the view get's updated
we go to the second interval and so on.
componentWillReceiveProps and other lifecycle methods seems like deceptive temptation to bring unnecessary complexity and noise to the code in the hands of inexperienced React coder. Why do they exist? What are their most typical use cases? In the moment of uncertainty, how would I know if the answer lies in the lifecycle methods?
I have been using react for couple of months now, and most of my work is creating a large application from scratch. So the same questions have presented themselves in the start.
The following information is based on learning while development and going through multiple docs out there to get it right.
As asked in the question here are couple of uses cases for the lifecycle methods in react
componentWillMount()
This is called once on the server side, if server side rendering is present, and once the client side.
I personally have used it just to do api calls which do not have direct effect on the components, for example getting oAuth tokens
componentDidMount()
This function is mostly used for calling API's (here is why to call it in componentDidMount and not in componentWillMount)
Components state initialisations which are based on the props passed by parents.
componentWillReceiveProps(nextProps,nextState)
This function is called every time props are received except the first render
Most common use I have encountered is to update the state of my current component which i can not do it in componentWillUpdate.
shouldComponentUpdate(nextProps, nextState)
This method is invoked before the render happens when new props or states are received. Here we can return false if the re-render is not required.
I see this as a performance optimisation tool. In case of frequent re-rendering of parent component this method should be used to avoid unnecessary update to current component
componentWillUpdate(nextProps,nextState)
this function is called every time a component is updated, it is not called when component mounts
Carry out any data processing here. For example, when a api fetch returns data, modelling the raw data into props to be passed to children
this.setState() is not allowed in this function , it is to be done in componentWillReceiveProps or componentDidUpdate
componentDidUpdate(prevProps,prevState)
Invoked right after the changes are pushed to the DOM
I have used it whenever the required data is not at the first render (waiting for api call to come through) and DOM requires to be changed based on the data received
Example, based on the age received show the user if he is eligible for application for an event
componentWillUnmount()
As the official docs mentions, any event listeners or timers used in the component to be cleaned here
In the moment of uncertainty, how would I know if the answer lies in
the lifecycle methods?
What analogy i suggest
Change is triggered in the component itself
Example, Enable editing of fields on click of an edit button
A function in the same component changes the state no involvement of lifecycle functions
Change is triggered outside of the component
Example, api call finished , need to display the received data
Lifecycle methods for the win.
Here are some more scenarios -
Does the change in state/props requires the DOM to be modified?
Example, if the current email is already present , give the input class an error class.
componentDidUpdate
Does the change in state/props requires to data to be updated?
Example, parent container which formats data received after api call and passes the formatted data to children.
componentWillUpdate
Props being passed to a child are changed , child needs to update
Example,
shouldComponentUpdate
Adding an event listener
Example, add a listener to monitor the DOM, based on window size.
componentDidMount
'componentWillMount' , to destroy the listner
Call api
'componentDidMount'
Sources -
Docs - https://facebook.github.io/react/docs/component-specs.html
this scotch.io article which cleared the lifecycle concepts
Event Listener - https://facebook.github.io/react/tips/dom-event-listeners.html
Some typical use cases for the most commonly used lifecycle methods:
componentWillMount: Invoked before initial rendering. Useful for making AJAX calls. For instance, if you need to grab the user information to populate the view, this is a good place to do it. If you do have an AJAX call, it would be good to render an indeterminate loading bar until the AJAX call finishes. I've also used componentWillMount to call setInterval and to disable Chrome's drag and drop functionality before the page renders.
componentDidMount: Invoked immediately after the component renders. Useful if you need to have access to a DOM element. For instance I've used it to disable copy and pasting into a password input field. Great for debugging if you need want to know the state of the component.
componentWillReceiveProps: Invoked when component receives new props. Useful for setting the state with the new props without re-rendering.
componentWillReceiveProps is part of Update lifce cycle methods and is called before rendering begins. The most obvious example is when new props are passed to a Component. For example, we have a Form Component and a Person Component. The Form Component has a single that allows the user to change the name by typing into the input. The input is bound to the onChange event and sets the state on the Form. The state value is then passed to the Person component as a prop.
import React from 'react';
import Person from './Person';
export default class Form extends React.Component {
constructor(props) {
super(props);
this.state = { name: '' } ;
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({ name: event.currentTarget.value });
}
render() {
return (
<div>
<input type="text" onChange={ this.handleChange } />
<Person name={ this.state.name } />
</div>
);
}
}
Any time the user types into the this begins an Update for the Person component. The first method called on the Component is componentWillReceiveProps(nextProps) passing in the new prop value. This allows us to compare the incoming props against our current props and make logical decisions based on the value. We can get our current props by calling this.props and the new value is the nextProps argument passed to the method.
I'm using ionic2, I implemented a class:
import {EventEmitter, Injectable} from 'angular2/core';
#Injectable()
export class LocalPushClear extends EventEmitter<number> {
constructor() {
super();
}
}
The class is used by on of my components to connect cordova plugin event to another component which subscribe to LocalPushClear, I listen to clear events, ones it fires, I emit using LocalPushClear and some other component subscribes:
this._LocalPushClear.subscribe(data => {
// Some action is taken here
});
The thing is that, I was expecting automatic change detection to be executed upon subscription callback execution(when its done), but it seems like there is no change detection execution at all, I have to do something like click a button or wrap my Some action with zone.run, I'm not sure if its a valid behavior or maybe I'm doing something wrong.
Edit:
I traces the code and it leads to Subject, so its basically custom event emitter that angular NgZone don't know about(at least I think), but I'm sure, if anyone could confirm, maybe future explain I will be very thankful.
You definitely should not extend EventEmitter. EventEmitter is only supposed to be used for #Output()s. Just use a Subject instead.
Angular doesn't get notified about values emitted by EventEmitter (when used this way) or Subject. Normally the code that causes the Observable (Subject) to emit new values is executed by code that causes change detection when completed for example when called from an event handler or setTimeout.
In your case the cause seems to be that the code that emits new values using LocalPushClear runs outside Angulars zone.
You can use one of the methods explained in https://stackoverflow.com/a/34829089/217408 to trigger change detection after the Observable emits an event.