Angular 2 Router Using BehaviorSubject Observable in Resolve
Asked Answered
W

3

8

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.

Wurtz answered 21/2, 2017 at 3:40 Comment(1)
What's the problem? "doesn't work" is no information.Upmost
W
15

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!

Wurtz answered 21/2, 2017 at 19:14 Comment(1)
hmm doesn't seem to work with angular 5 , getting white screenRuminate
Z
4

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())
Zahavi answered 3/8, 2019 at 12:46 Comment(1)
I couldn't get this working. The component at a different route isn't getting the emitted event from another component. Do I have to have a resolver for simple data sharing between components at different routes?Cede
S
1

Seems like it's a bug.

Reproduced it in a plunker: https://plnkr.co/edit/XmjC2rJ20VQWCsfncVIc?p=preview

@Injectable()
export class MyService {
    private _data: BehaviorSubject<Array<string>> = new BehaviorSubject(undefined);

    constructor() {
      console.log('created MyService');
      console.log(symbolObservable);
    }

    public getData(): Observable<Array<string>> {

        this._data.next(['test1', 'test2', 'test3']);
        this._data.do(myData => {
          console.log([myData, 'this console message DOES show up']);
        });

        return this._data.delay(1000); // <-- won't work .. maybe an angular bug !!
        return Observable.of(['test']);
    }
}

Created a bug on github: https://github.com/angular/angular/issues/14614

Strafford answered 21/2, 2017 at 8:4 Comment(1)
I think I agree that this should be handled by the framework in some way, so I'm very interested to see what the feedback is on the bug.Wurtz

© 2022 - 2024 — McMap. All rights reserved.