7

I'll start this question from notion that I've seen a similar question on StackOverflow, but that question had only answer for the difference.

What I'm asking is what should I use depending on situation and what drawbacks one or another method may have.

I know that detectChanges runs immediate change detection cycle on an element and its children, meanwhile markForCheck only marks current element and its ancestors as dirty and that they should be checked on the next change detection cycle.

I'm asking this mostly because I don't feel like I should always use markForCheck in async calls.

For example I have an InputComponent which is a wrapper for a regular HTML input. This InputComponent has ChangeDetectionStrategy.OnPush enabled.

When I make an asynchronous call to the server and get the data I need to run the change detection on that InputComponent to update a list of options and I have two options for that.

First (what I feel I should be using) is detectChanges because it would apply checks only for this exact component, whilst markForCheck would cause the whole tree branch to be checked.

So what should I use and do I need to use markForCheck ever and why?

Sergey
  • 7,184
  • 13
  • 42
  • 85

1 Answers1

4

What I'm asking is what should I use depending on situation and what drawbacks one or another method may have.

You should never call detectChanges().

There isn't a good edge case where detectChanges() offers value to the developer. It's usually used inside projects where immutability, state management and mutation of the component have not been well managed by the programmer.

All source code that needs detectChanges() can be rewritten so that it's not required.

On the other hand, markForCheck() does have good edge cases where it should be used.

I'm asking this mostly because I don't feel like I should always use markForCheck in async calls.

You will often find a reference to this near source code that calls markForCheck().

@Component({...})
export class ExampleComponent {
    //......
    public function work() {
        this.httpClient.get(...).subscribe(resp => 
            this.data = resp.data;
            this.changeDetectorRef.markForCheck();
        });
    }
}

In functional programming, a reference to this is impure and mutates an external state outside the scope of the function. Breaking from functional programming best practices introduces problems that require fixes to keep everything working. If you write only pure functions with your async operations you never have to call markForCheck(), but once you introduce a this reference the components state is mutated and the view needs to be notified.

There is nothing wrong with the above, but at the sametime excessive usage of this in RxJS subscriptions creates source code that can be difficult to maintain.

It's better to rewrite your source code to use reactive programming, and use the async pipe in the template. The key is to create components that are stateless so that a properties on the component don't need to be updated. Everything is done as a reactive stream.

@Component({
    template: `<ng-container *ngIf="data$ | async as data">
               <!-- stuff -->
               </ng-container>`,
    // .....
})
export class ExampleComponent {
    public data$: Observable<any>;

    public function work() {
        this.data$ = this.httpClient.get(...).pipe(shareReplay(1));
    }
}

If you design your components to be stateless and use RxJS for all of your data processing, then there shouldn't be a requirement to use markForCheck(). Even when you listen for DOM events the data can be piped to other observables to avoid using this.

While there will be times when you have to call markForCheck(). I recommend that you stop and rethink your approach to avoid the use of it, because there should be another way that doesn't require it.

