Skip to content

Commit ba556b3

Browse files
committed
Update instructions with @input @output exercise
1 parent eb21726 commit ba556b3

File tree

3 files changed

+126
-100
lines changed

3 files changed

+126
-100
lines changed

Warmup.pptx

-851 KB
Binary file not shown.

docs/chapters/chapter5.md

Lines changed: 124 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -351,8 +351,6 @@ export class MyComponent implements OnInit {
351351
map(res => res.data)
352352
);
353353
}
354-
355-
}
356354
}
357355
```
358356

@@ -655,170 +653,198 @@ export class CocktailItemComponent {
655653
656654
Now if you reload the app and select an ingredient, the app should work as before, but this time you have nice separation of how the Drinks ListView should present each item.
657655
658-
### To be continued here!!!!!!!
659-
660656
### Components with custom events
661657
662-
Adding a custom event to a component is easy. Let's have a look at `LeagueTableComponent` as an example.
663-
664-
To make it work we need first to create an `EventEmitter`:
658+
Adding a custom event - like a `(tap)` event - to a component is easy.<br/>
659+
This is done by adding an `EventEmitter` property with an `@Output` decorator.
660+
Like this:
665661
666662
``` javascript
667-
@Output() teamSelected: EventEmitter<number> = new EventEmitter<number>();
663+
@Component({
664+
selector: 'team-selector'
665+
...
666+
})
667+
export class TeamSelectorComponent {
668+
@Output() teamSelected: EventEmitter<number> = new EventEmitter<number>();
669+
}
668670
```
669671
670-
Note that this is made of 3 parts:
672+
This automatically means that our component, will have an event called `(teamSelected)`, which should be emitting *numbers*. And can be used like this (note that $event will pass the numbers emitted by the event).
673+
674+
``` XML
675+
<team-selector (teamSelected)="doSomething($event)"></team-selector>
676+
```
671677
672-
* @Output - decorator
673-
* teamSelected - eventName
674-
* EventEmitter<number> - EventEmitter with the type of output
675678
676-
Then every time we want to trigger the event, we can call `emit(value)` on `this.teamSelected`. Just like this:
679+
#### Emitting values
680+
681+
To make your component emit values, you just call `.emit(value)` on your event emitter. Like this (see `onTeamChanged()`):
677682
678683
``` javascript
679-
onTeamSelect(event) {
680-
const selectedTeamId = this.table.standing[event.index].teamId;
681-
console.log('::LeagueTableComponent::onTeamSelect::' + selectedTeamId);
682-
this.teamSelected.emit(selectedTeamId);
684+
@Component({
685+
selector: 'team-selector'
686+
...
687+
})
688+
export class TeamSelectorComponent {
689+
@Output() teamSelected: EventEmitter<number> = new EventEmitter<number>();
690+
691+
onTeamChanged(teamId) {
692+
this.teamSelected.emit(teamId);
693+
}
683694
}
684695
```
685696
686-
Obviously there must be something that actually triggers `onTeamSelect`. In this case this is done by the `<ListView>`
687-
688-
``` XML
689-
<ListView [items]="table?.standing" class="list-group" (itemTap)="onTeamSelect($event)">
690-
```
697+
This way, every time the `TeamSelectorComponent` calls `onTeamChanged`, this will result in the emitter triggering the `(teamSelected)` event.
691698
692-
All this means that everywhere we use `<my-league-table>` we can now add a handler for `teamSelected` like this (see `tables.component.html`):
699+
In the case of the below example, this will result in calling `doSomething()` with the `$event` value equal to `teamId`.
693700
694701
``` XML
695-
<my-league-table [competitionId]="PremierLeagueId" (teamSelected)="onTeamTap($event)"></my-league-table>
702+
<team-selector (teamSelected)="doSomething($event)"></team-selector>
696703
```
697704
698-
Note that `$event` will contain the value passed into `emit`, in this case this will be a `teamId`.
705+
### Components with custom input (two-way binding)
706+
<!--https://blog.thoughtram.io/angular/2016/10/13/two-way-data-binding-in-angular-2.html#creating-custom-two-way-data-bindings-->
699707
700-
### Exercise: Creating a presentation component with @Output
708+
To create a custom attribute that is capable of both taking data as an input and also updating it, we need to use two-way binding.
701709
702-
In this exercise we need to update the app, so that if the user taps on a team in the league table, the app should navigate to `TeamComponent` with `teamId` of that team.
710+
To do that we need to combine the power of the `@Input` and `@Output` decorators.
703711
704-
Even though you could make it happen by adding `[nsRouterLink]` on each team standing, we want the navigation logic to be delegated to the parent component, so it should be the `TablesComponent` that should trigger the navigation.
712+
Let's imagine we are working on a `ColorPicker` component, which should take a `color`, as an input, but when the user selects a different color, it should provide an updated value.
705713
706-
> So in short: when the user taps on a team, we need the `LeagueTableComponent` to emit `teamSelected` with the `teamId`. And the `TablesComponent` should intercept the `teamSelected` event and call `onTeamSelected` where it should navigate to the `TeamComponent`.
714+
First we need to create a property `@Input color`.
707715
708-
<h4 class="exercise-start">
709-
<b>Exercise</b>: Update LeagueTableComponent with @Output
710-
</h4>
716+
Then we need to add a custom event, which is called `propertyNameChange`. For our example we’ll use `@Output() colorChange`.
711717
712-
#### Step 1 - Add @Output EventEmitter
718+
Finally, we need to emit the new value `colorChange.emit(newColor);`
713719
714-
Add an `EventEmitter<number>` called `teamSelected` to your `LeagueTableComponent` in `league-table.component.ts`.
720+
Here is the full code:
715721
716-
<div class="solution-start"></div>
717722
``` javascript
718-
@Output() teamSelected: EventEmitter<number> = new EventEmitter<number>();
723+
@Component({
724+
selector: 'color-picker',
725+
templateUrl: './color-picker/color-picker.component.html'
726+
})
727+
export class ColorPickerComponent {
728+
@Input color: string;
729+
@Output() colorChange = new EventEmitter<string>();
730+
731+
onColorPick(newColor: string) {
732+
colorChange.emit(newColor);
733+
}
734+
}
719735
```
720-
<div class="solution-end"></div>
721736
722-
#### Step 2 - Emit value
737+
Please note that `@Input` could be also used with a `getter` and `setter`.
723738
724-
Update the `onTeamSelected` function, so that it `emits` the `teamSelected` event with the `teamId`
739+
Now you can use the `ColorPickerComponent` like this:
725740
726-
<div class="solution-start"></div>
741+
``` XML
742+
<color-picker [(color)]="selectedColorFromParentClass"></color-picker>
743+
```
727744
728-
``` javascript
729-
onTeamSelected(event) {
730-
const selectedTeamId = this.table.standing[event.index].teamId;
731-
console.log('::LeagueTableComponent::onTeamSelect::' + selectedTeamId);
732745
733-
this.teamSelected.emit(selectedTeamId);
734-
}
746+
### Exercise: Creating a presentation component with @Input @Output
747+
748+
In this exercise we need to update the `CocktailsComponent` to replace the first StackLayout that is used for finding an ingredient, and instead use the `SearchComponent`, like this:
749+
750+
``` XML
751+
<search-ingredient row="0"
752+
[(ingredient)]="selectedIngredient"
753+
(ingredientChange)="updateCocktailList($event)">
754+
</search-ingredient>
735755
```
736756
737-
<div class="solution-end"></div>
757+
The `SearchComponent` already contains most of the logic required to load a list of ingredients, and filter them as the user types in the TextField.
738758
739-
#### Step 3 - Call OnTeamSelect from the UI
759+
Your task is to update the `SearchComponent`, so that it allows:
740760
741-
Now we need the `ListView` in `league-table.component.html` to call `onTeamSelected` whenever the user taps on one of the teams.
742-
`ListView` has an event `itemTap` which does precisely that.
761+
* two way binding to `[(ingredient)]`,
762+
* emitting selected ingredient via `(ingredientChange)`
743763
744-
Add `(itemTap)="onTeamSelected($event)"` to the `ListView`.
764+
> Hint: Both of these task can be accomplished by adding `@Input()` and `@Output()` to `search.component.ts`. Make sure to also `emit()` ingredients via the emitter.
745765
746-
<div class="solution-start"></div>
766+
<h4 class="exercise-start">
767+
<b>Exercise</b>: Update LeagueTableComponent with @Output
768+
</h4>
747769
748-
ListView first line
770+
#### Step 0 - Switch to use SearchComponent
771+
772+
You can start by commenting out the code in `cocktails.component.html` that is between:
749773
750774
``` XML
751-
<ListView [items]="table?.standing" class="list-group" (itemTap)="onTeamSelected($event)">
775+
<!-- Ingredient Search <start> -->
776+
code
777+
<!-- Ingredient Search <end> -->
752778
```
753779
754-
<div class="solution-end"></div>
755-
756-
#### Step 4
757-
Now the `LeagueTableComponent` is ready to emit a `teamId` each time user taps on it. We just need to take it and navigate to the `TeamComponent`.
780+
Uncomment the `<search-ingredient>` code, to use the `SearchComponent` instead.
758781
759-
The `onTeamTap` function in `tables.component.ts` already has a logic to navigate to the `TeamComponent` with a specified `teamId`.
782+
Then also comment out the code in `cocktails.component.ts` that is between:
760783
761-
``` javascript
762-
private onTeamTap(teamId: number) {
763-
console.log('::TablesComponent::onTeamTap::' + teamId);
764-
this.router.navigate(['/football/team', teamId]);
765-
}
784+
```javascript
785+
/* Ingredient Search <start> */
786+
code
787+
/* Ingredient Search <end> */
766788
```
767789
768-
We just need to update each of the `<my-league-table>` tag to bind to the `(teamSelected)` event and call `onTeamTap`
790+
#### Step 1 - Add @Input() to ingredient
769791
770-
> **HINT**: Don't forget to pass `$event` to `teamSelected`.
792+
Add `@Input()` decorator to the `ingredient` property to the `SearchComponent` in `search.component.ts`.
771793
772794
<div class="solution-start"></div>
773-
``` XML
774-
<my-league-table [competitionId]="PremierLeagueId" (teamSelected)="onTeamTap($event)"></my-league-table>
795+
``` javascript
796+
@Input() ingredient: string;
775797
```
776798
<div class="solution-end"></div>
777799
778-
#### Step 5
800+
#### Step 2 - Add @Output EventEmitter
779801
780-
Test the app to see if this works.
802+
Add an `EventEmitter<number>` called `ingredientChange`.
781803
782-
Now upon tapping on a team in the table you should be redirected to a team view, which should display fixtures for that given team.
804+
This will work as `(ingredientChange)` event, but also it will enable two-way binding on the `ingredient` property.
783805
784-
<div class="exercise-end"></div>
806+
<div class="solution-start"></div>
807+
``` javascript
808+
@Output() ingredientChange = new EventEmitter<string>();
809+
```
810+
<div class="solution-end"></div>
785811
786-
### Components with custom input (two-way binding)
787-
<!--https://blog.thoughtram.io/angular/2016/10/13/two-way-data-binding-in-angular-2.html#creating-custom-two-way-data-bindings-->
812+
#### Step 3 - Emit value
788813
789-
To create a custom attribute that is capable of both taking data as an input and also updating it, we need to use two-way binding.
814+
Update the `selectIngredient` function, so that it `emits` the `ingredientChange` event with the `val`.
790815
791-
To do that we need to combine the power of the `@Input` and `@Output` decorators.
816+
<div class="solution-start"></div>
792817
793-
Let's imagine we are working on a `ColorPicker` component, which should take a `color`, as an input, but when the user selects a different color, it should provide an updated value.
818+
``` javascript
819+
public selectIngredient(val: string) {
820+
this.ingredientChange.emit(val);
821+
}
822+
```
794823
795-
First we need to create a property `@Input color`.
824+
<div class="solution-end"></div>
796825
797-
Then we need to add a custom event, which is called `propertyNameChange`. For our example we’ll use `@Output() colorChange`.
826+
#### Step 3 - Call selectIngredient from the UI
798827
799-
Finally, we need to emit the new value `colorChange.emit(newColor);`
828+
Now we need the selected `Label` in the ListView in `search.component.html` to call `selectIngredient` whenever the user taps on one of the ingredients.
800829
801-
Here is the full code:
830+
Add `(tap)="selectIngredient(item)"` to the `Label` inside the ListView.
802831
803-
``` javascript
804-
@Component({
805-
selector: 'color-picker',
806-
templateUrl: './color-picker/color-picker.component.html'
807-
})
808-
export class ColorPickerComponent {
809-
@Input color: string;
810-
@Output() colorChange = new EventEmitter<string>();
832+
<div class="solution-start"></div>
811833
812-
onColorPick(newColor: string) {
813-
colorChange.emit(newColor);
814-
}
815-
}
834+
``` XML
835+
<Label
836+
[text]="item"
837+
class="list-group-item" [class.selected]="ingredient === item"
838+
(tap)="selectIngredient(item)">
839+
</Label>
816840
```
817841
818-
Please note that `@Input` could be also used with a `getter` and `setter`.
842+
<div class="solution-end"></div>
819843
820-
Now you can use the `ColorPickerComponent` like this:
844+
#### Step 4
821845
822-
``` XML
823-
<color-picker [(color)]="selectedColorFromParentClass"></color-picker>
824-
```
846+
Test the app to see if this works.
847+
848+
Now upon tapping on a team in the table you should be redirected to a team view, which should display fixtures for that given team.
849+
850+
<div class="exercise-end"></div>

warmup/src/app/cocktail/search/search.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
<ListView [items]="ingredients$ | async" class="list-group" height="100%">
66
<ng-template let-item="item">
77
<StackLayout>
8-
<Label [text]="item" class="list-group-item" [class.selected]="ingredient === item"
9-
(tap)="selectIngredient(item)"></Label>
8+
<Label [text]="item" class="list-group-item" [class.selected]="ingredient === item">
9+
</Label>
1010
</StackLayout>
1111
</ng-template>
1212
</ListView>

0 commit comments

Comments
 (0)