I'm trying to set up my router config using a Resolve that returns an Observable from a BehaviorSubject. I've tried this in both angular 4.0.0-beta8 and angular 2.4.8+router 3.4.8
Here's my service:
@Injectable()
export class MyService {
private _data: BehaviorSubject<Array<string>> = new BehaviorSubject(undefined);
constructor() {}
public getData(): Observable<Array<string>> {
this._data.next(['test1', 'test2', 'test3']);
let asObservable = this._data.asObservable().delay(1000);
asObservable.subscribe((myData) => {
console.log([myData, 'this console message DOES show up']);
});
// if I return here, my component's constructor and ngOnInit never fire
// return asObservable;
let fakeObservable = Observable.of(['test1', 'test2', 'test3']).delay(1000);
fakeObservable.subscribe((fakeData) => {
console.log([fakeData, 'this console message shows up']);
});
console.log([asObservable, fakeObservable]);
/* console log output
Observable {
_isScalar: false,
operator: DelayOperator,
source: Observable {
_isScalar: false,
source: BehaviorSubject {
_isScalar: false,
_value: ['test1', 'test2', 'test3'],
closed: false,
hasError: false,
isStopped: false,
observers: Array[1],
thrownError: null,
value: ['test1', 'test2', 'test3']
}
}
},
Observable {
_isScalar: false,
operator: DelayOperator,
source: ScalarObservable {
_isScalar: true,
scheduler: null,
value: ['test1', 'test2', 'test3']
}
}
*/
return fakeObservable; // this WILL reach my component constructor and ngOnInit
}
}
Here's my resolve
@Injectable()
export class MyResolver implements Resolve<Array<string>> {
constructor(private myService: MyService) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Array<string>>|undefined {
return this.myService.getData();
}
}
Here's the router
RouterModule.forChild([{
path: 'mypath',
component: MyComponent,
resolve: {
data: MyResolver
}
}]);
And here's the component:
@Component({
selector: 'my-component',
template: '<Span>My Component</span>'
})
export class MyComponent implements OnInit {
constructor(private route: ActivatedRoute) {
console.log('component constructor');
}
ngOnInit(): void {
console.log(this.route.snapshot.data['data']); // ['test1', 'test2', 'test3']
}
}
This is probably not the best way of designing the interaction between the resolve and the service, so I'm very open to suggestions there. However, I might go crazy if I don't figure out why BehaviorSubject.asObservable() doesn't work, but the mocked observable does work.
I thought about this one overnight, and realized that I was using the resolve incorrectly. The crux of the problem is that the router expects the resolve result to eventually be completed. The BehaviorSubject, even though it only has one value at a time, will never be done, because the value can always change. I changed this._data.asObservable()
to this._data.asObservable().first()
, and it started working. It seems so obvious now!
For Angular 6+ to complete observable in resolver you need to do as follows:
this.myService.getData().pipe(take(1))
// or
this.myService.getData().pipe(first())
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