5.4 The New Conversation Component
The last component that we'll need is a form for creating new conversations.
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.4 The New Conversation Component
We're here looking at our project view. And the only feature that we have left to add to our application is creating new conversations. Right now, if we click the New Conversation button, nothing happens. So let's create our New Conversation form. Let's begin by looking at the project component. We have to add another route to this list here. And we'll add this route above the conversations with id route. So the route will be conversations/new. And when this route is used, we will use the NewConversationComponent. And of course, this means we have to include it up front here. So let's include the newConversation.component. Now, we can't forget to add this in our app module as well. So I'm gonna copy that line. And let's open up the app module. And I'm going to paste it right at the bottom of the list right there, NewConversationComponent. And let's not forget to add it to our list of declarations as well. All right. Now let's actually create this at newConversation.component.ts. And you know what, why don't we actually start with the template this time. So I will open up templates/newConversation.component.html. As you might expect we'll use an md-card with a class of conv-full. We'll start with an md-card-title of New Conversation. And then under that, we will have our md-card-content element. And the only real field we need here is the title. So let's go ahead and create a paragraph element. And in here, we will put an md-input element with a type of text and a placeholder of name. And once again, we will use our double binding syntax here for ngModel to create a two-way binding to the name attribute in our component. And then underneath this, what we need is actually really similar to what we have in our conversation view. So let's open that up. And right at the bottom here, we have a paragraph with two buttons inside. I'm gonna copy that, and let's paste that here. And we don't need this writable flag. For the click, we'll just call the function handleClick, that works. The text, instead of Send, can be Start Conversation. Otherwise, we will just cancel this and move back to the project route. Okay, that looks good. Okay, so with that in place, let's go ahead and create our component class. We'll start by importing component. Finally enough, the very last component we're creating does not need on init. What we will need is for TypeScript, we use Observable from 'rxjs/Rx'. We're also going to need the Router and the ActivatedRoute from '@angular/router'. And of course, we're going to need ProjectService, our good old service class. All right, let's create our component decorator now. The selector will be new-conversation. And the templateUrl will be templates/newConversation.component.html. All right, and now, let's export our class here, NewConversationComponent. And let's see what we need. We know we're going to have a title value, so why don't we just declare that at the top here as a string. And then we know the function that we need to write is called handleClick. And this is where we're going to create a conversation with the value that's in this.title. So to do this, we're going to need to inject the project service, but to get the project id, we're going to need to get the parent route of the activated route. So let's just go ahead and inject all three of those things. So we have our constructor here. We need private router, private route, and finally, private service. And why don't we get that project_id in here as an observable. Let's create a project_id up here, and this is just going to be an Observable that wraps a string. So up here, we can say router.routerState.parent, and get the parent of the route. And then we can get the params of the parent. And then we will map over those params and we'll get the params['id']. And of course, we need to assign this all to this.project_ id. And now we have an observable that is the project_id. Okay, so we can use that within handleClick. So what happens when they click? Okay, so let's use this.project_id. And we're going to use flatMap, this will get the id. And we can return this.service.createConversation, create conversation. We're going to pass it that id, which is the id of our project, and we're also going to pass it this.title, which will be the new title of the project. And of course flatMap here will unwrap the observable that createConversation returns, and this is all going to be a brand new observable. So, when this returns, we want to subscribe to that, and we're going to get our new conversation object. And what we're going to do is this.router.navigate, and we will navigate back up one level, and then, which will take us just to our project/projectid/conversation, which isn't actually a route that we have, but then we'll move back down by going to conversation/id. And because we're using navigate this time instead of navigate by URL, we actually have to tell it what route we're navigating relative to when we back up one. So what we can do here is pass an options object after that and say relativeTo: and we'll say this.route. So just to be clear, what's going on here is when the user clicks, we will map the project_id to a createConversation call, which uses the value of this.title. Then, when that call returns from the server, meaning we have successfully created our conversation, we'll subscribe to that, and we can navigate to the new id that was just created. And we're now getting relative to the current routes. So when we move back up one level, it's starting based on our current route. Okay, that should be all we need here. But of course we haven't created this createConversation method yet. So for the last time, let's go ahead and open up our projects.service. And let's see, we have getConversations. So underneath that, let's have createConversation. This is going to take an id, which is a string. And it's also going to take a title, or a name, really, is what we're calling this. So let's say a name, which is a string. And this is going to return an observable that wraps a Conversation. And then in here, we will just return this.http.post. And let's use back ticks here so we can interpolate directly /api/projects/, and then we'll put the id in there, and then /conversations. The object that we pass as the body of this post request will just be that name as a property. And then of course, we'll map this to this.extractData. So with that in place, everything should be working. So now when we come to our project page, I can click New Conversation and we see our dialog. I can Cancel this and go back to the Project page or I can add a New Topic here. Press Start Conversation and we have our New Conversation here. Okay, and we have a bit of a problem here. The problem is, we have no name showing up. So what's up with that? If we come back to our template here, you can see the problem is that we've used the name variable name with ngModel, but over here in our conversation component, for some reason I said title. Sorry about that, that should have been name the whole time. So we'll chang the two instances here that we have of that. And so now if we come back to our application and create a New Conversation. Let's add a New Topic here, and I'll press Start Conversation. And right away, we're taken to that New Topic. And I can go ahead and start up a message thread here. Okay, that's excellent. But notice there is something that is not being kept up to date. If I click Cancel here and we go back to this list here, you can see that we don't have our New Topic here. Now, if I went ahead and refreshed the page, then of course all three conversations would show up. But that's not really how I'd like to do it. So I guess what we could do, is poll the server looking for updates to our project object here. But that's not really necessary since the only update we need is coming from our own browser. Of course, other people may add topics, but I think those can be added on a refresh, that's okay. So let's make a simple emitter service that we can use to talk back and forth between these two components. So let's go ahead and create emitter.service.ts. This is of course going to be an injectable. So we'll go ahead and import Injectable from @angular/core. We also are going to need to import subject and subscription from rxjs. You'll remember the previous lesson where we discussed subjects and how they are observables and observers. Basically, they're one object that you can both publish new messages on and subscribe to messages from. So let's go ahead and use one of those to pass messages throughout our system. So, we're going to export a class which we will just call the EmitterService. And this is going to be really simple. We're going to create a private events subject inside of here. So this will just be a subject. And then we're going to have two methods here. The first one is going to be subscribe. And so far, we've only ever been subscribing to the next event on subscriptions. And so what we can do in here is do return this.events.subscribe, and we can pass it a next function, a function that it will run whenever it gets a new value. However, to be complete, what we can do is add error callbacks and onComplete callbacks as well. And all three of these are passed directly to the subscribe method. What happens here of course, as you might guess, is the error callback will be called if there's some kind of error in our events stream or whatever observable stream you're using. This isn't unique to the Subject class. And then of course, streams of data can complete, and so our onComplete function would call if this stream were ever to complete. All right, let's also add a next function, which is just going to take some event, which is the next value in our events stream. And so what we can do here is say this.events.next, and we'll pass at that event. And basically, what's going on behind the scenes whenever we subscribe to something is we're waiting for the next value, right? And so this way, we can have one set of our code, specifically our newConversation form can publish a next event. And our project component can be subscribed to those changes and can add that to the list when it sees that change. Okay, so this won't be too hard to use. Let's head over to our newConversation.component and let's import the EmitterService here, and this is just from emitter.service. We'll have to add this in the constructor here. So we'll say private emitter equals the EmitterService. And then finally, down here in handleClick, we'll need to do this when we receive our new conversation back from the server. Right before we navigate, we can say this.emitter.next. And we will just pass that conversation in as the next object. Okay, so now let's open our ProjectComponent. And let's import the EmitterService here as well. And let's see, where do we get the project? We get the project, okay, right here inside of this subscribe, after we have mapped over the route parameters and flat mapped our project. We then subscribe to the project. Okay, so after we have a project, let's say this.subscribe to hold a reference to our subscription. And we'll say this.emitter.subscribe. We will subscribe to our conversation here. And all we have to say is this.project.conversations.push and we will push in that newConversation. Now, of course, to get this emitter, we need to make sure we add it to our constructor. So I'll just say private emitter equals our EmitterService. And one more thing, when our project is closed, we should unsubscribe from the subscription. It doesn't matter too much, since we can't actually call our emitter from the newConversation form if the ProjectComponent is not on the screen. But we'll do it anyway for completeness. So at the top, we will import OnDestroy. And then in our class, we will implement OnDestroy. And finally, at the very bottom, we will write a function called ngOnDestroy. And this function will just call this.sub.unsubscribe. And there we go. That should be everything we need. Let's look over this. Actually, you know what, the one more thing we're going to need is, we need to provide our EmitterService as a provider here. So EmitterService here in the ProjectComponent and in NewConversation. Let's go ahead and add our providers list here. We're using ProjectsService here, so I guess this should be here as well, although Angular should have warned us about that. Anyway, we'll go ahead and add ProjectsService and EmitterService. There we go. All right. Let's come back to the browser and view an individual project, New Conversation. And now, even though this is great out in the background, it is still kind of live, right? So, cuz, I could click these URLs. It's just some box shadow that makes it look darkened as it is. So watch as I type this New Conversation and click Start Conversation. It should show up right away. So our last conversation, I click Start and nothing appears. Why is that? Now, if you've been paying very close attention, you should realize why. And the reasoning is something I mentioned right at the end of our AuthService construction. The problem is that we're providing the EmitterService inside of both newConversation and ProjectComponent. And that means we're getting two different EmitterService instances. Which means one is publishing and one is subscribing and they're both using different observable instances. So the right solution to this is to come into our app module here, let's go ahead and import the EmitterService. And then down here in our providers, we go ahead and add the EmitterService. And then we can remove it from the providers for our individual components. And now everything should be working fine. So for the last time, let's come back to our projects page. Notice that since the browser refreshed, we have our last conversation showing up there, but let's add a Final One. I'll click Start Conversation. And look at that, we're moved to the conversation dialog. However, it also appears as our list down here. And when we click it, we can view our conversation. So believe it or not, that is the end of our application. We have created all of our features and finished our project management application.