- Lunch time
- Breaks
- Power strips
- Pair with your neighbor!!
- Open Style Guide
- If you are using VS Code then make sure the following settings are put in place
"editor.formatOnSave": true,
"editor.insertSpaces": true,
Optionally
"files.autoSave": "onFocusChange"
Also, if you have not, install the following extensions
-
Show of hands - How many have used Anguar1?
-
Enterprisey?
-
Overwhelming?
-
As you get in the flow of things, things get easier. But there is a large overhead
-
Big changes
- Typescript
- Components everywhere
- Observables (a.k.a "reactive")
- Platform!!
- Need to hand hold it along the way
-
What stayed the same?
- DI
- Services
- Opinionated
-
Bootstrapping your app
- Angular CLI
-
Preferred editor
- Visual Studio Code
- Sublime Text
-
Finally,
- What do we have?
- what are we going to do?
- What is a component?
- How does Angular use components?
- Hierarchy of components
- Send data down, emit events up
- No scopes, no two-way binding, no more MVC
- Update
app.component.html
<div class="container">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark navbar-toggleable-sm justify-content-center">
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target=".navbar-collapse">
<span class="navbar-toggler-icon"></span>
</button>
<a href="/" class="navbar-brand d-flex w-50 mr-auto">Friends HQ</a>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav ml-auto w-100 justify-content-end">
<li class="nav-item">
<a class="nav-link" href="#">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">People</a>
</li>
</ul>
</div>
</nav>
</div>
<div class="container">
<!-- Insert your code here -->
</div>
<footer class="footer text-center">
<nav class="navbar fixed-bottom navbar-light bg-faded">
<a class="navbar-brand" href="#">Created by Looselytyped</a>
</nav>
</footer>
- See how
app.component.ts
is being used inapp.module.ts
- Introduce variables in
app.component.ts
and use them in the template
- Component hierarchy
- Just as
index.html
usesapp-root
we can create another component, sayapp-child-component
and use it inapp.component.html
- What does it take to create a new component?
- Create the necessary files in the right location (Refer to Style Guide)
- Create an
index.ts
file and export the component declare
the component inapp.module.ts
- Use it somewhere in the DOM
- Just as
-
Create
PeopleComponent
- The
people
directory should be undersrc/app/
- The
selector
needs to beapp-people
- The
templateUrl
should bepeople.component.html
- The
-
Update
app/people/people.component.html
<div>
<h1>People Component</h1>
</div>
- Be sure to update
app.module.ts
!! - Update
app.component.html
and use the selector where it says<!-- Insert your code here -->
- Now we need to display a "list" of people
- While it seems that
PeopleComponent
is an "empty" component, it is usual for "feature root" components to be just like the "root" component - they act as the place where you hang all the features off of
-
Create a
PersonListComponent
underpeople/person-list
directory- Use the snippets functionality in VS code if you have it installed
- The snippet is
Ctrl-space
followed bya-component
then "tab" key - The
selector
should beapp-person-list
- Note that here we are using the
person-list.component.css
file, so be sure to introduce astyleUrls
array in your@Component
descriptors
-
Update
person-list.component.html
<div class="person-list">
<div class="row">
<div class="col-xs-12 col-md-9">
<div class="list-group">
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">
<!--Display first friend here-->
</h5>
</div>
</a>
</div>
</div>
<div class="col-xs-12 col-md-3 sidebar">
<a href="#" class="btn btn-success sidebar-cta disabled">
Add someone
</a>
</div>
</div>
</div>
- Update
person-list.component.css
.person-list .list-group, .sidebar {
margin-top: 20px;
}
.person-list .list-group {
border: 1px solid #eee;
border-radius: 3px;
}
.person-list .sidebar .btn {
padding: 15px;
width: 100%;
}
- Be sure to update
app.module.ts
! - Use the
PersonListComponent
inpeople.component.html
- Models, and mapping to entities
- Using models in your components
- Define a interface called
Friend
inshared/friend.model.ts
directory to map entities inserver/api/db.json
- Declare an array of friends on
PersonListComponent
and initialize it to an array- Copy the array found in
server/api/db.json
toperson-list.component.ts
and assign it to a local variable if it makes it easier
- Copy the array found in
- Display the first and last name of the first friend in that array in
person-list.component.html
- Do we need another component?
- Whats the reuse potential?
- Is there enough state to manage?
- How do we loop over a list of items?
- If we are looping over a child component, how do we supply it with what it needs?
-
Create a
ShowPersonComponent
underpeople/show-person
selector
must beapp-show-person
- Make sure you have the
stylesUrl
attribute set in@Component
-
Ensure it has an
@Input() friend: Friend
attribute -
Update
show-person.component.html
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">
{{ friend.firstName }} {{ friend.lastName }}
</h5>
<small>
<div class="heart-rating">
<span class="fa fa-heart" data-rating="1"></span>
</div>
</small>
</div>
</a>
- Update
show-person.component.css
.heart-rating {
line-height:32px;
}
.heart-rating .fa-heart, .heart-rating .fa-heart-o {
font-size: 2em;
}
.heart-rating .fa-heart {
color: red;
}
- Use
*ngFor
inperson-list.component.html
invokingapp-show-person
setting thefriend
attribute for each friend in thefriends
array
- How do we attach events to components?
- We can use the browser events like
click
and invoke a method on the component
- We can use the browser events like
- Given that the state of the component changes how do we conditionally apply a
class
to the component?- We can use the
[ngClass]
directive - Notice that this is a "setter" just like
[friend]=friend
is
- We can use the
- Modify
show-person.component.html
to attach aclick
handler toheart-rating
- Introduce a method called 'like' in
ShowPersonComponent
that that toggles thefav
flag on thefriend
attribute - Invoke it by attaching a
(click)
handler in the template so that you invokelike
on a click - To avoid event propogation you can simply do something like
like(); false;
in the template
- Introduce a method called 'like' in
- Once you do that, use
ngClass
to flit theclass
of thespan
betweenfa-heart
andfa-heart-o
- You can use tertiary statements in HTML like so
(friend.fav)?'fa-heart':'fa-heart-o'
- You can use tertiary statements in HTML like so
- How do we notify the parent of an event?
- We can use the
EventEmitter
and wrap anything in the event that is to be propagated
- We can use the
- The parent can then listen for the name of that "event" and attach a callback in the template
-
Introduce an
Output
event emitter inShowPersonComponent
that emits an event of typeFriend
-
When a friend is "liked" go ahead and emit the event wrapping the friend in it
-
In
PersonListComponent
's template "listen" for that event, and attach a handler to set an attribute nameddisplayBanner
to true- NOTE that you HAVE to declare the attribute first (initialize to
false
) and then set it to true on receiving an event - Make sure that you switch
displayBanner
to false eventually (You can usesetTimeout
for this
- NOTE that you HAVE to declare the attribute first (initialize to
-
Here is the HTML you will need to add to
person-list.component.html
<div class="col-xs-12 col-md-9">
<div *ngIf="displayBanner" class="alert alert-success box-msg" role="alert">
<strong>List Saved!</strong> Your changes has been saved.
</div>
</div>
- Here is what
displayBanner
should look like inPersonListComponent
showBanner(friend: Friend) {
this.displayBanner = true;
setTimeout(() => {
this.displayBanner = false;
}, 3000);
};
-
How do services work in Angular?
- If they too have dependencies (for e.g. a service might need
HttpClient
) then you need to mark the service as@Injectable
- They need to be "provided"
- Once provided they can be injected into other components
- If they too have dependencies (for e.g. a service might need
-
AJAX Calls
- Will need
HttpClient
injected HttpClient
has methods (likeget
andput
) returnObservable
-s
- Will need
- Open a new terminal and run
npm run server
(So now you have two terminals, one runningng serve
and now this)
-
Import
HttpClientModule
from@angular/common/http
in our module -
Create a
FriendsService
underapp/shared
(Use the command lineng g service shared/Friends --dry-run true
and see what it does- Make sure it is
@Injectable
!!
- Make sure it is
-
Inject
HttpClient
in it's constructor -
You will need a BUNCH of imports
import {
HttpClient,
HttpHeaders,
} from '@angular/common/http';
import {
Observable,
} from 'rxjs';
- Implement a
getFriends
method thatget
shttp://localhost:3000/friends
and returnsObservable<Array<Friend>>
getFriends(): Observable<Friend[]> {
return this.http.get<Friend[]>('http://localhost:3000/friends');
}
- Now that we have a service, how do we use it in our components?
- This is the same as how we used
HttpClient
in ourFriendService
!
- This is the same as how we used
- We also need to discuss the lifecycle of components, especially what
OnInit
offers us, and why it is useful
- Inject the
FriendService
inPersonListComponent
- Instead of hard-coding the array of friends, use
onInit
to populate the friends array like so
this.friendService.getFriends()
.subscribe(friends => this.friends = friends);
- Much like
HttpClient.get
we also haveHttpClient.put
but the signature is a tad more elaborate.- Specifically in our situation, our backend which is the
json-server
that expects us to supply the rightHttpHeaders
else it won't do anything put
also requires a payload
- Specifically in our situation, our backend which is the
- Implement
saveFriend
which takes aFriend
as an argument, does aput
on the backend with the suppliedFriend
as a payload, and with the rightHttpHeaders
. Here is what the headers look like
const headers: HttpHeaders = new HttpHeaders({
'Content-Type': 'application/json',
'Accept': 'application/json',
});
- Note that the backend sends you back the updated friend record!
- Go ahead and use that method in
ShowPersonComponent
'slike
method to save when you like/unlike a friend- First, inject the service in the constructor
- Then update the
like
method to usesaveFriend
- Smart vs. dumb components
- Smart components
- Usually leverage services
- Know how to get/load/update data
- Dumb components
- Fully defined via their API
- Everything is an
@Input
or an@Ouput
- Smart components
- Can we make
ShowPersonComponent
"dumb"? - Implement this and see what it looks like
- This means that
ShowPersonComponent
will no longer needFriendsService
injected into its constructor - Also, simplify the
like
method to not save the friend
- This means that
like() {
this.friend.fav = !this.friend.fav;
this.notifyParent.emit(this.friend);
}
But! Where do we save our friend? - In the PersonListComponent
of course!
- Instead, when the parent
PersonListComponent
receives thenotifyParent
(which in turn invokesshowBanner
) it will now need to "save" the fact that a friend was "liked". ModifyshowBanner
to first save the friend, and thenshowBanner
- How does routing work in Angular?
- I like to use a separate file called
app.routes.ts
- This declares a
Routes
object which is essentially an array ofRoute
objects - Each
Route
object introduces apath
and acomponent
to use for that path - We need to then install the routes as part of our
imports
inapp.module.ts
- Finally we need to use
router-outlet
in the DOM to signify which portions of the DOM get managed by the router
- I like to use a separate file called
- First you need to
import
RouterModule
into yourAppModule
- Create a new file called
app.routes.ts
next toapp.module.ts
file so that we- route
people
to use thePeopleComponent
- trap
**
toredirectTo
people
- Be sure to
import
Routes
coz you will need it
- route
- Import that file into
app.module.ts
and import the routes usingRouter.forRoot
- Update
app.component.html
to userouter-outlet
- Usage of
routerLink
androuterLinkActive
directives in the DOM
- Update the navbar links in
app.component.html
to use these two directives.- Remember that I have already supplied an
active
class to highlight which link is active
- Remember that I have already supplied an
- Use the
angular-cli
to generate aDashboardComponent
- In your terminal you will need to do
ng generate component <component-name>
- In your terminal you will need to do
- Update
dashboard.component.html
to look like this
<div class="container">
<div class="dashboard">
<div class="dashboard-box dashboard-stat">
<h2>Statistics about your account</h2>
<ul class="list-inline">
<li class="list-inline-item">
<span class="stat-number">1</span>
<span class="stat-description">Contacts</span>
</li>
<li class="list-inline-item">
<span class="stat-number">2</span>
<span class="stat-description">Kids</span>
</li>
</ul>
</div>
</div>
</div>
- Update
dashboard.component.css
to look like this
.dashboard {
background-color: #fff;
padding-top: 50px;
}
.dashboard .dashboard-box.dashboard-stat {
margin-bottom: 40px;
text-align: center;
}
.dashboard .dashboard-box {
background-color: #fff;
border: 1px solid #dfdfdf;
border-radius: 3px;
padding: 15px;
}
.dashboard .dashboard-box h2 {
border-bottom: 1px solid #eee;
font-size: 14px;
font-weight: 600;
padding-bottom: 5px;
}
.dashboard .dashboard-box.dashboard-stat li:not(:last-child) {
margin-right: 25px;
}
.dashboard .dashboard-box.dashboard-stat li {
display: inline-block;
}
.dashboard .dashboard-box.dashboard-stat .stat-number {
display: block;
font-size: 25px;
}
.dashboard .dashboard-box.dashboard-stat .stat-description {
font-size: 13px;
}
- Introduce a new route that uses the
DashboardComponent
when the route is/dashboard
- Update the
Dashboard
link inapp.component.html
so that it links correctly and displays the right class (active
) when clicked