Building Awesomer Apps with Angular Challenge 8: Solution
This course has been updated! We now recommend you take the Angular 9 Fundamentals course.
Transcript from the "Challenge 8: Solution" Lesson
>> Lucas Ruebbelke: So let's get into this challenge. So this was, in terms of complexity, this is kind of where we're kinda leaving the foundational things and starting to get into some more complex concepts. And so I wanna just go through this step by step and make sure that everybody has an understanding of what we're doing.
[00:00:25] Now what I have done is in the widgets.component, or rather, let me just show in the browser. I've went ahead and generated the list and the details, so they're just sitting there, and CLI commands. I haven't done anything else. And the idea is to take this Widgets list, put it in the widgets-list component.
[00:00:48] The widget-details layout, and put into the widget-details component. So all we're doing is we're taking this thing that exists, picking it up and separating it into two components. And then wiring up how things are going to communicate. So if we go into our code, the first thing I'm going to do is I'm just going to take this card,
>> Lucas Ruebbelke: Pick it up, and let's just paste it into the list. Then what I'll do is I'll go back to the template, and I'm going to copy this card.
>> Lucas Ruebbelke: And I'll paste this into the widget-details component.
>> Lucas Ruebbelke: Okay, and then from here, let's go ahead and delete these two columns, and let's see what happens.
[00:01:51] I think when this renders, what I expect to happen is that it's gonna kinda work, but not really. So this is kinda what I expected, kind of broad strokes but there's no data yet. So let's start with the list, it's kind of the easier of the two. So the first thing we need to do is we need to get widgets into this list.
[00:02:17] So let's go to the widgets list and we'll just go ahead, and I'm gonna just delete all this.
>> Lucas Ruebbelke: In fact, I'm not even in this case going to use OnInit.
>> Lucas Ruebbelke: And I'm just going to define an input, so @Input.
>> Lucas Ruebbelke: We'll go widgets.
>> Lucas Ruebbelke: And it is an array of Widget objects, okay?
[00:02:53] So we're just simply saying I want to expose a property of widgets to the outside world so that the parent component can populate it. Okay, so let's go back into the widgets.component and,
>> Lucas Ruebbelke: Let's go, because it's an Input, it's a property binding,
>> Lucas Ruebbelke: Which is pretty amazing that WebStorm just picked that right up, and we'll just go widgets.
>> Lucas Ruebbelke: And let's,
>> Lucas Ruebbelke: Check it out. So far so good, pretty easy. Let's add in, because we're here,
>> Lucas Ruebbelke: The,
>> Lucas Ruebbelke: Let's actually do it, I wanna try something real quick.
>> Lucas Ruebbelke: Actually this has to be event. This is kinda one of these things that gets me.
>> Lucas Ruebbelke: Method expression, this is actually selectWidget.
[00:04:09] So neither of these exist, I was going to see if it would try to create this for me.
>> Lucas Ruebbelke: And let's call this actually, yeah.
>> Lucas Ruebbelke: So in widgets-component,
>> Lucas Ruebbelke: We'll go selectWidget.
>> Lucas Ruebbelke: I'm just trying to make this a little bit more self-documenting. Let's just go saveWidget and cancel.
>> Lucas Ruebbelke: And I know we're going to need one more, let's just go deleteWidget.
>> Lucas Ruebbelke: And I'll just copy this out here. We're just basically gonna log it out and then reset the form, or actually not even reset.
>> Lucas Ruebbelke: But we are going to delete, okay? So now let's go into our component and let's create some outputs, @Output,
>> Lucas Ruebbelke: selected = new Event.
>> Lucas Ruebbelke: Emitter, and this is from the Angular Core.
>> Lucas Ruebbelke: And we'll do the same for output, for delete, rather.
>> Lucas Ruebbelke: And I'm gonna do one more input for read only.
>> Lucas Ruebbelke: We're just gonna set this to false. So two inputs, two outputs. Now let's go into our template here.
>> Lucas Ruebbelke: And,
>> Lucas Ruebbelke: I'm in the details, so let's go into the list, rather.
>> Lucas Ruebbelke: So instead of actually calling the method, so instead of saying selected, we need to actually emit, so we'll go selected,
>> Lucas Ruebbelke: .emit.
>> Lucas Ruebbelke: And then for the button, we're going to go click,
>> Lucas Ruebbelke: deleted.emit and we're going to send in the widget.
>> Lucas Ruebbelke: What we're also going to do, you may have noticed this,
>> Lucas Ruebbelke: Is because DOM events bubble, so the delete button is a child of the list item, that when you click on the delete button it's going to capture that click event at that element. But it's also going to bubble up, and it's going to fire this one, this event handler.
[00:07:21] And so what we have to say is,
>> Lucas Ruebbelke: After we've clicked this button, we actually want to stop the event. So event.stopImmediatePropagation. So what this does is it prevents it from deleting and then selecting it at the same time, that will cause unpredictable behavior.
>> Lucas Ruebbelke: And so now that we have this selected and deleted event firing, let's see what happens in our code.
[00:07:53] So I'll just pull up the Chrome Console. And,
>> Lucas Ruebbelke: In the details component, it's throwing up a little bit. So I don't think it, yeah we're trying to populate this and we haven't gotten that far yet. So let's press pause on this. Let's go over to the detail component, yes?
>> Speaker 2: In the [INAUDIBLE] can you parse iwo parameters or just one?
>> Lucas Ruebbelke: Just one.
>> Speaker 2: Okay.
>> Lucas Ruebbelke: I tried to do this one time, what I had to do is actually combine it into like a custom object. And so you can, what you need to do just, there's actually a pattern for this where you take a bunch of parameters and you just combine into a single, like a parameter object.
[00:08:38] So that's what I had to do with that. So just the event parameter is just kind of like the thing, you just have to respect that. I think this actually happened like at the last workshop somebody asked me, can you do two? And it ended up in the weeds.
[00:08:56] I was just kind of like nope, it has to be an event object. So single parameter. All right, so, back to our template over here. So now in our details component we want to send in a single widget.
>> Lucas Ruebbelke: And I believe this is selectedWidget. So let's go into our detail component and define the input.
[00:09:34] So what would that look like? First and foremost, we need to do what?
>> Speaker 2: [INAUDIBLE]
>> Lucas Ruebbelke: What's that?
>> Speaker 2: Pass through the widget data model?
>> Lucas Ruebbelke: So you need to pass it the widget, but how do we indicate that we can do that?
>> Speaker 2: Button?
>> Lucas Ruebbelke: Yes, so this is we're kind of putting a stake in the ground, we're saying okay, now we are Going to accept this property from the parent and it is going to be available to the entirety of the component.
[00:10:12] So we will go widget and this is of type widget
>> Lucas Ruebbelke: Like so
>> Lucas Ruebbelke: Let's see if.
>> Speaker 3: I kept getting stuck on that too.
>> Lucas Ruebbelke: Select a name.
>> Lucas Ruebbelke: And here,
>> Lucas Ruebbelke: So I think for right now we can do an Elvis operator. Or safe navigation operator.
[00:11:15] It's still doing it. And here and here. Actually I would wager that's where that's happening.
>> Lucas Ruebbelke: No! Things are going from good to awesome!
>> Lucas Ruebbelke: Ha, ha, ha.
>> Lucas Ruebbelke: Since I'm here.
>> Lucas Ruebbelke: I'm gonna have to undo this in a moment. What's happening is the isolating that's is binding to select widget, which we haven't actually set up yet.
>> Lucas Ruebbelke: One thing before we go any further
>> Lucas Ruebbelke: So And we were talking about shared mutable state and why it's a bad thing.
>> Lucas Ruebbelke: I change it here, it changes up here on the title, and also over here. And this is simply because we're passing by reference. And so they're sharing basically their pointers to the same object.
[00:13:01] You change it in one place, It changes in another place. And so this is,
>> Lucas Ruebbelke: Kinda represents one, there's no way to back out of this and undo it. The ship has sailed. But more importantly is that you're having an action here create or change something over here.
[00:13:23] And what you need to do for the sake of stability and avoiding unpredictable side effects. You need to isolate those. So let's go back and fix that. I'm just going to do some of this here,
>> Lucas Ruebbelke: Into our which detailed component. So now the difference is we're going to change this to a setter
>> Lucas Ruebbelke: And it's going to become a function.
>> Lucas Ruebbelke: Stick's value is gonna be a type widget. Then what we're going to do is create a selected widget property that we're going to do internally. That we will assign this to, so this.Selectedwidget equals a value. And let's go, let's do a ritual name as well.
[00:14:57] Now bare with me, I know I had a condition in here that I don't quite remember.
>> Lucas Ruebbelke: Also.
>> Lucas Ruebbelke: Right now, I'm actually not solving any problems so I'm still passing by reference. I'm just saying create a new pointer and assign in to a selected widget. What we have to do is use object.assign.
[00:15:28] And we're basically saying create a new object by combining essentially this blank object and value. And so what this does is this returns
>> Lucas Ruebbelke: A new object that basically clones, like it basically takes everything together and basically does a shallow copy which in most cases will work. But now we have a copy of the widget here using object of the sign.
[00:15:58] And I talk more about kind of some immutable operations tomorrow if everybody would like. Okay, let's just double check this.
>> Lucas Ruebbelke: Still need to bind to originalName in my template.
>> Lucas Ruebbelke: originalName.
>> Lucas Ruebbelke: Perfect, all right, the last thing that we need to do here is create our.
>> Lucas Ruebbelke: Outputs.
>> Speaker 2: Hello?
>> Lucas Ruebbelke: Yes?
>> Speaker 2: In this country for selector widget? I mean input setWidget and getWidget so that way, when widget is patched by the view, it will use a getter and not a setter. I mean, you already have a setter property for the widget, right? And I can have a getter property which will be used by the view when it is time to use the widget.
>> Lucas Ruebbelke: Yes, but if you use a getter, where would you put this logic at?
>> Speaker 2: You keep the set as it is, and in the get, you say that there is no selected widget, returned is our selected widget. So, what you do is that you use the widget name in the view actually.
>> Lucas Ruebbelke: So you're saying, like, get widget.
>> Speaker 2: Hit on widget.
>> Lucas Ruebbelke: Like this?
>> Speaker 2: Yeah, there go. Because [INAUDIBLE] simulator, input to be defined for that right, get.
>> Lucas Ruebbelke: Maybe, let's try it, let's see what happens.
>> Lucas Ruebbelke: So it hasn't exploded.
>> Lucas Ruebbelke: So I think you could, I know you can use getters and then bind to a getter, definitely possible.
[00:18:32] I would need to think about like the utility of that, and how I feel about it.
>> Lucas Ruebbelke: I don't know, maybe six of one, half a dozen of another, let me let that sit in that brain for a minute. So, it is an interesting technique, and we'll get to reactive forms and we'll kind of see an example of this again, so thanks for bringing that up.
>> Lucas Ruebbelke: And there actually may be a case to do this, but there won't have to be a gitter. For me, if you're gonna use a getter, like there needs to be some kind of like logic in here. Like, there maybe a reason like, when somebody request this, we actually do this additional thing first.
[00:19:11] And I think, that's where that might come in handy. All right let's wrap this up.
>> Lucas Ruebbelke: Output
>> Lucas Ruebbelke: We'll go saved = new EventEmitter.
>> Lucas Ruebbelke: Just gonna duplicate this, and we'll go, cancel.
>> Lucas Ruebbelke: Okay, so now let's hop back into our widget details component.
>> Lucas Ruebbelke: And on the form semit, it's going to be save.emit.
>> Lucas Ruebbelke: And on cancelled.
>> Lucas Ruebbelke: We'll just go like this, and I'm just gonna go ahead and pass in the selected widget. There may be a time where you may actually capture it, just in case. So I've seen where if you go back out And you need to go back forward so on a multi-step form.
[00:20:28] You may actually wanna just save that for reference if they ever come back, you might able to basically, restore that state, cancelled.emit.
>> Lucas Ruebbelke: And yes Sebastian, I will go over what set is doing in just a minute. And why we're doing that. Okay, so now, in our widget component template, we have two outputs.
[00:20:59] Saved, and we'll go save widget. Capture the event, pass it through, Cancelled.
>> Lucas Ruebbelke: Just pass this through as well, we're here, let's go into
>> Lucas Ruebbelke: Just in case we wanted to capture itself. So, let's see if this renders, hopefully it does, yes.
>> Lucas Ruebbelke: Saving,
>> Lucas Ruebbelke: Cancelling,
>> Lucas Ruebbelke: I don't think it's deleting, let's see, if I messed that up.
[00:21:48] Maybe I'm not tracing it out, delete widget, let me see here.
>> Lucas Ruebbelke: I didn't hook that up. Deleted, delete widget, pass in the event. And I think, this is everything.
>> Lucas Ruebbelke: There we go, so you can see it's tracing down here in the console.
>> Lucas Ruebbelke: So let me just talk about what the setter function is doing, and why we're using it.
[00:22:21] So in the form, what we want to avoid is having our state mutation kind of seep out of the component, we want to contain it. So, by using the set method, every time we set the widget, this method gets called and we're creating a copy of the widget using object.assign and assigning it to a local number.
[00:22:48] So now we're saying, create a copy of this, and assign it to a selected widget. And this is what we are mutating in the form. So it's just simply a way to capture what's coming in, create a copy, and then, binding that to the form. So hopefully, that makes sense.
[00:23:05] It's just the way to do it, in a somewhat immutable fashion by isolating the mutation until we're ready to propagate a back up. Any questions about that? So it's interesting is, it was actually pretty easy to create the components. The majority of what you'll do, is just kinda spend generating your inputs and outputs.
[00:23:27] And then kinda re-wiring it back up. But, the main thing with component driven architecture, is that you're creating these small components that do a specific thing. And then you're using inputs and outputs, or these contracts to communicate between them. And so, what you'll have is this kinda this hierarchy tree structure of components, from your top level component.
[00:23:50] That goes down very broad, and gets more and more specific, until you get to a very specific piece of functionality within your application. The goal is to have components like this, that are, for the most part, completely stateless. We're actually not storing internal state, other than what is defined by the bindings.
[00:24:11] And so this is a very stable component, unless angular breaks or we pass something we don't expect, this thing is going to work the same way all the time, which I quite like.