This project was generated with Angular CLI version 6.0.8.
Run ng serve
for a dev server. Navigate to http://localhost:4200/
. The app will automatically reload if you change any of the source files.
Run ng generate component component-name
to generate a new component. You can also use ng generate directive|pipe|service|class|guard|interface|enum|module
.
Run ng build
to build the project. The build artifacts will be stored in the dist/
directory. Use the --prod
flag for a production build.
Run ng test
to execute the unit tests via Karma.
Run ng e2e
to execute the end-to-end tests via Protractor.
To get more help on the Angular CLI use ng help
or go check out the Angular CLI README.
-
Html file: double curly braces of interpolation binding of component property values
-
Decorator function: specifies Angular’s metadata for the component
-
selector: the component’s CSS selector, the name of the HTML element that identifies this component within a parent component’s template
-
templateUrl
-
styleUrl
-
Lifecycle hook
-
Export a class and import it elsewhere
-
(Use Angular CLI to) Create a Hero class under “src/app”, and import it in HeroesComponent. Declare a Hero instance in HeroesComponent, display the instance’s properties in HeroesComponent’s HTML template. This doesn’t affect how HeroesComponent is used by its parent.
-
<div>
,<span>
: no special meaning, just represents its children -
Pipe operator |: Angular ships with built-in pipes, and you can create your own. A good way to form strings, currency, dates and other display data.
<div>Name: {{hero.name | uppercase}}</div> <div>Name: <span>{{hero.name | uppercase}}</span></div>
-
Two-way data binding from ngModel in HTML: use
ngModel
that binds the hero.name component property to the data in the input box to achive the two-way data flowing.<label>name: <input [(ngModel)]="hero.name" placeholder="name"> </label>
And
ngModel
belongs to the optionalFormsModule
and it isn't available by default. You have to import it inAppModule
(app.module.ts
).import { FormsModule } from '@angular/forms'; // NgModel lives here // other critical metadata besides those in @Component decorators // are in @NgModule decorators @NgModule({ // external modules that the app needs imports: [ BrowserModule, FormsModule // ngModel belongs to FormsModule, is not default ], })
-
@NgModule
decorator: app metadata live in this decorator. All components should be declared in oneNgModule
. When Angular CLI createdHeroesComponent
, it declared it in the@NgModule
inAppModule
(and alsoAppComponent
). -
Show a list of items:
<ul>
unordered list,<ol>
ordered list,<li>
list item as a child of the outer<ul>
or<ol>
. Use*ngFor
to loop over an array:<ul class="heroes"> // heroes is the component property named "property" <li *ngFor="let hero of heroes"> // hero represents one item in the array heroes <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul>
-
Show an item conditionally:
*ngIf
<div *ngIf="selectedHero"> // the component property name "selectedHero" <h2>{{selectedHero.name | uppercase}} Details</h2> <div><span>id: </span> {{selectedHero.id}}</div> <div> <label>name: <input [(ngModel)]="selectedHero.name" placeholder="name"> </label> </div> </div>
-
Create a separate
hero-detail
component. This component has ahero
property to represent the hero to show details. This is an@Input
from its parent component (wherehero-detail
is used).import { Component, OnInit, Input } from '@angular/core'; @Input() hero: Hero; // @Input decorator
In the template of the parent component, bind the
HeroesComponent.selectedHero
tohero-detail
's hero property:<app-hero-detail [hero]="selectedHero"></app-hero-detail>
Angular's property binding: One-way binding from the
selectedHero
property ofHeroesComponent
to thehero
property ofHeroDetailComponent
. -
service: a way to share information among classes that don't know each other.
import { Injectable } from '@angular/core'; // provides metadata for the service, like @Component() // by default a service is registered with the root injector @Injectable({ providedIn: 'root', }) export class HeroService { constructor() { } }
The class is
@Injectable()
service, which participates in the app's dependency injection system.Inject
HeroService
intoHeroesComponent
:constructor(private heroService: HeroService) { }
The parameter simultaneously defines a private
heroService
property and identifies it as aHeroService
injection site. When Angular creates aHeroesComponent
, the Dependency Injection system sets theheroService
parameter to a singleton instance ofHeroService
. This is a TypeScript parameter property. -
The best location to make a remote call to a server is in
ngOnInit()
, instead ofconstructor()
which is suited for simple initialization. -
Asynchronous call and
Observable
:In
HeroService
temporarily useRxJS
'sof()
method to return anObservable<>
object:import { Observable, of } from 'rxjs'; getHeroes(): Observable<Hero[]> { return of(HEROES); }
In
HeroesComponents
use the async way to get the data from the service:getHeroes(): void { this.heroService.getHeroes() .subscribe(heroes => this.heroes = heroes); }
-
"Service-in-service": inject
MessageService
intoHeroService
,and then injected intoHeroesComponent
-
If you want to bind a component property to html template, it has to be public.
In
MessageComponent
:constructor(public messageService: MessageService) {}
-
How to write the template of
MessageComponent
. -
@NgModule: ng generate module. Router in this example is also a module (
AppRoutingModule
). -
Set up
Routes
inAppRoutingModule
.Routes
tells the router which view to display when the user clicks a link or paste a URL. A typical AngularRoute
has 2 properties:
-
path
: a single string as a postfix to the root address -
component
: the component that the router should create when navigating to this routeconst routes: Route[] = [ { path: '', component: } ];
Default route
''
that redirects to some other route:{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
Parameterized route,
:id
is a placeholder for a special id, andid
is a parameter inrouter
, and can be used at other places:{ path: 'detail/:id', component: HeroDetailComponent }
Initialize the router and start it listening for browser location changes by:
@NgModule({ // supplies the service providers and directives needed for routing, // and performs the initial navigation based on the current browser URL imports: [ RouterModule.forRoot(routes) ], })
In
AppComponent
, replace<app-heroes>
with<router-outlet>
. Remove<app-heroes>
because heroes will only be displayed when navigated there.<router-outlet>
tells the router where to display the routed views. TheRouterOutlet
is one of the router directives that became available to theAppComponent
becauseAppModule
importsAppRoutingModule
which exportedRouterModule
.
-
<nav>
: represents a part of a page that links to other pages or other part of the page.<a>
: a hyperlink (hyper anchor) represented by its label. In this case we have therouterLink
attribute. (?) TherouterLink
is the selector for theRouterLink
directive that turns user clicks into router navigations. It's another of the public directives in theRouterModule
.<nav> <a routerLink="/dashboard">Dashboard</a> <a routerLink="/heroes">Heroes</a> </nav>
-
(?) html presents grid
<div class="grid grid-pad"> <a *ngFor="let hero of heroes" class="col-1-4" routerLink="/detail/{{hero.id}}"> <div class="module hero"> <h4>{{hero.name}}</h4> </div> </a> </div>
Use Angular interpolating binding for
routerLink
. -
In
HeroDetailComponent
:
-
get the id of the selected hero from the router
-
get the information from
HeroService
of the selected hero and display it -
get the location of the previous page and be able to naviagate back
import { ActivatedRoute } from '@angular/router'; import { Location } from '@angular/common'; import { HeroService } from '../hero.service'; // ActivatedRoute: information about the route to this instance of the HeroDetailComponent. // This component is interested in the route's bag of parameters extracted from the URL. // The "id" parameter is the id of the hero to display. // Location: an Angular service for interacting with the browser. // Use it to navigate back to the view that navigated here. constructor( private route: ActivatedRoute, private location: Location, private heroService: HeroService ) { } ngOnInit() { this.getHero(); } getHero(): void { // + operator converts a string to a number // parameters are all strings const id = +this.route.snapshot.paramMap.get('id'); // create a getHero(id) method in HeroService this.heroService.getHero(id).subscribe(hero => this.hero = hero); } // in HTML add a button with (click)="goBack()" goBack(): void { this.location.back(); }
-
HTTP service.... I'm too lazy to write this. See the Angular's tutorial page
-
Tell the meaning of them:
*ngFor
{{hero.name}}
(click)
[hero]
<app-hero-detail>
-
Data binding:
One-way binding:
-
{{hero.name}}
, interpolation: bind a component property name to the template. -
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
, property binding: pass the value ofselectedHero
from the parent component to thehero
property of the child component. -
(click)="selectHero(hero)"
, event binding, calls the component'sselectHero()
method when the button is clicked.Two-way binding:
-
<input [(ngModel)]="hero.name">
, a data property value flows to the input box from the component like property binding, and the changes to the input box flows back to the property value in the component as with event binding.
-
Pipe: a class with decorator
@Pipe
defines a function that transforms input values into output values for display in a view. To use@Pipe
class in templates, use the pipe operator (|). And pipes can be chained. Pipes can also take arguments to control how it performs its transformation. -
Service and Dependency Injection (DI).
A service class has an
@Injectable
decorator. The main mechanism of DI is injector. The injector for the app is created by Angular during bootstrap process. The injector maintains a container of dependency instances that it has created, and reuses them if possible. For any dependency you need in your app, you must register at least one provider with the app's injector, so that the injector can use it to create new instances.Different ways to register providers:
-
A service can register providers itself, in the metadata of the service (in the
@Injectable
decorator)@Injectable({ providedIn: 'root', // when you run ng generate service xxx, the service is registered with the root injector })
-
Register providers in a specific
@NgModule
, and the same instance of a service is available to all components in that NgModule. Use theproviders
property of the@NgModule
decorator:@NgModule({ providers: [ BackendService, Logger ], ... })
-
Register providers in a specific
@Component
, and a new instance of the service is created with each new instance of that component. Use theproviders
property of the@Component
metadata:@Component({ selector: 'app-hero-list', templateUrl: './hero-list.component.html', providers: [ HeroService ] })
When Angular discovers that a component depends on a service, it first checks if the injector already has any existing instances of that service. If a requested service instance does not yet exist, the injector makes one using the registered provider, and adds it to the injector before returning the service to Angular.
When all requested services have been resolved and returned, Angular can call the component's constructor with those services as arguments.
per https://angular.io/guide/router
-
When defining routes in the RouterModule, you can pass a
data
property to a route.The
data
property is a place to store arbitrary data associated with this specific route. The data property is accessible within each activated route. Use it to store items such as page titles, breadcrumb text, and other read-only, static data. You'll use the resolve guard (see the part for route guards) to retrieve dynamic data later in the guide.Maybe I can change how our UI implements its breadcrumb? Dunno, need to look into this more... at least a new idea. It doesn't seem like a high priority though, the current way works fine.
const routes : Route = [ { path: 'heroes', component: HeroListComponent, data: { title: 'Heroes List' } }, ]
-
Now I've seen two different ways of defining routes:
-
Way 1: no component if the path still has children, only including component if this is a leaf path (our project). So each leaf path defines a complete page. Component-less route.
const routes: Routes = [ // Services { path: 'services', children: [ { path: '', component: ServiceList, }, ] },
const adminRoutes: Routes = [ { path: 'admin', component: AdminComponent, children: [ { path: '', children: [ { path: 'crises', component: ManageCrisesComponent }, { path: 'heroes', component: ManageHeroesComponent }, { path: '', component: AdminDashboardComponent } ] } ] } ];
A component-less route makes it easier to guard child routes using
CanActivateChild
(see the part of route guards). -
Way 2: including component in non-leaf paths as well. All components on a route path construct a page from top to down.
const crisisCenterRoutes: Routes = [ { path: '', // The first part of the page showing without any condition component: CrisisCenterComponent, children: [ { path: '', // The second part of the page showing without any condition component: CrisisListComponent, children: [ { // The third part of the page showing if a valid id provided path: ':id', component: CrisisDetailComponent, canDeactivate: [CanDeactivateGuard], resolve: { crisis: CrisisDetailResolverService } }, { // The third part of the page showing if no id provided path: '', component: CrisisCenterHomeComponent } ] } ] } ];
-
The
RouterOutlet
is a directive from the router library that is used like a component. It acts as a placeholder that marks the spot in the template where the router should display the components for that outlet. Its template selector is<router-outlet></router-outlet>
. When the router detects the URL goes to some route defined in its RouterModule, it places its corresponding component as a sibling component to theRouterOutlet component
(instead of replacing it). -
After the end of each successful navigation lifecycle, the router builds a tree of
ActivatedRoute
objects that make up the current state of the router. The tree of ActivatedRoute is calledRouterState
(an interface that extendsTree
and contains anotherRouterStateSnapshot
field calledsnapshot
). You can access the current RouterState from anywhere in the application using theRouter
service and itsrouterState
property (router.routerState.snapshot.root
->root: ActivatedRouteSnapshot
). EachActivatedRouter
provides methods to traverse up and down the route state tree to get information from parent, child, and sibling routes, and properties likesnapshot: ActivatedRouterSnapshot
,paramMap
, andqueryParamMap
.This means to me, only after a navigation event succeeds (finishes, and finishes without errors), the new state will be recorded into the router.
-
When setting
enableTracking
totrue
in the RouterModule, router events are logged into the console.Router.events
provides events as observables. To filter on certain navigation events, use RxJS'sfilter()
operator:@Component({ selector: 'app-routable', templateUrl: './routable.component.html', styleUrls: ['./routable.component.css'] }) export class Routable1Component implements OnInit { navStart: Observable<NavigationStart>; constructor(private router: Router) { // Create a new Observable that publishes only the NavigationStart event this.navStart = router.events.pipe( filter(evt => evt instanceof NavigationStart) ) as Observable<NavigationStart>; } ngOnInit() { this.navStart.subscribe(evt => console.log('Navigation Started!')); } }
-
NavigationEnd
event happens when navigation ends successfully.NavigationCancel
event happens when the navigation is canceled, and it is because a router guard returns false during navigation.NavigationError
event happens when the navigation fails due to unexpected errors. -
Route guard
CanActive
, used for authentication.
-
Define an authentication guard that implements
CanActivate
.CanActivate
is an interface that decides if a route can be activated. If all guards return true, navigation will continue. If any guard returns false, navigation will be cancelled. If any guard returns a UrlTree, current navigation will be cancelled and a new navigation will be kicked off to the UrlTree returned from the guard.The interface has a method
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot)
that returnsObservable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree
. TheActivatedRouteSnapshot
contains the future route (ActivatedRoute) that will be activated and theRouterStateSnapshot
contains the future RouterState of the application, should you pass through the guard check. (So these two are the snapshot values ofActivatedRoute
andRouterState
. Remember thatActivatedRoute
represents a node in the URL tree, andRouterState
is the tree ofActivatedRoute
s)// auth_guard.ts import { Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; @Injectable({ providedIn: 'root', }) export class AuthGuard implements CanActivate { constructor(private readonly router: Router) {} canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { // If pass the authentication, return true, and don't need to do anything // else. The router is able to navigate to the next route. // If not pass the authentication, store the target future URL somewhere // else (this.state.url in another service that the login component can // use), navigate to another page (this.router.navigate() to the login // component), and return false. And actually the second navigation // automatically cancels the current one, so it's just returning false // to be clear. } }
-
Use the guard in the routing module with a
canActivate
guard property.import { AuthGuard } from '../auth/auth.guard'; const adminRoutes: Routes = [ { path: 'admin', component: AdminComponent, canActivate: [AuthGuard], children: [ { path: '', children: [ { path: 'crises', component: ManageCrisesComponent }, { path: 'heroes', component: ManageHeroesComponent }, { path: '', component: AdminDashboardComponent } ], } ] } ];
- Route guard
CanActivateChild
. TheCanActivateChild
guard is similar to theCanActivate
guard. The key difference is that it runs before any child route is activated.
-
Make the authentication guard also implements
CanActivateChild
.The
canActivateChild()
method takes same parameters ascanActivate()
. And in this case, their logic will be same: both checking if the current user is logged in. So they can actually reuse a same helper method.// auth_guard.ts import { Injectable } from '@angular/core'; import { CanActivate, CanActivateChild, ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; @Injectable({ providedIn: 'root', }) export class AuthGuard implements CanActivate, CanActivateChild { constructor(private readonly router: Router) {} canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { return this.activate(next, state); } canActivateChild( next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { return this.activate(next, state); } activate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { // If pass the authentication, return true, and don't need to do anything // else. The router is able to navigate to the next route. // If not pass the authentication, store the target future URL somewhere // else (this.state.url in another service that the login component can // use), navigate to another page (this.router.navigate() to the login // component), and return false. And actually the second navigation // automatically cancels the current one, so it's just returning false // to be clear. } }
-
Use the auth guard in the routing module with a
canActivateChild
property.const adminRoutes: Routes = [ { path: 'admin', component: AdminComponent, canActivate: [AuthGuard], children: [ { path: '', canActivateChild: [AuthGuard], children: [ { path: 'crises', component: ManageCrisesComponent }, { path: 'heroes', component: ManageHeroesComponent }, { path: '', component: AdminDashboardComponent } ] } ] } ];
With component-full route, you'll need to do the following, and you don't need to make
AuthGuard
implementsCanActivateChild
:const adminRoutes: Routes = [ { path: 'admin', component: AdminComponent, canActivate: [AuthGuard], children: [ { { path: 'crises', component: ManageCrisesComponent, canActivate: [AuthGuard], }, { path: 'heroes', component: ManageHeroesComponent, canActivate: [AuthGuard], }, { path: '', component: AdminDashboardComponent, canActivate: [AuthGuard], } } ] } ];
-
Route guard
CanDeactivate
. Didn't get what it is doing...... -
Resolver: pre-fetching data before activating the route. (It seems more complicated than we can afford. Our current Obsevables returned from RPC calls work in a more intuitive way. So not going to write down everything, just put the link here.)
-
Route guard
CanLoad
, used for lazy loading and authorization.With the use of
CanActivate
guard, users who are not logged in are not navigated to the admin components, but the router is still loading the AdminModule even if the user can't visit any of its components. Ideally, you'd only load the AdminModule if the user is logged in.
-
Make
AuthGuard
implementCanLoad
as well.// auth_guard.ts canLoad(route: Route): boolean { let url = `/${route.path}`; return this.checkLogin(url); }
-
Use the guard in AppRoutingModule.
const appRoutes: Routes = [ { path: 'compose', component: ComposeMessageComponent, outlet: 'popup' }, { path: 'admin', loadChildren: './admin/admin.module#AdminModule', canLoad: [AuthGuard] }, { path: 'crisis-center', loadChildren: './crisis-center/crisis-center.module#CrisisCenterModule', data: { preload: true } }, { path: '', redirectTo: '/superheroes', pathMatch: 'full' }, { path: '**', component: PageNotFoundComponent } ];