Reactgular
  • 52,335
  • 19
  • 158
  • 208
  • 1
    But isn't it a huge kick on performance since `markForCheck` would cause the whole branch to be updated instead of a single component and its descendants? Also, you've talked about immutability. Why are we talking about immutability and `markForCheck`/`detectChanges` methods? As far as I'm aware the first one "plans" the change detection whilst second performs it immediately. In any case both do the same job in different ways and "volumes" – Sergey Aug 06 '19 at 19:11
  • @Sergey *mutation* is the source of all view changes. Immutability is a design pattern in front-end development that manages *mutation* so that we can update views more effectively. `markForCheck()` does not add any performance overhead, because Angular only renders the view that has changed. It does not render the whole branch. The whole branch is only rendered if you are **NOT** using `OnPush` change strategy. – Reactgular Aug 06 '19 at 19:16
  • Maybe I put it wrong. I meant that it would run check for a branch upwards (one check vs 50 checks has a difference). You said to make all in RxJs way. Does it mean that even simple calculation functions should be rewritten to work with a stream? It seems a bit overhead (especially if data can be eventually consumed by another component via `ViewChild`). Also, can I still work with local variables to return different results in different circumstances from a stream? – Sergey Aug 06 '19 at 19:21
  • 1
    @Sergey what is easy to read and maintain is likely the best approach for you. Try to do more with RxJS, and use more `thing$ | async` in the templates to learn. It takes time to change your thinking so that you can solve problems using RXJS, but later you will find more usages for it and need to use `markForCheck()` less. That's all I can say about it. You can see by asking your question that you're starting to learn. – Reactgular Aug 06 '19 at 19:27
  • Why do you suggest using `async` when it actually does `markForCheck` under the hood and this is still a state but all around the template and not accessible from `component.ts` – Sergey Aug 06 '19 at 19:37
  • It can be accessible in the component like this ``. It's really easy to work with, and don't worry about the internals of the async pipe. It's hard to explain things in comments. Take a look at my bookmark project, because I use it allot there. https://github.com/reactgular/bookmarks – Reactgular Aug 06 '19 at 19:42
  • 1
    I keep asking myself the same question as @Sergey . And @Reactgular's response doesn't actually answer that. `async` just hides `markForCheck` inside. But the whole branch from the root to my component is marked dirty and reevaluated. If any component in this branch includes a method in a template, this method will be called. `detectChanges()`, on the other hand, always goes downward - so if immediate children of this component use OnPush, then the only effect of `detectChanges()` will be checking a single component. This seems to be exactly what a developer intends to achieve. – amakhrov Nov 24 '19 at 04:06
  • 1
    @amakhrov for now I have an idea that the key difference here is that `detectChanges` actually *runs* change detection meanwhile `markForCheck` just *says* to Angular that it should be checked. What it means for us is that triple `markForCheck` would result in one change detection, when triple `detectChanges` would cause triple change detection runs what could cause a huge performance degradation – Sergey Nov 24 '19 at 07:15
  • @amakhrov maybe this will help: https://stackoverflow.com/questions/41364386/whats-the-difference-between-markforcheck-and-detectchanges – Reactgular Nov 24 '19 at 12:07
  • I think the point of my answer was to explain that `detectChanges()` is a *code smell*. If you're using it to solve an async side effect, then you should probably implement your code using the `async` pipe instead. There are very rare edge cases where you have to call `markForCheck()`, but if I do a search in a project for these keywords and find them. I often find source code written by someone who doesn't understand how to use Rxjs effectively. That means, spend less time trying to figure out how views work, and more time learning observables. – Reactgular Nov 24 '19 at 12:14
  • 1
    @Sergey - agree with the point on multiple runs of detectChange(). It's quite easy to implement a promise-based debounce, though. Anyway I can hardly think of a real-life case where I would want to call it multiple times in a row. @Reactgular "If you're using it to solve an async side effect, then you should probably implement your code using the async pipe instead". Let's say I'm implementing my own Async pipe. Why would I use `markForCheck()` internally (that's what Async pipe does) instead of `detectChanges()`? – amakhrov Nov 24 '19 at 19:38
  • And a note on code smell. I guess using imperative way for triggering change detection is inevitable when interacting with a component via its public methods. It's not my favorite pattern, but it seems to be widely used in `angular/material`: - https://github.com/angular/components/blob/master/src/material/core/option/option.ts#L149 - https://github.com/angular/components/blob/master/src/material-experimental/mdc-slide-toggle/slide-toggle.ts#L281 And in such cases those components call `markForChange()`. Again - why not `detectChanges()`? – amakhrov Nov 24 '19 at 19:41
  • @amakhrov I think that's because of thing I've described above about triple calls. It's not about calling by intent but because of multiple calls by any reason. Just let Angular decide when and how many checks to run – Sergey Nov 25 '19 at 07:50
  • @Sergey ok, I agree with that. Indeed, with the existing implementation only markForCheck allows repeating calls safely. I guess I'm actually wondering why it was implemented this way in the first place. Why not "mark **self** dirty" instead of "mark self and all ancestors branch dirty"? – amakhrov Nov 25 '19 at 16:50
  • There is a related issue (feature proposal) in Angular repo: https://github.com/angular/angular/issues/14628 . – amakhrov Nov 26 '19 at 18:39
  • How about third party libraries like some canvas libraries that mutate the objects directly? I'm using runOutsideAngular to avoid change detection. Reassignment would cause many troubles in my case. – Chi Feb 19 '21 at 16:32
  • 1
    Very subjective and misleading conclusion "You should never call detectChanges()." - there is no better way for just reflecting changes for one isolated independent component than calling detectChanges(). And rxjs is valuable and essential, but it is not a silver bullet. – Fes9L Mar 14 '23 at 19:01