Reputation: 816
Let's simplify the question. Say the angular application only has a few components. In app component html,
<app-nav></app-nav>
<router-outlet></router-outlet>
The ts file we inject a service to get navigation menu. The service contains a BehaviourSubject to handle menu items.
@Injectable({
providendIn: 'root'
})
export class AppService {
public items = new BehaviourSubject<Item[]>([]);
public getMenu() {
return this.http.get<Item[]>(this.url);
}
public getItems() {
return this.items.value;
}
}
In app component ts file.
ngOnInit() {
this.service.getMenu().subscribe(
res => {
this.items = res;
this.service.items.next(this.items);
});
}
So far so good. Then in my nav component I can receive the items.
export class NavComponent implements OnInit {
items: any;
constructor(private service: AppService){}
ngOnInit() {
this.items = this.service.getItems();
}
}
Then in Nav html I have
<span *ngFor="let item of items">
<div>{{item.name}}</div>
</span>
So the normal step is to go to http://localhost:4200 then navigation bar is displayed correctly. However if I type the url of another component directly. Say
http://localhost:4200/app/one
It seems the page loads component one
first. The navigation bar is rendered before get the menu items. Actually I log the items in component one, it is empty. Eventually it also goes to app component but it is too late.
So my question is that I want to get menu items in the very first place. Whatever I typed any url I can always get the navbar menu items and rendered correctly. I doubt that I used BehaviouSubject
wrongly. Any hint?
Upvotes: 0
Views: 5436
Reputation: 14750
The problem you are having is that your nav component only sets the value of items
one time. Since your behavior subject has a default value of empty array, when OneComponent.ngOnInit
calls service.getItems()
it receives an empty array and that value is never updated on further emissions.
There is a question here that explores using BehaviorSubject.value
appropriately. I would discourage use of .value
in your case.
It actually makes the code much more complicated, and is breaking the "observability" of the data flow.
Since NavComponent
uses AppService
directly, AppComponent
doesn't need to be involved at all! Just let NavComponent
subscribe to items directly.
Also, it seems unnecessarily complex to have the AppService
call a service method to get the items, then another service method to have the service push the new value out. It would be much simpler if the service was responsible for pushing out the newest values and consumers of the service simply subscribe to the observable.
service:
export class AppService {
private items = new BehaviorSubject<Item[]>([]);
public items$ = this.items.asObservable();
public getMenu() {
return http.get().pipe(
tap(items => this.items.next(items))
);
}
}
The code above allows consumers to simply subscribe to items$
and receive the latest value automatically. Whenever getMenu()
is called, it will push the newest value through the subject.
The NavComponent
can use this directly, and the AppComponent
doesn't need to do anything:
nav component:
export class NavComponent {
items$ = this.service.items$;
constructor(private service: AppService){ }
ngOnInit() {
this.service.getMenu().subscribe();
}
}
<span *ngFor="let item of items$ | async">
<div>{{ item.name }}</div>
</span>
app component:
export class AppComponent {
}
LOL, nothing in the app component. Here's a working StackBlitz
So, going back to your original question:
How to get BehaviorSubject value in other component if I type the component url directly?
The overall idea is to reference the BehaviorSubject as an observable, so you always receive the most recent value; don't use .value
to pull out a single value at one moment in time.
Upvotes: 1
Reputation: 510
So you want the Menu Bar rendered on any page immediately?
If that's true, I think putting your initializing code into the app.component.t
s is not quite what you want.
I would say that using APP_INITIALIZER to load menu items before your application starts might be a better choice. See e.g. Angular: How to correctly implement APP_INITIALIZER
Upvotes: 0
Reputation: 31
You need to subscribe the items BehaviourSubject in component one.
Here is my solution:
items: any;
constructor(private service: AppService){}
ngOnInit() {
this.service.items.subcribe(items => this.items = items);
}
Because the Component One
was initialized before the get request done. At this time, the BehaviourSubject
didn't receive any new value so it emitted the seed value (an empty array). So I think subscribe()
will work.
Upvotes: 0