Enterprise Software Development
Building Applications with Angular
By Matt Wicks, Luke Cook & Jean Thirion
Morning Tea | 10:30am - 10:45am |
Lunch | 1pm - 2pm |
Afternoon Tea | 3:30pm - 3:45pm |
Finish | 5pm |
House Keeping
We will share code on GitHub��https://github.com/SSWConsulting/ng-workshop-syd-oct-2024
Welcome !
Agenda
Fundamentals & The Angular CLI
RxJS, Observables & Http
Components & Services
Component Communication & Routing
Reactive Forms
State Management
Modules & Code Splitting
Unit Testing
Who has used Angular?
Who has used React or Vue?
Who is using ngrx/ngxs?
Who has done unit testing?
Let’s get to know each other
Name?
Angular experience?
What you would like to get from the workshop?
@angular2
IT’S JUST “ANGULAR”
Speed & Performance
Productivity
Complete Framework
Component Tree
Component Tree
Todo
App
Component
Todo
Component
Todo List Component
Todo Edit
Component
NgModules
App Component
Todo
Component
Todo List Component
Todo Edit Component
Home Component
Angular itself is easy
... but it forces you to confront modern web development.
TypeScript
Module Loader
RxJS
JS
Superfast and cross platform
More productive
Complete framework
Opinionated - structured
Enterprise friendly
You can be more ambitious
Summary - What is Angular and Why Care ?
What’s new in Angular 16 (May 2023)
What‘s new in Angular 17 (Nov 2023)
What‘s new in Angular 17
What‘s new in Angular 18 🔥🔥🔥 �(May 2024)
SSR improvements:
Let’s look at some code
👩💻
Lunch order
CRM - Module 8
RXJS AND OBSERVABLES
Modern web apps are composed of asynchronous events�
Async code seems hard to write, refactor and debug
RxJS
RxJS simplifies async code�
RxJS
An API for asynchronous programming
with observable streams
Observable�[]
Observer 1
[]
Observer 2
[]
Observable�[1]
Observer 1
[1]
Observer 2
[1]
Observable�[1, 4]
Observer 1
[1]
Observer 2
[1]
Observable�[1, 4]
Observer 1
[1, 4]
Observer 2
[1, 4]
Observable�[1, 4, 7]
Observer 1
[1, 4]
Observer 2
[1, 4]
Observable�[1, 4, 7]
Observer 1
[1, 4, 7]
Observer 2
[1, 4, 7]
.map(x => x + 1)
Observable�[1]
Observer 1
[2]
.map(x => x + 1)
Observable�[1,4]
Observer 1
[2]
.map(x => x + 1)
Observable�[1,4]
Observer 1
[2,5]
.map(x => x + 1)
Observable�[1,4,7]
Observer 1
[2,5]
.map(x => x + 1)
Observable�[1,4,7]
Observer 1
[2,5,8]
.map(x => x + 1)
.filter(x => x > 2)
Observable�[1,4,7]
Observer 1
[2,5,8]
Observer 2
[4,7]
.map(x => x + 1)
.filter(x => x > 2)
.reduce((x,y) => x + y)
Observable�[1,4,7]
Observer 1
[2,5,8]
Observer 2
[4,7]
.map(x => x + 1)
.filter(x => x > 2)
.reduce((x,y) => x + y)
Observable�[1,4,7]
Observer 1
[2,5,8]
Observer 2
[11]
Combination
combineAll
combineLatest
concat
concatAll
exhaust
forkJoin
merge
mergeAll
race
startWith
switch
withLatestFrom
zip
zipAll
Multicasting
cache
multicast
publish
publishBehavior
publishLast
publishReplay
share
Error Handling
catch
retry
retryWhen
Utility
do
delay
delayWhen
dematerialize
finally
let
materialize
observeOn
subscribeOn
timeInterval
timestamp
timeout
timeoutWith
toArray
toPromise
Conditional
and Boolean
defaultIfEmpty
every
find
findIndex
isEmpty
Mathematical
and Aggregate
count
max
min
reduce
Creation
ajax
bindCallback
bindNodeCallback
create
defer
empty
from
fromEvent
fromEventPattern
fromPromise
generate
interval
never
of
repeat
repeatWhen
range
throw
timer
Transformation
buffer
bufferCount
bufferTime
bufferToggle
bufferWhen
concatMap
concatMapTo
exhaustMap
expand
groupBy
map
mapTo
mergeMap
mergeMapTo
mergeScan
pairwise
partition
pluck
scan
switchMap
switchMapTo
window
windowCount
windowTime
windowToggle
windowWhen
Filtering
debounce
debounceTime
distinct
distinctKey
distinctUntilChanged
distinctUntilKeyChanged
elementAt
filter
first
ignoreElements
audit
auditTime
last
sample
sampleTime
single
skip
skipLast
skipUntil
skipWhile
take
takeLast
takeUntil
takeWhile
throttle
throttleTime
Enter search term…
this.companies$ = this.searchText.valueChanges.pipe(
debounceTime(500),
distinctUntilChanged(),
switchMap(searchText => this.companyService.search(searchText)),
);
@Injectable()�export class PeopleService {
constructor(private http: Http) { }
getPeople() {
return this.http.get(`http://swapi.co/api/people/`)
.map(response => response.json());
.subscribe(people => console.log(people));
}
}
@Component()�export class SearchComponent implements OnInit {� searchControl = new FormControl();� ngOnInit() {� this.searchControl.valueChanges.subscribe(value => {� // do something with value here� });� }�}
ngOnInit() {� this.route.params.pipe(
map(params => params['id'])
mergeMap(id => this.contactsService.getContact(id))
)
.subscribe(contact => this.contact = contact);
}
RxJS is a journey...
HTTP is not the best example of why to use observables over promises
Modern web development is asynchronous
RxJS takes some effort to fully understand, but it simplifies writing async code
RxJS is baked into Angular
Summary - RxJS
CRM - Module 12
STATE MANAGEMENT WITH NGRX
State management is complex and time consuming
CONTAINER COMPONENT
SERVICE A
SERVICE A
CONTAINER COMPONENT
SERVICE A
SERVICE A
PRESENTATIONAL COMPONENT
CONTAINER COMPONENT
SERVICE A
SERVICE A
PRESENTATIONAL COMPONENT
@Input/@Output
PRESENTATIONAL COMPONENT
@Input/@Output
CONTAINER COMPONENT
SERVICE A
SERVICE A
PRESENTATIONAL COMPONENT
@Input/@Output
PRESENTATIONAL COMPONENT
@Input/@Output
PRESENTATIONAL COMPONENT
@Input/@Output
SERVICE B
CONTAINER COMPONENT
SERVICE A
SERVICE A
PRESENTATIONAL COMPONENT
@Input/@Output
PRESENTATIONAL COMPONENT
@Input/@Output
PRESENTATIONAL COMPONENT
@Input/@Output
SERVICE B
CONTAINER
COMPONENT
SERVICE B
SERVICE A
SERVICE B
CONTAINER COMPONENT
SERVICE A
SERVICE A
PRESENTATIONAL COMPONENT
@Input/@Output
CONTAINER COMPONENT
PRESENTATIONAL COMPONENT
@Input/@Output
SERVICE B
CONTAINER
COMPONENT
SERVICE B
SERVICE A
SERVICE B
SERVICE A
CONTAINER COMPONENT
SERVICE A
SERVICE A
PRESENTATIONAL COMPONENT
@Input/@Output
CONTAINER COMPONENT
PRESENTATIONAL COMPONENT
@Input/@Output
SERVICE B
CONTAINER
COMPONENT
SERVICE B
SERVICE A
SERVICE B
SERVICE A
CONTAINER COMPONENT
SERVICE A
SERVICE B
CONTAINER COMPONENT
SERVICE A
SERVICE A
PRESENTATIONAL COMPONENT
@Input/@Output
CONTAINER COMPONENT
PRESENTATIONAL COMPONENT
@Input/@Output
SERVICE B
CONTAINER
COMPONENT
SERVICE B
SERVICE A
SERVICE B
SERVICE A
CONTAINER COMPONENT
SERVICE A
SERVICE B
SERVICE C
SERVICE D
SERVICE C
SERVICE D
CONTAINER COMPONENT
SERVICE A
SERVICE A
PRESENTATIONAL COMPONENT
@Input/@Output
CONTAINER COMPONENT
PRESENTATIONAL COMPONENT
@Input/@Output
SERVICE B
CONTAINER
COMPONENT
SERVICE B
SERVICE A
SERVICE B
SERVICE A
CONTAINER COMPONENT
SERVICE A
SERVICE B
SERVICE C
SERVICE D
SERVICE C
SERVICE D
CONTAINER COMPONENT
SERVICE A
SERVICE A
PRESENTATIONAL COMPONENT
@Input/@Output
CONTAINER COMPONENT
PRESENTATIONAL COMPONENT
@Input/@Output
SERVICE B
CONTAINER
COMPONENT
SERVICE B
SERVICE A
SERVICE B
SERVICE A
CONTAINER COMPONENT
SERVICE A
SERVICE B
SERVICE C
SERVICE D
SERVICE C
SERVICE D
CONTAINER COMPONENT
SERVICE A
SERVICE A
PRESENTATIONAL COMPONENT
@Input/@Output
CONTAINER COMPONENT
PRESENTATIONAL COMPONENT
@Input/@Output
SERVICE B
CONTAINER
COMPONENT
SERVICE B
SERVICE A
SERVICE B
SERVICE A
CONTAINER COMPONENT
SERVICE A
SERVICE B
SERVICE C
SERVICE D
SERVICE C
SERVICE D
CONTAINER COMPONENT
SERVICE A
SERVICE A
PRESENTATIONAL COMPONENT
@Input/@Output
CONTAINER COMPONENT
PRESENTATIONAL COMPONENT
@Input/@Output
SERVICE B
CONTAINER
COMPONENT
SERVICE B
SERVICE A
SERVICE B
SERVICE A
CONTAINER COMPONENT
SERVICE A
SERVICE B
SERVICE C
SERVICE D
SERVICE C
SERVICE D
CONTAINER COMPONENT
STORE
STORE
PRESENTATIONAL COMPONENT
@Input/@Output
CONTAINER COMPONENT
PRESENTATIONAL COMPONENT
@Input/@Output
CONTAINER
COMPONENT
STORE
STORE
CONTAINER COMPONENT
STORE
+
=
+
RxJS
redux Pattern
ngrx
Angular
ngrx supercharges the redux pattern with RxJS
@angularsuperpowers @BrendanSSW @Jean_SSW
Component
Store
Reducers
ACTIONS
SELECTORS
NEW STATE
{
type: 'DELETE_COMPANY',
payload: {id:1}
}
{
companies: [
{id:1, name:'SSW'},
{id:2, name:'Microsoft'}
],
contacts: [
{id:1, name:Duncan}
]
};
companies: [
{id:1, name:'SSW'},
{id:2, name:'Microsoft'}
];
{
companies: [
{id:2, name:'Microsoft'}
],
contacts: [
{id:1, name:Duncan}
]
};
companies: [
{id:2, name:'Microsoft'}
];
current state
new state
action
store state
Component
Store
Reducers
ACTIONS
SELECTORS
Effects
Services
ACTIONS
NEW STATE
{
type: 'DELETE_COMPANY',
payload: {id:1}
}
{
companies: [
{id:1, name:'SSW'},
{id:2, name:'Microsoft'}
],
contacts: [
{id:1, name:Duncan}
]
};
{
companies: [
{id:2, name:'Microsoft'}
],
contacts: [
{id:1, name:Duncan}
]
};
companies: [
{id:2, name:'Microsoft'}
];
new state
action
{
type: 'DELETE_COMPANY_SUCCESS',
payload: {id:1}
}
action
store state
ngrx
… or not
Authentication
@angularsuperpowers @MattEightyAte @Jean_SSW
JavaScript SPA
ASP.NET Core WebAPI
Identity Server
Browser
@angularsuperpowers @MattEightyAte @Jean_SSW
JavaScript SPA
ASP.NET Core WebAPI
Identity Server
Browser
oidc-client.js
Login Form
OIDC client redirects browser to a login page on the Identity Server.
User Logs in.
@angularsuperpowers @MattEightyAte @Jean_SSW
ASP.NET Core WebAPI
Identity Server
Browser
oidc-client.js
Identity Server redirects back with an Authorization Token
Token is saved on the browser to LocalStorage
auth.html
Auth Token
@angularsuperpowers @MattEightyAte @Jean_SSW
JavaScript SPA
ASP.NET Core WebAPI
Identity Server
Browser
Send the Auth Token as a HTTP header with all API requests
The API server checks the token against the Identity Server
Http
Auth Token
@angularsuperpowers @MattEightyAte @Jean_SSW
Auth Service - token in LocalStorage
@angularsuperpowers @MattEightyAte @Jean_SSW
Http Interceptor - apply token to all requests
@angularsuperpowers @MattEightyAte @Jean_SSW
Route Guards
@angularsuperpowers @MattEightyAte @Jean_SSW
SSW Reward App !
Download the App from the store
Get your first 500 points 🔥😍
Get a smart coffee cup, MiBands, Google Nest...
Feedback
Thank you!