I usually manage to find what I'm doing wrong just browsing existing questions, but here, nothing has helped.
I'm working with a simple Ng2 module that attempts to list and update the contents of a NeDB store.
Mind you, I have no issues with the NeDB store, I have confirmed that it gets updated correctly, and correctly loaded initially, so the problems I have lie elsewhere.
The problems I have are the following:
"the async pipe doesn't work".
I have this module.
@NgModule({
imports: [CommonModule],
exports: [],
declarations: [WikiComponent],
providers: [WikiDbService],
})
export class WikiModule { }
I have this component.
@Component({
selector: 'wiki',
templateUrl: './wiki.component.html'
})
export class WikiComponent implements OnInit {
items: Observable<WikiItem[]>;
constructor(private _db : WikiDbService) { }
ngOnInit() {
this.items = this._db.items;
this.items.subscribe({
next: x => console.log("got value", x),
error: e => console.error("observable error", e),
complete: () => console.log("done")
});
}
}
I have this template.
<p>{{items | async | json}}</p>
<ul>
<li *ngFor="let item of (items | async)">{{item.name}}</li>
</ul>
<input #newName (keyup)="0">
<button (click)="_db.addByName(newName.value)">ADD</button>
And I have this service.
@Injectable()
export class WikiDbService {
private sub: BehaviorSubject<WikiItem[]> = new BehaviorSubject<WikiItem[]>([]);
private db: DataStore;
public items: Observable<WikiItem[]> = this.sub.asObservable();
constructor() {
console.log("BehaviorSubject", this.sub);
console.log("Observable", this.items);
this.db = new DataStore(
{
filename: path.join(app.getAppPath(),"wiki.db"),
autoload: true,
onload:
(err)=>{
if(!err) {
this.db.find<WikiItem>({},
(e,docs) => {
if(!e) {
this.sub.next(docs);
}
})
}
}
});
}
public add(v: WikiItem) {
this.db.insert(
v,
(e, nDoc) =>
{
if(!e) {
this.sub.next([...this.sub.getValue(),nDoc]);
}
}
)
}
public addByName(str:string) {
this.add({name: str, _id: undefined});
}
}
When routing to my component with a non-empty persistent store I get the following console log (corresponding to the logging in the OnInit method of the component):
got value > [] (wiki.component.ts:20)
got value > [Object, Object, Object, Object] (wiki.component.ts:20)
However my DOM stays as this:
<wiki>
<p>[]</p>
<ul>
<!--template bindings={
"ng-reflect-ng-for-of": ""
}-->
</ul>
<input>
<button>ADD</button>
</wiki>
So a manual subscription to my observable does work and gets me the values. But the async pipe doesn't get them.
Am I doing something wrong here, or is this a bug?
EDITS
12/19/16 3:45pm
The
ngFor
directive was "let item of items | async" before, and I thought maybe the async pipe was scoped to the item and not my observable so I added brackets, but results were unchanged. This is not relevant for the issue.
12/20/16 3.06pm
As per @olsn's advice, Initialized the component's items
property with an auto-log, to check if the template subscribed to the Observable.
It does. So it comes down to detecting the changes, I guess. Amending the title.
Adding this bit of information : My Component is now as such (commented changes)
@Component({
selector: 'wiki',
templateUrl: './wiki.component.html',
changeDetection: ChangeDetectionStrategy.OnPush // <=== I've read this might help. It doesn't.
})
export class WikiComponent implements OnInit {
items: Observable<WikiItem[]> = this._db.items //
.do(x => console.log("got value", x)) // <== new initialization, with a stream
.publishReplay().refCount(); //
constructor(private _db : WikiDbService, private _cd: ChangeDetectorRef) { }
ngOnInit() {
// <=== moved items initialization
}
reload() : void {
this._cd.markForCheck(); // <== added a button to force the change detector to react. Does not do anything.
}
}
with this addition in the template :
<button (click)="reload()">REFRESH</button>
@osln gave a correct answer.
The problem wasn't fundamentally about subscription or detecting changes, it was because my sub.next
call were in callbacks given to an external library, which concretely meant that I was doing them outside of Angular territory.
Forcing them back onto Angular soil with NgZone calls was the way to fix this issue.
Thanks @osln.
Try to initialize your item-object before ngInit and add a temporary log directly into the stream, that way you know if the template really subscribes to the stream, because your current log is done on a completely separate stream.
@Component({
selector: 'wiki',
templateUrl: './wiki.component.html'
})
export class WikiComponent implements OnInit {
items: Observable<WikiItem[]> = this._db.items
.do(x => console.log("got value", x)
// if items is not a Behavior- or ReplaySubject or ReplayObservable, also add the following:
.publishReplay()
.refCount();
constructor(private _db : WikiDbService) { }
ngOnInit() {
// ..nothing to do here
}
}
Additionally you might try to wrap your data-retrieval in an NgZone.run
:
First inject this in your DbService: private ngZone: NgZone
(from @angular/core
) and then instead of just using this.sub.next(docs);
, use:
this.ngZone.run(() => this.sub.next(docs));
(also for the add-call)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With