简体   繁体   中英

Angular 8 UI observable not triggering change detection - ChangeDetectionStrategy is set to default

I have a container component with a service injected into it. The service contains an observable. In the container component's template, I am binding that observable to a child component's input property using an async pipe. Everything seems to work except that I must interact with the UI to get change detection to fire and display the data that was pushed through the observable.

(by not updating I mean it seems that: <ng-container *ngFor="let section of sections;"> in the child component html is not executing)

I have tried many things including creating another observable in the parent component and relaying the data from the service through that observable - but this was just a troubleshooting measure and is very crufty. It had the same results I believe.

Note:

1. The service is pulling data from a .net backend via a socket (SignalR).
2. I am also using Angular Material's drag and drop library in this project.
3. **This post seems very similar**, but I can't discern a cause or solution from it: https://stackoverflow.com/questions/48955143/subscribing-to-observable-not-triggering-change-detection
4. I mention 3 because that person was using angular material's infinite scrolling library and I am using drag and drop. Maybe something in that library is interfering with change detection.
5. There is no OnPush change detection anywhere in this project

Service:

export class DocumentCreationService {

  private library = new Subject<DocumentSection[]>();
  public library$ = this.library.asObservable();
...
  private registerOnServerEvents(): void {

  this.proxy.on('documentSectionsRetrieved', (result: DocumentSection[]) => {
        this.library.next(result);
    });
  }
...
  public retrieveDocumentSections = () => {
    this.proxy.invoke('getDocumentSections')
              .then(() => console.log("getDocumentSections completed"))
              .catch(err => console.error(err));
  }
...
}

Container ts:

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent {
...
  constructor(public documentCreator: DocumentCreationService) { }
}

Container html:

<document-section-library #library [hidden]="selectedView !== 'library'" [sections]="documentCreator.library$ | async" [connectedDropTargets]="dropTargets">

Child ts:

@Component({
  selector: 'document-section-library',
  templateUrl: './document-section-library.component.html',
  styleUrls: ['./document-section-library.component.scss']
})
export class DocumentSectionLibraryComponent {
...
  @Input()
  public sections: DocumentSection[];
...

Child html:

  <div class="document-section-library__contentarea" cdkDropList id="sectionLibrary" #sectionLibrary="cdkDropList" [cdkDropListConnectedTo]="connectedDropTargets" [cdkDropListData]="sections" (cdkDropListDropped)="onDrop($event)">

    <ng-container *ngFor="let section of sections;">

      <ng-template [ngIf]="section.content && section.content.length > 0" [ngIfElse]="table">

        <document-section-custom class="document-section" cdkDrag 
          [id]="section.id"
          [templateId]="section.templateId" 
          [name]="section.name" 
          [enabled]="section.enabled" 
          [content]="section.content">
        </document-section-custom>

      </ng-template>

      <ng-template #table>
        <document-section-table class="document-section" cdkDrag 
          [id]="section.id" 
          [templateId]="section.templateId"
          [name]="section.name" 
          [enabled]="section.enabled" 
          [content]="section.content">
        </document-section-table>
      </ng-template>

    </ng-container>

  </div>

I would like the UI to reflect the data pushed via the observable immediately, without my needing to click on something or otherwise interact with the UI.

In this case I am trying to do this with an observable because I couldn't seem to get it working any other way either.

Ultimately, what I would really like, is to just have an array of DocumentSection as a public property in the service, update that array via methods on the service, and have the change to the array reflected immediately. I am not sure I even really need an observable for that. I would love to have the property on the service act like a local property on the component.

So two questions:

  1. What is going on with this change detection issue and how can I solve it?

  2. Why can't (or can) I, just create a public array on the service and bind that normally (not via an async pipe) and have it work in the same way it would if it were a public property on the container component?

Perhaps what's happening is that they subscription ( async in the container view) is happening after the initial documents have arrived, so Angular doesn't pick up that emission?

I would try changing:

private library = new Subject<DocumentSection[]>();

to

private library = new BehaviorSubject<DocumentSection[]>([]);

Alternatively, you could try doing without observables altogether, and binding to a basic array accessed via a getter.

private _library = []; //  update when data is fetched
public get library() { return this._library || []; } // data-bind this

Ultimately this seems to have been some kind of conflict/issue with Angular and JQuery and/or the signalr (pre .net core) library - that signalr library has a dependency on JQuery. I replaced the signalr library with a newer .NET Core version (and I updated the backend to .NET Core as well of course) and removed JQuery and everything worked properly. I wish I could have gotten a better root cause for anyone else hitting this, but after 48 hours fighting this, I'm happily moving on.

If anyone has any thoughts/ideas as to what about those two libraries may have been causing the issues, I am still interested.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM