- Read Tutorial
- Watch Guide Video
As we continue down the proposal journey we are going to now create the ability to have this
link to a real proposal because right now we're really just pulling in the parameters. And what I really want is to bring it in so it brings in the proposal on the show page so to do that we're going to start off in the proposal.service.ts
and then we're going to connect this with the proposal show component starting off we're going to create a new function. So we have getProposals()
. We want to create a function called to getProposal
(singular) and this is going to take an id:
and it's going to be of type number
. That's what the argument is going to be. And inside of this, it's going to be a pretty straightforward data call. So I'm going to say return this.http.get(this.proposalsUrl
and then append a "/"
. And then after that a +
and then id
. And then we're going to add the JSON call. So this is going to be .json
and that is everything that we're going to need on this side
getProposal(id: number) { return this.http.get(this.proposalsUrl + "/" + id + '.json') }
and this id
obviously we're getting from the argument and just so we can test this out and say we know we're not crazy. The URL is 'http://localhost.3002/proposals.json'
. We can ignore the JSON side of it though. And if I pop this out. And just go to localhost:3002/proposals/1.json
. So if I go to that then it's going to bring this up so what it's essentially doing is in the Rails application this is calling the show action. And that's exactly what we want. So here we are. And so now what we're going to be doing is we're calling id
we're passing the id
in that we get from getProposal
and we're going to be passing that into JSON.
So now that we have that let's open up our proposal-show.component.ts
and once we have that open we can start integrating some of the other things that we're going to need. The first thing we're going to need in this is going to be adding in the Input
module and then after that, we already have ActivatedRoute
and Params
but additionally we need to bring in all of those Http
libraries so we're going to have import
and here we're going to have Http
then we're going to have a Response
followed by Headers
followed by RequestOptions
and that's going to be pulled directly from the '@angular/http'
and that's everything we need on that side and then lastly we need to pull in the Observable
library so bring in import
and Observable
. I told you earlier on that we were going to be using observables a lot. So definitely want to get used to them because they are one of the key components of working with any kind of reactive style programming like we're doing. So this is going to be observable from 'rxjs/Rx'
. And let me make sure that I'm pulling this in from the right library. Yes that looks right. If it was wrong then observable would be giving us a little error message right here. But this is everything we need on that side. And now let's also bring in our ProposalService
from './proposal.service'
. And that's all we need there.
import { Component, OnInit, Input } from '@angular/core'; import { ActivatedRoute, Params } from '@angular/router'; import { Http, Response, Headers, RequestOptions } from '@angular/http'; import { Observable } from 'rxjs/Rx'; import { Proposal } from './proposal'; import { ProposalService } from './proposal.service';
And then we'll need to add a provider. So in our list of providers we're going to add in here the ProposalService
and that is everything.
providers: [ ProposalService ]
This is all the data we need to add inside of this component. And so far so good. We don't have any errors moving down the line. We have our class. And so we have our ProposalShowComponent
which implements OnInit
. We have a few items here we have an id:
and a routeId:
. We aren't going to need these anymore. These are just for handling our sample data so we can get rid of these. We're going to have a constructor and we do need to have some additional items that we're going to use dependency injection on. So first we have ActivatedRoute
and we are going to need to keep that. But we have two other ones that we need. The first one is going to be the proposalService
and that is going to be of type of ProposalService
and make sure you have commas after each one of these. And then the last one is going to be http
or you could do the first one it doesn't really matter. So you can do http
and say this is coming from the Http
library.
export class ProposalShowComponent implements OnInit { constructor( private http: Http, private proposalService: ProposalService, private route ActivatedRoute ) {}
So that gets rid of all of our error messages and now we are good to go. Next thing we need to do is we need to actually create an input so we're going to use an attribute decorator.
@Input() proposal: Proposal;
All this is saying is that we need the ability to slide in the input we're not putting any metadata or anything like that but we are going to be grabbing that. So if you hover over it Ill give you some additional data it says declares a data-bound input property angular automatically updates data-bound properties during change detection. And this is one where I spoke with another developer and showed him the code and we talked about some things. This is one thing that we may or may not need we may need to add this or we could probably take it away right now but in order to scale it up later, this is going to be something that we're going to want so we can just keep it in here. So next thing is our ngOnInit()
and we can get rid of all of this code because that was for our hard-coded items and now we can go and we can create our connection to our observable. So, I'm going to say let
and we'll say proposalRequest
and set this equal to this.route.params
.
ngOnInit(): void { let proposalRequest = this.route.params }
And so this is just creating a variable called proposalRequest
and it's just grabbing this.route.params
. Technically we wouldn't need to do this we could just use this data right here but I think proposalRequest
makes more sense and so it will be easier to read especially later on when we come back and look at this code and wonder what in the world this.route.params
would represent.
So this is representing our proposalRequest
nd then on the next line, because we're chaining this, we're going to add a .
and I'm going to add a new function we haven't used before called flatMap()
now flatMap()
is one of the coolest functions that Observable
has what it does is, you know the map()
function and the map()
function works very well in certain cases. However in cases like this what flatMap()
does is it streamlines the entire Observable
stream so without having a thorough knowledge of observables it's kind of hard to picture but we talked about how observables are events that occur during streams and what flatMap()
does is it makes the entire process more straightforward. So if you don't use this and you try using a regular map()
function, what could happen is if a call comes in at the wrong time or at a time we don't expect, it could throw either an error or it could bring in some confusing data. So flatMap()
makes the whole process more straightforward. flatMap()
just like it's cousin and map takes a function as an argument. And in this case, we're going to pass in the params: Params
as the initial argument and we are going to have this function run and inside of this function we are going. So inside of this function, we're going to say
this.proposalService.getProposal(+params['id]));
If you remember we also add in the plus sign so that it converts it to a number or to an integer. And we're going to just grab the I.D. that's returned from the params so all this is essentially, is just something stored in a variable. So this proposalRequest
variable includes all of these items it includes the check on the params and then our iteration over using flatMap()
where we're converting our params into something that's calling our proposal service. So at the end of the day what this is really doing is it's just calling our proposal service so it's calling the service it's passing in the params I.D. which means that if you go up in the URL and you go to a proposal/5
it's going to go in find the ID of 5. That's all it's going to do. Now what we can do is say proposalRequest.subscribe
. Remember our old friend subscribe. This is going to actually make the observable work. And now we grab our response and we're going to create our anonymous function and say this.proposal = response.json()
. So this what this is doing is converting it into the JSON format. And if you're wondering where we got proposal that's what we did when we created this variable and we said it's of type proposal. So all of this should work.
ngOnInit(): void { let proposalRequest = this.route.params .flatMap((params: Params) => this.proposalService.getProposal(+params['id'])); proposalRequest.subscribe(response => this.proposal = response.json()); }
And so essentially just to review what we have here is we are creating a proposal request where we have our flatMap()
that is going to map all of our parameters and go and get the proposal. So this is going to be what actually makes the API request through our service and then from there we're subscribing to that. So we're using the observable then we're grabbing the response and we're setting it equal to this proposal and were converting it to JSON so we're taking that response value what's returned and we're storing it inside of this proposal. So now if I hit save and we'll see if we broke anything. And no. So far so good. Now if I click on this so far everything on that side (browser) is good in terms of there is no error. But now let's go take a look let's actually open up our view code so I'm going to go to proposal-show.component.html
. Right now we're calling ID which ID is no longer something that we have. So we have a proposal show and I'm going to get rid of this entirely and I'm going to make this be the proposal.customer
.
<div class="container"> <div class="card proposal-card"> <h1>{{ proposal.customer }}</h1> <div> The id for this proposal is: {{ proposal.id }} </div> </div> </div>
Say of and it looks like we have some kind of bug lets look on inspect and see what's going on. Click on console and what it says is unhandled promise rejection template parse error unexpected closing DIV tag. Oh well here I thought it was a big issue. We just forgot to close off that <h1>
tag when I deleted too much. So now we are on to real error. That one was just a parsing error, so what it says here is uncaught in promise error in the actual code let's see it says it's caused by cannot read property customer of undefined and so what it's saying is that this is undefined in the view. So let's hit save and now if I go to proposals and you can see that we have an error here where it says 404 not found. Ok this is what the error is and this is kind of what I was thinking the error might be as this is the real problem we have localhost when it makes this call. It is adding the JSON because of this URL. So let's see what happens if I take .JSON
off.
'http://localhost:3002/proposal';
Then, save and see if this fixes it and there you go. Ok. So now let's go back here at {{ proposal.customer }}
and we're still at an error.. So let's see what is causing this. I'm gonna go to proposals and just hit refresh entirely just to see what else is up. OK. These are working. Now if I click on one of these this is where we get the error. So it's saying that it cannot read property customer
of undefined so it can't read the value it brings in the proposal and we are in the proposal show and we know that we have a proposal right here but it is not actually bringing in the value we need it's bringing in an object and I'm going you get rid of this just so you don't have anything else that's confusing it. But the biggest problem is it's looking to find a customer here and it's not. And that's what the main issue is. So with that in mind what I think is happening is that the way that javascript works because it has its nonblocking nature and it uses Javascript hoisting. That means that this method calls (proposal.customer
)or this function call because that's essentially what it is we're calling customer on Proposal that this is occurring before the data actually gets there. So this is kind of a common problem that you'll see in any kind of javascript application when you try to call something that doesn't exist yet. It may exist in the future but not quite yet. So the easiest way to fix this is to put an if statement so I'm going to put an *ngIf="proposal"
Now if I hit save and this is gonna refresh. And now let's see. So now if I click on this. There you go.
Now this is working. So that's all it is is that you need to perform a check. And what this is going to do is it's going to wait until the proposal is there before it shows. Now the cool thing about this is it's not like it does an if statement if it's not there then it skips over it. It's just checking. And then as soon as it becomes available then everything inside of this div is then going to pop up. So we have that. Now let's actually populate our show page so I'm going to go to the proposal-new.component.html
. And I'm going to pull in all of this code
( Jordan copied the above code from the proposal-new.component.html
and pasted it into the proposal-show.component.html
file starting on line 5 )
and we're going to put it in a div so that our proposal can actually have all of the proposal items. So here is this. And let me see I want to make sure that I don't have a bad div or a misaligned here. So I think this is all I need. But we will see in a second especially if we get an error. We don't need this other nested div. That was when we had two things right next to each other which were not going to have now and then we can and unindent that.
<div class="container"> <div *ngIf="proposal" class="card proposal-card"> <h1>{{ proposal.customer }}</h1> <div class="col-md-12"> <p>Hi {{ proposal.customer }},</p> <p>It was a pleasure getting to meet with you and review your project requirements, I believe it is a great fit for the types of applications that I build out. Please feel free to check out some of my past projects <a href="{{ proposal.portfolio_url }}">here</a>.</p> <p>To successfully build out the application I will be utilizing {{ proposal.tools }}, and a number of other tools to ensure that the project follows industry wide best practices.</p> <p>Ensuring that you are fully informed is one of my top priorities, so in addition to incorporating everything that we discussed, I will also be creating a project management dashboard and sending you a project update message daily so that you can have a transparent view of the development as it takes place.</p> <p>To accomplish the project and meet the requirements that we discussed, I am estimating that it will take {{ proposal.estimated_hours }} hours in development time to complete. The project should take {{ proposal.weeks_to_complete }} weeks to complete and with my hourly rate of {{ proposal.hourly_rate }}/hour, the estimated total will be {{ proposal.hourly_rate * proposal.estimated_hours | currency:'USD':true:'.0' }}.</p> <p>The next step from here is to set up a meeting to finalize the project and sign contracts.</p> <p>I am excited to working with you and build out this project.</p> </div> </div>
OK let's hit save and see what this looks like. There we go. OK let me close this up and look at that. Here is our show page.
We have "Hi Customer 8"
and it has all the information. And now also look at this. If you look down it has all of our calculations. Everything like this so now if we navigate proposals come to customer 5. It has it there it even has our link. So if I click this it, will pop us over to whatever the freelancer's portfolio URL is coming back and we're going to have everything load up just like before. So this is perfect. I'm very excited about this. Now though one change, I want to make. We're going to make in the next guide and that is going to be with our list so here in our proposal-list.component.html
right here you notice how we're using a router link.
<a routerLink="/proposal/{{proposal.id}}" class="proposal-link">
This is fine but I want to show you a different way of doing this where we can actually control the link outside of the view and actually move this in the component. So I'll see you then.