0

I am adding a css class to elements from a service, but the changes do not happen immediately. The changes take about a second to update the view. I need the changes to happen immediately with no delay. I have tried an ApplicaionRef and calling this.applicationRef.tick() in the service, but that did not work. I tried running the service's method in NgZone's this.zone.run, but that also did not work.

@Component({
  selector: 'calling',
  templateUrl: './calling.component.html',
  styleUrls: ['./calling.component.scss']
})
export class CallingComponent {
 constructor(private hidingService: HidingService) {}

 callHide() {
  this.hidingService.hide();
 }
}
@Injectable({
  providedIn: 'root'
})
export class HidingService {
 constructor() {}

 hide() {
  const elements = document.querySelectorAll('.hideable-field');
  elements.forEach(element => {
    element.classList.add('hide');
  });
 }
}

Here's the CSS that's in styles.css (accessible from all components) for reference, but this part is working. Adding this 'hide' class hides the field with this CSS:

.hideable-field {
  &.hide {
    visibility: hidden !important;
  }
}

If I use this hiding logic in the Component, it works. The fields are hidden immediately. If I use this hidden logic in the service it takes a moment.

johannchopin
  • 13,720
  • 10
  • 55
  • 101
Dwall
  • 1
  • 3
  • Not sure about the lag in service vs component. but you might want to update your logic and use renderer2 `this.renderer.addClass(this.el.nativeElement, className);` to make it more angular. Also for operations like this, i'd prefer to use directives on elements rather than using querySelectorAll to find the elements. its cleaner and more angular way. – pouya zad Feb 06 '20 at 17:57
  • May be there are many elements with `.hideable-field` class and iterating all the elements in forEach may be the reason for this delay. – Angela Amarapala Feb 06 '20 at 17:57

2 Answers2

0

It's typically an anti-pattern to have a service make changes that directly affect the HTML of your component. It's also not a good idea to use document query selectors in Angular. BUT, if you want to get something working quickly, try wrapping a timeout around the hide function in your service:

 hide() {
  setTimeout(() => {
    const elements = document.querySelectorAll('.hideable-field');
    elements.forEach(element => {
      element.classList.add('hide');
    });
  }, 0)
 }

A more correct approach would be to have a boolean isHidden property on each of your objects. Then, from within your template you can use [hidden] or *ngIf to conditionally display them. See Angular2: conditional display, bind to [hidden] property vs. *ngIf directive for a description of the difference between the two approaches. An example using *ngIf:

Somewhere in your component's template:

<ng-container *ngFor='let element of myElements'>
  <div class='my-element' *ngIf='!element.isHidden'></div>
</ng-container>

The new hide function in your component:

 hide() {
   this.myElements.forEach(element => element.isHidden = true);
 }
raychz
  • 1,085
  • 1
  • 8
  • 24
0

The solution turned out to be waiting for the UI to be updated before returning. There is a method called requestAnimationFrame() that allows you to do this.

export class HidingService {
 constructor() {}

 hide(): Observable<{}> {
  const elements = document.querySelectorAll('.hideable-field');
  elements.forEach(element => {
    element.classList.add('hide');
  });

  const subject = new Subject();
  requestAnimationFrame( () => {
    // Fields will hide in this frame
    requestAnimationFrame( () => {
      // Fields are hidden
      subject.next(); 
    });
  });
  return subject.pipe();
 }
}
Dwall
  • 1
  • 3