10

I've been struggling with some stuff in Angular4. Please consider this Plunker: https://embed.plnkr.co/Sh4LaBtXOfxTzeL996jb/

It's a simple app which has a list of 3 items. It displays those 3 items in a subcomponent that lets the user edit the item.

I want to update the state on every keystroke (keyup). As I emit the new value, the data in the parent component (App) is updated, which causes a rerender of all the item subcomponents, which in turn causes the input field stop being focussed (as it is replaced).

That means the user can only type a single character before needing to refocus the input.

Is there any way to prevent the rerender / keep the focus on the input or a better way to set it up alltogether?

As a temporary fix for this I'm now registering what field has been changed in localStorage and on ngAfterViewInit I'm resetting the focus... which works but is ugly. I feel there should be a better way?

(In the Plunker I've setup a very simple update but in my real app I'm using firebase, subscribing to the list info which gives me the list as a whole when it emits. When that happens I set this.list to the data that's emitted, which causes the rerender.)

Thanks!

5
  • why not using a HostListener ? Commented Aug 23, 2017 at 15:30
  • Well, most of us learn here, more or less, could the person, who down voted, explain why ? Commented Aug 23, 2017 at 15:43
  • @Hitmands Thanks, I'll look into that Commented Aug 23, 2017 at 15:48
  • @Hitmands Could you maybe elaborate on using HostListener? I've read up on it, but not sure how this would help me. Commented Aug 23, 2017 at 16:15
  • Please see my update Commented Aug 23, 2017 at 19:37

3 Answers 3

13

You could also use the ngFor track by function to prevent rerendering. When you use track by, Angular recreates DOM nodes when the specified attribute changes and only then, otherwise it reuses the DOM node and changes it.

Info about trackBy

Sign up to request clarification or add additional context in comments.

5 Comments

This is exactly what I was looking for. Thanks a bunch! I'll leave this Plunker where I implemented it here, for anyone interested: embed.plnkr.co/zH6Y9cxAxGneIVtLMz2u
@Meldon You should upvote and accept my answer, thank you and good luck!
I know, I have :)
Great answer! Helped me a lot.
brilliant - 2 hours of struggle comes to an end after this.
3

you can import ChangeDetectorRef and manually disable / re-enable changes for a specific component as needed.

 import { ... other stuff... , ChangeDetectorRef } from '@angular/core' 

And then in the component

 constructor( ... other stuff... , private cdr: ChangeDetectorRef) { ... } ngOnInit() { this.cdr.checkNoChanges(); } 

That can cause all sort of view wackiness though, so you may just take other commenters suggestion and worry about preventing the focus change, rather than disabling change detection altogether. You could also try setting change detection to OnPush mode, which would be less drastic.

 import { ChangeDetectionStrategy ...otherstuff } '@angular/core' 

and then use it in the component metadata of the top-most component you need to stop automatically propagating changes downward

@Component({ selector: 'some-name', changeDetection: ChangeDetectionStrategy.OnPush, template: ' etc..' 

3 Comments

Thanks! I've played around with the ChangeDetectionStrategy but that it still detects changes if inputs are changed (by design). I'll look into ChangeDetectorRef.
Yes i tested it in the plunker and it didn't seem to have any effect. But even disabling changes entirely didnt seem to have an effect in the plunker, so im not sure if the plunker is working right. Or maybe I don't know how to plunker.
So, I played around with ChangeDetectorRef, but can't get it to work. See embed.plnkr.co/cEMaj2Pf9CxWVIFxv0Ys I detach it in ngAfterViewInit but the problem persists. I think this is due to the parent (AppComponent) being updated, completely rerendering its view and thus creating new EditComponents, and any settings in previous EditComponent instances are being discarded. I can't detach the parent's ChangeDetector because there's some other stuff that relies on changes. Also, it feels like rewriting the change detection mechanism.. Anyway, thanks again for you answer!
0

UPDATE:

 doUpdate(data) { //no need to update the whole list, only the item will do the trick! this.list.items[data.id].name = data.newVal; } 

The problem is that the list item was generated each time thus the DOM was updated also, so the focus was lost.

Plunker

Old answer:

This will take care of the focus changing:

 doUpdate(data) { data.preventDefault(); ...... 

5 Comments

Thanks for your answer, I don't know why you got a downvote. Your answer does prevent focus changing, but it also somehow prevents the update taking place. Not sure what happens there, preventDefault() as I know it in a regular event does just that: prevents the default behavior. After, you can do whatever, which doesn't seem the case here.. Anyway, I'd still like to see the changes reflected in the json text inside the <pre> tags (which doesn't seem to happen in your suggestion), just not having the focus changed.
Thanks for the follow-up. Yeah, I tried it, but then the input is being unfocussed again...
I appreciate your continued efforts! Unfortunately your updated answer doesn't seem to quite cut it. It should be this.list.items[data.id - 1] = { ... } as the id's start at 1 and the array is 0-indexed. Anyway, if I try that, the situation doesn't change, it is still rerendered, changing the focus... see embed.plnkr.co/seitDE0f3d3E0Vjk1pOF Also, I'm kinda tied to updating the list as a whole as that is what's emitted from firebase (at which point I do not know what action took place). Maybe my setup is just wrong or the problem is inherent to how the change detection works...
Thanks again for your update. It's not entirely accurate: In the update method of the EditComponent you're passing this.toto as the id, which causes a new item to be added to the list as the id doesn't match. You'll see if you console.log(this.list.items) instead of console.log(this.list.items[data.id-1].name) in doUpdate in the AppComponent. If you pass the correct item id (this.itemId), the component is rerendered and loses focus
Thanks a lot for your persistence, I've upvoted your answer as it came very close to what I wanted. Please check out the accepted answer as that turned out to be exactly what I was looking for, while keeping updating the list as a whole.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.