AngularJS 2 - Superpowers FireBootCamp by SSW (in progress!)
Table of Contents
Chapter 1 - AngularJS 2 Superpowers FireBootCamp by SSW
Summary
- AJS 2, Angular CLI, Components, Data Binding, Services, HTTP, Routing, Forms, ngrx, etc
Links
- Emmet
- SSW Code
- Today code - firecamp-crm-demo
- Proven code - firecamp-crm
npm i
- Libraries
- Universal lib listen for SEO bot for serer-side rendering
- Wallaby
- Duncan Hunter Angular CLI Intro including vscode extensions
- SSW Rules
- RxJS (use v5, not stable v4) - simplifies reactive asynch coding
- applying the Observer Pattern to streams (setup Observers to watch properties of the object and update UI)
- dealing with streams of data, whereby Observable is an array, and can apply Operators to output multiple Observers
(i.e. apply chains of operators to get streams such as Obs #1
.count
, Obs #2.where(x => x > 5)
, Obs #3.where(x => x > 5).count()
- Example
- Cont’d
- avoids polling
- http requests return Observables
Questions
- code splitting - lazy loading - how differ to Webpack plugins, this is part of Angular CLI that uses Webpack?
- where is State stored?
- Promises vs Observables - consensus is that Observables better and use them (what about Cross-Cutting concerns??)
- Is cache busting library added to Webpack config built-into AJS 2
- Is Webpack code splitting how AJS 2 does Lazy Loading?
- What is
import By ... @angular/platform-browser
- Why use variables ending with $ (i.e.
this.companies$
)
Component Tree
- Module
- Component (Typescript Class)
- Inject services with DI
- Prop binding from Component to Template
- Event binding from Template (and Directives) back to Component
- Component (Typescript Class)
Lazy Loading
- Store features in src/
to point to for lazy loading
Bootstrapping
- main.js runs app.component.ts
- @NgModule bootstraps AppModule and registers imports Angular Modules as dependencies so globally available
- Interfaces files are just there for development purposes
- Component file imports are just for TypeScript
- Angular CLI has build chain using Webpack
Setup
- Install AJS2 CLI https://cli.angular.io/
Angular CLI
- Try to reduce size of bundled files
Example App Architecture
- App Module (top-level Typescript Class)
- Home Module
- Lazy load just module required
- Company Module
- CRUD
Scaffold and Build App
- Generate component shorthand without unit tests
ng g c company/company-list --spec false
(generates component-list.component.ts / _.css / _.html) - Import the component logic into app.component.ts. Declare it in @NgModule
declarations
to make it globally available in app - Angular CLI version
ng -v
- Update app.component.html with Custom Directive (i.e.
) ngOnInit()
lifecycle hook on instantiation to call function (i.e.this.companies
) that retrieves from hard-coded static list, service abstraction, observable, and then back-end db- Handlebars `` to access properties of class model in html using built-in pipe filter
- Create interface for class @Component property
companies: any[]
withng g interface company/company
to generate app/company/company.ts and change toCompany[]
- Implement Interface and import it into
company-list.component.ts
- Add styling to
company-list.component.html
using Bootstrap after installingnpm i bootstrap --save
, then update build setup angular-cli.json “styles” property with path to bootstrap.css in node_modules. DO NOT add to index.html, as the node_modules folder not exist when bundles and deploys to browser. Restart the app since changed build setup (i.e.ng serve ...
) - Scaffold HTML Markup (using Emmet/Zen coding). Execute and hydrates to generate code on page
tag | class | > tag > tag > qty
table.table.table-striped>thead>tr>th*4
- Create multiple handlebars `` at once using IDE feature
- Snippet library by John Papa using
ng2
to generate*ngFor="let company of companies"
(*
syntactic sugar that will be replaced when markup hydrates) - Wrap in Bootstrap container in html
<div class="container"...
(center aligned) - Add Component-specific CSS styling into company-list.component.css
.btn-column { ... }
(AJS 2 modifies the class name behind the scenes to be Component-specific.btn-column[_ngcontent...
) - Add an “Add” button to .html
- Retrieve data from CompanyService within
getCompanies() { ...}
instead of using hard-coded inline static list - Make Service
ng g service company/company --spec false
to generate company.service.ts and Move data retrieval from company-list.component.ts -
company.service.ts uses
@Injectable
to chain dependencies (give all dependencies we need without having to plumb it up manually). - Create field-level variable and assign it in company-list.component.ts
constructor(private companyService: CompanyService) { ...}
. - DI plumbing requires dependencies to be registered in
providers
array of @NgModule so available globally (or optionally only to a feature module) - Change company.service.ts to retrieve data from using
Observable
by importing
- Instead of using
.then
(Promises) for asynch, we use RxJS Observables pattern (baked into AJS2). Unwrap Observable usingsubscribe
in component-list.component.ts. Call service using Observable.
getCompanies() {
this.companyService.getCompanies()
.subscribe(companies => this.companies = companies);
// manual subscriber implementation
- Alternative is Async Pipe (but ONLY use when only subscribing once, otherwise use
.subscribe
to minimise/consolidate amount of streams using operators liketakeUntil
to avoid performance issues, as each time used it creates new subscription!!) to deal with retrieved Obserable (instead of.subscribe
line of code, which gets removed) (cast array of Company[] to Observable), then unwrap Observable in HTML markup using| async
pipe and subscribe using async logic for us automatically
i.e. ` import {Observable … companies: Observable<Company[]>; `
- Update Service and swap-out for call to http service using HttpModule in app.module.ts. Inject into company.service.ts constructor, and import Http
- Service for API API Endpoint
- Add Config file with API endpoint as class property to query i.e.
API_BASE_URL = 'firebootcamp-crm-api/azurewebsite.net/api/'
-
Repeat for Delete Companies by updating company.service.ts passing in company id param i.e.
this.http.delete(... + ${company.id}...
and company-list.component.ts i.e.this.companyService.deleteCompany(companyId).subscribe(() => this.getCompanies());
-
Add Event Binding Handler for Click Event to company-list.component.html i.e.
<button (click)="deleteCompany(company.id)...
- Create AppRoutesModule outlet to DOM tree
<router-outlet></router-outlet>
to Route/navigate to a different Component as SPA (watch URL and change). - Create app.routes.ts and import Components to route to, and pass to AppModule.
- Note: Nested routes and Nested router outlets allowed
- Configure Routes
-
Update app.module.ts
imports
property array of @NgModule withAppRouterModule
-
Create route to navigate to when Add Company button clicked. Update routes with dynamic property.
{path: 'company/edit/:id', component: CompanyEditComponent}
-
Add click handler to Add button in company-list.component.ts that takes us to new Component. Share same component for Add/Edit
<button routerLink="/company/edit/new
// when click “Add” button<button routerLink="/company/edit/ ...
// when click “Edit” button -
Add link to Company List
<a routerLink="/home" ...
-
Add new CompanyEditComponent
ng g c company/company-edit --spec false
-
Modify CompanyEditComponent and inject dependencies
-
Update company-edit.component.ts to detect if in New or Edit mode ` ` and add logic in ngOnInit
-
Create form in company-edit html
.form-group*2>label[for=item$]{item$}+input.form-control+.alert.alert-danger
-
Two-way data binding AJS 2 - Prop binding with event handler i.e.
<input [(ngModel)]="company.name"
// Component name property bind to input field (Banana-in-a-box syntax) - Note:
- ( … ) - event handling
- [ … ] - property binding
- component.name - Components model
- store data in properties of Component
- dumb views
- Template Reference Variable (handle to access for input field for other Components)
#name="ngModel"
// set variable #name to model property of the input field[hidden]="name.valid || name.pristine"
// set hidden property of div if name property is valid (for validation div) -
Configure Form
<form #companyForm="ngForm" (ngSubmit)="saveCompany()"
// create variable companyForm. Call method on form submit event<button type="submit" [disabled]="companyForm.invalid"
- Update company-edit.component.ts
getCompany()
metod to retrieve company from companyService and.subscribe
to it and assign it to the Component company property - Repeat for
saveCompany()
and ensure corresponding methods exist incompanyService
. PassCompany
object intosaveCompany
method in Service. Add headers for POST to service inline (refactor into helper function)
saveCompany(company: Company) {
const headers = new Headers({'content-type' : 'application/json'});
const options = new Request
-
Import Headers and Request from Http dependency
-
Programmatic Routing
this.router.navigateByUrl ...
Lazy Load Homepage
- Create a new Home Component using AJS2 CLI. Generate /src/app/home/home*
- Lazy load through router and build system using conventions
- Create /src/app/home/home.routes.ts and customise
{ path: '', component: HomeComponent },
imports forChild
@NgModule({ … HomeRouter
- If we import Home into App, it would load all JS for both, but we only want JS for specific module being used to load using Lazy Loading
- Decouple app.routes.ts, by passing in
{ path: 'home', loadChildren: 'app/home/home.module#HomeModule' }
instead of just a string for the module (i.e. physical_path # class_name_for_module) - Restart
ng serve
since our new HomeModule is0.chunk.js
which needs to be built beforemain.bundle.js
- Check browser Network where both are loaded, clear network traffic, reload, click the link to HomeModule, and see 0.chunk.js load (Webpack code splitting)
- Code and compiler compiled in browser to see how fast load
- Tree Shaking optimisation (i.e. JS file with functions, it removes/tree shakes out other fns as not used so ahead of time know dependency graph of what required). Only ES6 Class libraries can be Tree Shaken
- Decouple app.routes.ts, by passing in
Deploy
- Deploy using Webpack server (part of AJS 2 CLI)
ng build --prod
// read app and deploy to /dist folder (–prod does source maps, etc)
ng build --prod --aot
// extra step compile offline html templates in node server, tree shaking, minification, etc (very buggy)
// check if any libraries loaded that do not support AOT as may present issue
http-server
// browser Empty Cache & Hard Reload, go to localhost:8080/company/list, and loads in < 1000ms
- Load dist/index.html in production
Testing
- Jasmine - assertion lib / BDD
- Karma - test runner
- Wallaby - shows inline testing results in real-time
.spec.ts
in src folders
- Isolated Test
home.component.spec.ts
import ( HomeComponent } from './home.component';
describe ('Component: HomeComponent', () => {
it ...
}
ng test
// AJS 2 auto finds .spec.ts files
-
See sample code
company-list.component.spec.ts
- Isolate test usng Mocks
TestBed
// moduleproviders
// means pass me my real CompanyServiceNO_ERRORS_SCHEMA
// continue run regardless of errorsspyOn
// intercept requests and replace with mock
State Magagement / Async - Redux / ngRX
firebase-crm-ngrx
-
Perfect for managing complex state
- redux pattern - implement a predictable state container
- Store - maintains state
- Component - subscribes to Store and passes to View
- Reducer - only place change state
-
Dispatcher - dispatch Action Payload to Store
-
Parent and Dummy Presetation Components - can turn off change detection in Children
- Use browser @ngrx debugger
- Click the headings in debugger to revert state
-
See modifications in: company.reducer.ts & company-list.component.ts ?? (subscribe to store
private store: Store<any>
- redux pattern (State Management / Unidirectional Data Flow) + RxJS (Observables) + AJS 2 = ngrx
Common Bugs
- Missing forward slash at start of route i.e.
<button routerLink="/company/edit/ ...
TODO
- Obtain slides afterward
-
- Angular Style Guide - John Papa, Deborah Corada
- Investigate using Interceptor as part of Async Pipe
- Vue.js
- Dynamically create links for Router by passing in relative path and reuse a path from its parent
- tslint.json to configure Code Rules Consistency for teams is good for code reviews
- Add Observables in routes
- DotNet Meetup User Group in Dec (2nd week) - AJS 2 being done
Workflow
- Switch to browser with 3 finger swipe
- Browser debugging add breakpoints for RxJS Observable to ensure .subscribe added for it to fire and track changes