Refetching Data with Subjects in Angular

Sam Julien
InstructorSam Julien
Share this video with your friends

Social Share Links

Send Tweet

One of the things that drove me crazy when I was first learning RxJS in Angular was how difficult it seemed to reload a collection of data after I added or deleted an item. With promises, I could just use .then() and be on my way. What do we do with observables? Should we just nest another subscription? Even if we do that, since we're using the async pipe, nothing will happen. There are better ways to handle this that are more reactive. In this lesson, I'm going to show you one way using Subjects. Subjects are kind of like event emitters that can have multiple listeners.

Sam Julien: [0:00] We've got a simple form here to create a new habit, but when I create a new habit, Feed dog twice a day, and hit Add, you can see that even though the new habit was created successfully on the server, nothing shows up in our list, unless I manually hit the Refresh button and then we go and get the list again.

[0:22] If we were just using promises here, we would probably just add a new call to our getHabits() method inside of a .then function, but that's not what we're doing here. We also can't just add a new call into our subscribe() method here, because we're using the async pipe.

[0:39] We're not actually subscribing to the getHabits method in our components code. We're subscribing through the async pipe in our template. What do we do?

[0:50] My temptation when I was first getting started with RxJS and Angular would have been to ditch the async pipe altogether and nest the call inside of a subscribe of our addHabit() method here, but there are better ways to do this that are more reactive. I want to show you one of those ways. In RxJS, there's always multiple ways of doing something. I'm going to show you one of them.

[1:11] What we need is something to be listening for when this addHabit method completes and then tells this getHabits call to refire. It turns out there's something that we can use for that called a subject.

[1:26] A subject is an event emitter that can have multiple listeners. It's still an observable, but it can do something called multicasting, which means it just has multiple listeners. It can listen for one thing and then trigger something else to happen.

[1:40] What we're going to do here is we're going to create a new private class member called refetchSubject, and we're going to create a new Subject(). Underneath our constructor, we'll go ahead and create a getter for this. We'll say get refetch(), and we're going to return our refetchSubject as an observable. The reason we're doing this is just so that nobody can overwrite the actual subject that we're using here.

[2:08] Now what we can do is in our addHabit call in the service. We can pipe in a call to our subject's next method. I'm going to use the tap operator here, which is an operator for side effects. In that tap() method, I'm just going to call this .refetchSubject.next().

[2:29] What this is going to do is once the call to create a habit is done, it's going to fire off the next method of the refetchSubject observable. Then that subject can notify anything else that's listening to it that something has happened.

[2:46] Let's go back over to our list component. Instead of using the getHabits call with the async pipe, we're going to switch that to use the refetch observable that we're making available from our subject. That way we can just pipe our call into it.

[3:02] I'm going to use the switchMap() operator. SwitchMap basically switches over the observable that we're using here. Inside of the switchMap, I'm going to call this .habitservice.gethabits().

[3:15] What's happening here is we're saying to the async pipe, "Hey, async pipe, listen to the refetch observable, and every time you hear something from it, switch over to going and calling our gethabits method."

[3:29] Let's look at our browser here. We do have a problem, don't we? Our list isn't showing up. Let's try adding a new habit, Eat veggies twice a day, and hit Add. Now you can see we are getting the full list, including the new habit that we've added. Our problem is that we're not getting the initial list of habits when we load the page. What's happening there?

[3:54] This is because we're using a plain Subject with the async pipe. A plain Subject doesn't initialize with a value. Even if we were to call the next method on that subject somewhere in the constructor of the service, there's no guarantee that the async pipe would subscribe to the subject in time for it to receive that value.

[4:14] If we weren't using the async pipe, we could just subscribe directly to the refetch subject, and then call the gethabits method ourselves in ngOnInit(), but I don't want to do that. Instead, I want to use a variant of the subject called a BehaviorSubject.

[4:30] What's cool about the BehaviorSubject is that it takes an initial value, so any subscribers will always get the initial value, even if they subscribe late to the observable. For our purposes, we don't need any particular value here, so I'm going to use null.

[4:47] You could do something with the parameters here. You could pass something like true or false to decide whether or not to trigger the refresh. That's one way of doing it. I'm just going to leave this as null, and that means we also need to use null here in our addHabit method.

[5:04] When the page refreshes, you can see that we're loading the list of habits. I'll refresh it one more time in case you didn't see that. Now we're loading the list of habits when the page loads, and then when I add a new habit, Call friends every week, and hit Add, you can see that we're refetching the data after the new habit is created on the server.

[5:25] This is one way that you can refetch data but still use the async pipe to handle your subscriptions for you.