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:
What is going on with this change detection issue and how can I solve it?
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.