5.3 The New Message Form
Of course, a conversation is useless if we can't add messages to it. Let's add that capability to our conversation component. We'll also use an
Observable to poll the server for messages from other users.
1.Introduction5 lessons, 29:28
2.The Projects List6 lessons, 39:35
3.The Users5 lessons, 31:28
4.The New Project Form4 lessons, 30:48
5.The Project Page4 lessons, 49:46
6.Conclusion2 lessons, 04:08
5.3 The New Message Form
In the previous lesson we built our conversation component which allows us to view a conversation as you can see here we've got a list of messages in the conversation but we can't do yet is add our own messages to this list of messages. So in this lesson, we're going to look at adding messages to a conversation. This time we're going to start in In the projects service. So far, we have functions for getting projects and creating projects and getting a conversation, but what we need now is a create message method. So let's go ahead and write create message. We're going to take a conversation ID which should be a string and we're also going to take a message which should be a string as well, and let's return this.http.post and we'll post two API/conversations/ and then we'll put the conversation ID in there. Then we'll add /messages, and that will be the URL, and then we'll just use this message as the body for this post request and then we'll just map that to this dot extract data and we'll get the response back. Okay, so now that we can create messages. Let's open up our conversation component and see what we need to do. The first thing we need to do is figure out if a user is allowed to make a message in this conversation. And of course they're allowed to make a message if we go back to our projects view here. They're allowed to make a message if they're. A part of that project. Basically, if they're avatar shows up on the project summary card. So since we're logged in as Jimmy would we should be able to message people in the Foobar App project but we should not be able to message people in the Annual Company Dinner project. The way we can figure that out from our conversation view is by looking at The project that we are in you might remember that from within our project component. We were able to figure out which project to display by looking at the route. And the same thing will be true for our conversation component. However our conversation component doesn't actually see the whole URL. Because this is a child route of the project route, the activated route really only includes conversation and the conversation ID. We can actually see this if we open up our individual project component, you can see that right here is our road object and so inside of our conversation component. The activated route only has the conversation ID token we can't actually get to the Project ID So what we have to do is use the router to get the parent route of our current route. So let's start by point a router in here up here from in our angular router imports. Let's get the router and we'll go ahead and include that in our constructor here so private router = router and then here within the constructor let's go ahead and create an observable for the project itself. So we can say this top project equals rotor and then we can say router.routerstate, and then we have .parent and parent as a method that we can call and we'll pass it wrote a notice wrote here is this variable here it's the activated route. So basically we're asking for the parent route of the currently activated route. Then just as we did with our activated route where we did wrote dot params, we can do the same thing here and say parent route dot params and then let's go ahead and map over our params object and we will get params Square bracket ID which is the ID of our project. And then finally we can do flatMap with that ID and we can pass it to, this.service.getProject and will pass at that ID. All right. And so now this Project here is an observable with a project inside of it and why don't we add that up here we can say the project is an observable that wraps our project and we don't actually have project here, so let's go ahead and import project from Project. So now that we have access to the project, let's go ahead and figure out if the currently logged in user is part of this project. In our class property section we'll create a property called writeable. This will be of a boolean that defaults to false. And then down here in ngOnInit, right at the top of this function, let's say this.username.subscribe, we created Created the user name observable last time and this is just an observable that wraps just the user name of the current user. So we can subscribe to the user name and then in here will say this.project.subscribe and subscribe to the project And then in here we can say this.writable=project.users.index of user name is it greater than minus 1. And if it is then we know that this will return true which means the user can write in this project and so right about will be set to true. Okay. So writable set to true, let's go ahead and open up our conversation component template again. And write down here after our md-ist. Let's create a paragraph here and we can say *ngIf 'writable'. Then in here we will have an md-input, this will have a type of 'text' and the class of 'full-width' We'll give it a placeholder of Message and then we will bind to the ngModel value and we will bind that to the message variable within our conversation component. Now this is some new syntax that we haven't seen yet. We've seen using square brackets to bind to a variable inside a component and we've seen using parentheses to bind a value from inside but Back out to one of our own variables. But what we're doing here is combining them. And basically what we're doing here is creating a two-way binding. So the square brackets means that whatever the value of message is will be set as the value of ngModel inside our empty input component here. And then the parentheses mean that whenever the value of We ngModel changes. It will reassign that to our message variable. So basically we're creating a two way binding between the value of this empty input and the message attribute in our conversation component. And we could actually set this message here we could just say messages going to be a string. If we wanted to provide an initial value we could do that. But we won't do that. Okay, outside of this, let's create another paragraph for some buttons. The first button here will also only be visible if writable is true, this will be an md-raised-button and we will bind to the click event here and we will call the handle click function within our conversation component. Let's also set the color here to primary, okay? And the text for this button will be send for sending a message then will also have another button here notice this empty raised button Will be visible whether or not the readable is true, and this is because it's just going to take us out of the conversation view. And so in here let's use router link and we're going to bind that here doubled.forward/, doubled.forward/. And I think this is kind of a neat feature of Angular 2. We can actually use this syntax to just say go back up to a previous route. We don't actually have to know what that is we just have to know our conversation component has two elements in its route. So if we back up two elements, we'll be back to our project field. Okay. So, this all looks pretty good one thing md input is not self closing so let's go ahead and fix that. I think that looks good and if we come back to the browser and view our conversation. You can see that this is showing up just fine. We have our message text box here, we have send and we have cancel And if I click cancel or take it back to the project view. Now, of course, right now if I type anything in the message box and click send, nothing happens except begin error because there is no handle click function. So, let's see what we can do about that. At the bottom of our conversation component, let's add a handleClick function. Let's start by subscribing to the username, so we'll say this.username.subscribe. And this way we can grab the username in here. Then we can create a message object, and we'll just call that msg. The Text of this will be this.message and the username will just be the username that we just received. We don't have to worry about adding the avatar to this server will do that for us. So then we can actually use our conversation ID observable here to make this message on the server. We can say this.conv_id .subscribe to get that conv_id value. And then in here, we can say this.service.createMessage. And you'll remember that create message took two parameters. This first one was that conv_id. And the second one was the msg object. And let's just shorten this up a bit. We'll just make this parameter name Id there we go, that's nice, but actually subscribes not quite right, we want to flat map over this. So we're going to map but we know this is going to return unobservable and so we will flat map. And then let's subscribe to the result of that. The result of that is going to send back a message, right. That is the saved message and so what we can do is we have this conversation object here. We can say this.conversation.messages and let's just push in the new message that we've just received. And that way we are updating a value and since angular will be watching and he properties that we have on this class that will automatically be updated in our template And so we don't have to worry about doing anything there at all. And then we can just say this.message = ' ' to clear the text box and we're done. We should be ready for the next message. So let's see if this works if we come back to the browser here, it looks good. We don't have anything any errors in the console lets add a message here. And I'll click send. And look at that, it's automatically added to our thread that looks really good. However, the problem here comes if we have multiple people signed in, in different browsers. So here what I've done is opened a second browser window and I've signed in April over here. And now we can both see this conversation. And let's say April goes ahead and just add a smiley face. She sees it immediately, but there's no way that Jimmy will see it at all because he's not looking for changes to that thread. Of course, if we refresh the page, then when that data is freshly requested from the server, he'll get the updated conversation. But it'd be nice if we could kind of pull the server and get that data every once in a while. So how might we be able to do that? Well, it's actually not gonna be too hard. As you might have guessed, the answer is once again in observables. Let's import some observable classes from rxjs. We will import the Observable class itself and we'll also import the Subscription class for typescript. And that is from rxjs/Rx. Our class is going to have a sub property which is going to be of the subscription type and now where do we get the conversation? We get the conversation right here inside of energy on the net lines 44 to 48 right now. This is where we get the conversation we get it just once from the server and that's all. But what we need to do is change this. So that we periodically ask the server for new data. So let's create this.sub, which is going to be our subscription, and we are going to say Observable.timer, and what this timer is going to do is it takes two parameters. The first one is an initial delay and the second one is a subsequent delay. So basically remember think of observables as a stream of data. What this observable dot timer is going to do, is it returns unobservable that emits a number first after the initial delay and then subsequently after each subsequent delay. So what we're gonna do here is just get an integer and these integers are just gonna be counting up zero, one, two, three, etc. And the first one will be immediately, because we have 0 as our initial delay. And then, after that, we will get another value out of this observable every 5000 milliseconds or every 5 seconds. And you might wonder, well, what good is this? The point isn't that we need those numbers, the point is just, we have an observable that sends a value at equal intervals, and so what we can do is flat map this observable. So we will say flat map and we don't care what value flat map receives is, instead we just want to return unobservable. And so we'll use this .conv_id.flatMap inside of this, and we will then call exactly what we've got down below. We can take the id and we can call this.service.getConversation and then pass that id. And this will return the conversation. And so every 5000 milliseconds, another value will come out of this observable. It will come to flatMap here and it will map it into a request for getting our conversation. And so then we can basically get rid of these two lines here. And we can just subscribe to that. And every time we get that conversation, we're just going to reset the value of this.con. And so this is a really neat way to periodic pull this Server for more information. So let's see if this works. If we come back to the browser you can see that they've actually both refreshed and we have April's message showing up on Jimmy's side. Let's go ahead and add another message for Jimmy here. We'll just. Give a simple message Hi. If I click send, Jimmy gets it right away and within five seconds we should see over here on equal side, there we go. Jimmy's message appears, and of course you can play with the delay depending on your usage, but there we go we're pulling the server. Now there is one more thing we have to worry about cleaning up here and I'm gonna show you this if we open up the Network tab here and I'm going to clear the Network tab. Then you can see that in a couple of seconds. We get another request here to A.P.I. slash conversations slash six and of course every five seconds. We're going to get a call to this but now let's navigate away from it and let's go over the Projects page here for example and at the bottom of this list you can see we're still getting requests because the observable object that we created is still making those requests and even though we're not looking at the view that needs them it's still making those requests behind the scene because we have a subscription to it. So basically what we need to do is destroy the subscription object and then there's nothing watching the observable and so the observable is smart enough to not emit these events if nothing is watching it. So, what we need basically is the opposite of OnInit, and the interface that we want to import here is OnDestroy. Now TypeScript allows us to implement multiple interfaces. So, we will include OnDestroy here. And then down at the very bottom of this class, let's add an ngOnDestroy method. And it's really simple, we just need to say this .sub.unsubscribe. Of course let's check to make sure this is working. So now, if we look at our conversation here you can see there's one request coming in. Let's wait for a second request. There it is now I'm gonna cancel this. And if we wait a couple of seconds here, you can see that no matter how long we wait no request comes in. That's excellent. All right, so now we can successfully add messages to our conversation and will even pull the server to look for messages from other people.