Application Architecture Angular2
![](http://enitiate.solutions/wp-content/uploads/2015/07/3D_blueprints.jpg)
Application Architecture Angular2
Prerequisites
![](http://img00.deviantart.net/5def/i/2014/137/0/5/architecture_concepts_minimalist__by_pk87-d734uqk.jpg)
- Web Components (Angular / Polymer)
- TypeScript
- Observable
- RxJs
- Redux
Web Components
![](http://img00.deviantart.net/5def/i/2014/137/0/5/architecture_concepts_minimalist__by_pk87-d734uqk.jpg)
http://webcomponents.org/
Web Components
![](http://img00.deviantart.net/5def/i/2014/137/0/5/architecture_concepts_minimalist__by_pk87-d734uqk.jpg)
Web Components
![](http://img00.deviantart.net/5def/i/2014/137/0/5/architecture_concepts_minimalist__by_pk87-d734uqk.jpg)
2016-06-24 |
Shadow DOMDescribes a method of establishing and maintaining functional boundaries between DOM subtrees and how these subtrees interact with each other within a document tree. |
2016-06-21 |
Custom ElementsThis document describes the method for enabling the author to define and use new types of DOM elements in a document. |
2016-02-25 |
HTML ImportsThis document defines a way to include and reuse HTML documents in other HTML documents. |
Web Components
![](http://img00.deviantart.net/5def/i/2014/137/0/5/architecture_concepts_minimalist__by_pk87-d734uqk.jpg)
Polymer
![](http://img00.deviantart.net/5def/i/2014/137/0/5/architecture_concepts_minimalist__by_pk87-d734uqk.jpg)
https://www.polymer-project.org
![](https://www.polymer-project.org/images/logos/p-logo.png)
Polymer
![](http://img00.deviantart.net/5def/i/2014/137/0/5/architecture_concepts_minimalist__by_pk87-d734uqk.jpg)
![](https://assets.toptal.io/uploads/blog/image/393/toptal-blog-image-1399907631482.png)
Polymer
![](http://img00.deviantart.net/5def/i/2014/137/0/5/architecture_concepts_minimalist__by_pk87-d734uqk.jpg)
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="bower_components/paper-styles/paper-styles.html">
<link rel="import" href="bower_components/iron-icon/iron-icon.html">
<link rel="import" href="bower_components/iron-icons/iron-icons.html">
<link rel="import" href="bower_components/iron-icons/maps-icons.html">
<link rel="import" href="bower_components/iron-dropdown/iron-dropdown.html">
<link rel="import" href="bower_components/paper-icon-button/paper-icon-button.html">
<link rel="import" href="bower_components/paper-checkbox/paper-checkbox.html">
<link rel="import" href="bower_components/paper-radio-group/paper-radio-group.html">
<link rel="import" href="bower_components/paper-radio-button/paper-radio-button.html">
<link rel="import" href="bower_components/paper-button/paper-button.html">
<link rel="import" href="bower_components/paper-input/paper-input.html">
<link rel="import" href="bower_components/paper-item/paper-item.html">
<link rel="import" href="bower_components/paper-card/paper-card.html">
<link rel="import" href="bower_components/paper-spinner/paper-spinner.html">
<paper-card heading="{{title}}">
<div class="subtitle">{{subtitle}}</div>
<img src="thumbnail"/>
<div class="card-content">{{description}}</div>
<paper-button>Remove from collection</paper-button>
<paper-button>Add to collection</paper-button>
</paper-card>
Angular 2
![](http://img00.deviantart.net/5def/i/2014/137/0/5/architecture_concepts_minimalist__by_pk87-d734uqk.jpg)
https://angular.io
Angular 2
![](http://img00.deviantart.net/5def/i/2014/137/0/5/architecture_concepts_minimalist__by_pk87-d734uqk.jpg)
@Component({
moduleId: module.id,
selector: 'book-preview',
directives: [NgSwitch],
templateUrl: 'book.preview.html',
styleUrls: ['book.preview.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BookPreviewComponent {
public collectionAdd$ = new Subject<AddOutput>().map(e =>Object.assign(new AddOutput(), e));
public collectionRemove$ = new Subject<RemoveOutput>().map(e =>Object.assign(new RemoveOutput(), e));
@Input() book: Book;
@Output() collectionToggle$: Observable<AddOutput|RemoveOutput> = this.collectionAdd$
.merge(this.collectionRemove$);
constructor() {
console.log('BookPreviewComponent')
}
...
}
<book-preview (collectionToggle$)="onCollectionToggle($event)"></book-preview>
TypeScript
![](http://img00.deviantart.net/5def/i/2014/137/0/5/architecture_concepts_minimalist__by_pk87-d734uqk.jpg)
http://www.typescriptlang.org/
![](https://pbs.twimg.com/profile_images/743155381661143040/bynNY5dJ_400x400.jpg)
TypeScript
![](http://img00.deviantart.net/5def/i/2014/137/0/5/architecture_concepts_minimalist__by_pk87-d734uqk.jpg)
class Animal {
constructor(public name: string) { }
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
class Horse extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 45) {
console.log("Galloping...");
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");
sam.move();
tom.move(34);
Observable
![](http://img00.deviantart.net/5def/i/2014/137/0/5/architecture_concepts_minimalist__by_pk87-d734uqk.jpg)
Proposal to introduces
Observable type to the ECMAScript
const search: Observable<SearchOutput> = this.keyup$
.debounceTime(300)
.map(event => (event.target as HTMLInputElement).value)
.distinctUntilChanged();
- Enables the resolution of complex problem in an elegant, clean and simple way (ie: Retrys and Debounce)
- Removes States from Components
- Enables the writing of Functional Reactive Application with uni-directional data flow
RxJs
![](http://img00.deviantart.net/5def/i/2014/137/0/5/architecture_concepts_minimalist__by_pk87-d734uqk.jpg)
http://reactivex.io/
![](http://reactivex.io/assets/Rx_Logo_S.png)
RxJs
![](http://img00.deviantart.net/5def/i/2014/137/0/5/architecture_concepts_minimalist__by_pk87-d734uqk.jpg)
Observable Toolkit
![](http://reactivex.io/rxjs/img/mergeAll.png)
![](http://image.slidesharecdn.com/frptalk-141128120856-conversion-gate02/95/functional-reactive-programming-with-rxjs-8-638.jpg?cb=1417176753)
http://rxmarbles.com/
Redux
![](http://img00.deviantart.net/5def/i/2014/137/0/5/architecture_concepts_minimalist__by_pk87-d734uqk.jpg)
https://github.com/reactjs/redux
![](https://raw.githubusercontent.com/reactjs/redux/master/logo/logo.png)
A predictable state container
for JavaScript apps.
Concepts
![](http://img00.deviantart.net/5def/i/2014/137/0/5/architecture_concepts_minimalist__by_pk87-d734uqk.jpg)
- Immutability
- Uni-Directional Data Flow
- Fonctional Reactive Programming
- Data Store
- Smart/Dumb Components
- Inter-Components Data-Transfer Patterns
- Reducers / Projections / Effects
Attention FRP Architecture is Optional
Polymer is a UI Library, there are other
UI Libraries that you can choose from
Elements
![](http://vintagedesigns.com/architecture/gothic/pointed/char/chimney-tops.jpg)
- Actions
- App
- Components
- Effects
- Models
- Pages
- Projections
- Reducers
- Services
- Bootstrap
App Structure
Page (Smart)
Component (Dumb)
Smart / Dumb Components
![](https://a1-images.myspacecdn.com/images03/34/bb1b92a68c184fd1811735aece35337c/300x300.jpg)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/10703/images/2831645/app-shell.jpg)
Smart Component (Page 1)
collectionCnt$
router links
![](https://s3.amazonaws.com/media-p.slid.es/uploads/10703/images/2831659/book-find.jpg)
Smart Component
(Page 2)
Action + / - Collection
searchQuery$
searchPending$
book$
![](https://s3.amazonaws.com/media-p.slid.es/uploads/10703/images/2831666/collection.jpg)
Action + / - Collection
book$
Smart Component
(Page 3)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/10703/images/2831740/components.jpg)
Dumb
Component
![](https://a1-images.myspacecdn.com/images03/34/bb1b92a68c184fd1811735aece35337c/300x300.jpg)
Smart Components
Has a reference to the Store
Dumb Components
Primarly getters and
no ref to the Store
![](https://a1-images.myspacecdn.com/images03/34/bb1b92a68c184fd1811735aece35337c/300x300.jpg)
![](http://www.winxdvd.com/resource/new-fourteen/vs.png)
Data Flow
![](https://s3.amazonaws.com/media-p.slid.es/uploads/10703/images/2838050/overall.jpg)
What is the Store ?
![](http://www.oraclehome.com.br/wp-content/uploads/2011/08/512-odbc-administrator1.png)
{
books: ...,
search: ...,
collection: ...,
}
A Store is an arbitrary
Object Structure
That will generate
snap-shots of your current App State
---(snap-shot)---(snap-shot)---(snap-shot)---(snap-shot)---(snap-shot)->
But what goes
at the place of these
mysterious 3 small dots
???
Reducers
---(snap-shot)---(snap-shot)---(snap-shot)---(snap-shot)---(snap-shot)->
{
books: bookReducer,
search: searchReducer,
collection: collectionReducer,
}
Like a DB a Store
has a Structure and Content
The structure is the top level of the App State
The Content is generated by the Reducers
{
books: ...,
search: ...,
collection: ...,
}
{
books: data <== bookReducer,
search: data <== searchReducer,
collection: data <== collectionReducer,
}
books:{
entities:{
0BSOg0oHhZ0C:{
id: string;
isInCollection:boolean; <----- Attention
volumeInfo: {
title: string;
subtitle: string;
authors: string[];
publisher: string;
publishDate: string;
description: string;
averageRating: number;
ratingsCount: number;
imageLinks: {
thumbnail: string;
smallThumbnail: string;
};
};
},
0fdgsdfgsdfg:{...},
...
ids:[
"0BSOg0oHhZ0C",
"0fdgsdfgsdfg",
"sD7HBwAAQBAJ",
"Vf32PZXJ2gMC"
...
]
}
BookReducer Data Output
collection:{
ids:[
"0BSOg0oHhZ0C",
"0fdgsdfgsdfg",
"sD7HBwAAQBAJ",
"Vf32PZXJ2gMC"
...
]
}
CollectionReducer Data Output
collection:{
ids:[
"0BSOg0oHhZ0C",
"0fdgsdfgsdfg",
"sD7HBwAAQBAJ",
"Vf32PZXJ2gMC"
...
]
}
SearchReducer Data Output
Rob Wormald
Data Flow
![](https://s3.amazonaws.com/media-p.slid.es/uploads/10703/images/2838050/overall.jpg)
What is a Reducer ?
![](http://st.hzcdn.com/simgs/e3a1480104871f37_4-4696/modern-dining-tables.jpg)
A reducer is a function that create a new "Table State"
(doing so will creates a new "App State")
bookReducer
{
books: bookReducer,
search: searchReducer,
collection: collectionReducer,
}
books:{
entities:{
0BSOg0oHhZ0C:{
id: string;
isInCollection:boolean; <----- Attention
volumeInfo: {
title: string;
subtitle: string;
authors: string[];
publisher: string;
publishDate: string;
description: string;
averageRating: number;
ratingsCount: number;
imageLinks: {
thumbnail: string;
smallThumbnail: string;
};
};
},
0fdgsdfgsdfg:{...},
...
ids:[
"0BSOg0oHhZ0C",
"0fdgsdfgsdfg",
"sD7HBwAAQBAJ",
"Vf32PZXJ2gMC"
...
]
}
BookReducer Details
(Table = Books, Column = entities, ids)
Objectif
The search return an Array of Books
Context
- Merge the SearchRes with the Current EntitiesMap (Array)
- Merge the SearchRes ids with the Current ids
- Create a new EntitiesMap from the merge Books
ie: Update bookReducer
Reducer Example
case BookActions.SEARCH_COMPLETE:
{
const searchRes: Book[] = action.payload;
//get the entities values as an array
const currentBooks: Book[] = Object.keys(state.entities).map(k => state.entities[k]);
//merge of search and current books
const newBooks: Book[] = _.uniqBy([...currentBooks, ...searchRes], 'id');
//same for ids
const newBookIds: string[] = _.uniqBy([...state.ids, ...newBooks.map(book => book.id)]);
const newEntitiesMap = newBooks.reduce((entities: { [id: string]: Book }, book: Book) => {
return Object.assign(entities, {[book.id]: book});
}, {});
return {
ids: newBookIds,
entities: newEntitiesMap
};
}
Data Flow
![](https://s3.amazonaws.com/media-p.slid.es/uploads/10703/images/2838050/overall.jpg)
What is an Action
![](https://patentimages.storage.googleapis.com/US6553706B1/US06553706-20030429-D00003.png)
Actions Pattern
Event {Type, Payload} that trigger a Reducer and/or an Effect
static SEARCH = '[Book] Search';
search(query:string):Action {
return {
type: BookActions.SEARCH,
payload: query
};
}
static SEARCH_COMPLETE = '[Book] Search Complete';
searchComplete(results:Book[]):Action {
return {
type: BookActions.SEARCH_COMPLETE,
payload: results
};
}
What is an Effect
![](http://www.kito.ca/media/W1siZiIsIjIwMTQvMDUvMjIvMWJhNWVuNG9qeV9jaGFpbi5wbmciXSxbInAiLCJ0aHVtYiIsIjE2MDB4NjAwIl1d/chain.png?sha=e84ed4a1)
Effects
(Short for Side Effects)
A chain of actions with side effects
and the App State as Scope
![](http://1.bp.blogspot.com/-t8CEuQJV3_c/UKZSGCr534I/AAAAAAAACos/GTWUuHX5k58/s1600/Screen+Shot+2012-11-16+at+8.47.25+AM.png)
@Effect() search$ = this.appState$
.whenAction(BookActions.SEARCH)
.map<string>(toPayload)
.switchMap(query => this.googleBooks.searchBooks(query)
.map(books => this.bookActions.searchComplete(books))
.catch(() => Observable.of(this.bookActions.searchComplete([])))
);
//Search Reducer set pending to true
case BookActions.SEARCH: {
const query = action.payload;
return Object.assign(state, {
query,
pending: true
});
}
//Search Reducer set pending to false
case BookActions.SEARCH_COMPLETE: {
const books: Book[] = action.payload;
return {
ids: books.map(book => book.id),
pending: false,
query: state.query
};
}
Pattern For Service Call
//Book Reducer
case BookActions.SEARCH_COMPLETE: {
const searchRes: Book[] = action.payload;
const currentBooks = Object.keys(state.entities)
.map(k => state.entities[k]);
//merge books and current searchRes
const newBooks = _.uniqBy(
[...currentBooks ,
...searchRes],
'id');
const newBookIds = _.uniqBy(
[...state.ids,
...newBooks.map(book => book.id)
]);
return {
ids: newBookIds,
entities: createEntitiesMap(newBooks)
};
}
@Effect() Add$ = this.appState$
.whenAction(CollectionActions.ADD_TO_COLLECTION)
.map((o: {action: Action, state: AppState}) => {
return o.state.books.entities[o.action.payload];
})
.map(entity => this.collectionActions.addToCollectionComplete(entity));
//Book Reducer Update isInCollection: true on the selected Book
case CollectionActions.ADD_TO_COLLECTION: {
const bookId = action.payload;
const newBook = Object.assign(state.entities[bookId], {isInCollection: true});
const newBooks = applyNewBookToBooks(state, newBook);
return {
ids: state.ids,
entities: getNewEntities(newBooks)
};
}
//Collection Reducer add the Book id to the collection.ids
case CollectionActions.ADD_TO_COLLECTION_COMPLETE:
const addBookId = action.payload.id;
return {
ids: _.uniqBy([...state.ids, addBookId]),
};
Pattern For Chaining Actions
Data Flow
![](https://s3.amazonaws.com/media-p.slid.es/uploads/10703/images/2838050/overall.jpg)
How do I trigger an Action ?
And from where ?
![](https://i.ytimg.com/vi/ICpncjXsy5s/maxresdefault.jpg)
Only trigger Actions in
Smart Components
this.store.dispatch(this.collectionActions.addToCollection(e.id));
this.store.dispatch(this.collectionActions.removeFromCollection(e.id));
![](https://a1-images.myspacecdn.com/images03/34/bb1b92a68c184fd1811735aece35337c/300x300.jpg)
Inject Store in Smart Components
![](https://a1-images.myspacecdn.com/images03/34/bb1b92a68c184fd1811735aece35337c/300x300.jpg)
Don't let dumbs talk to each other
![](https://a1-images.myspacecdn.com/images03/34/bb1b92a68c184fd1811735aece35337c/300x300.jpg)
![](https://a1-images.myspacecdn.com/images03/34/bb1b92a68c184fd1811735aece35337c/300x300.jpg)
![](https://a1-images.myspacecdn.com/images03/34/bb1b92a68c184fd1811735aece35337c/300x300.jpg)
Always put a smart in-between
Ok now I know how to create new App State
![](https://cdn.meme.am/instances/62985534.jpg)
How do I access
my App State
![](http://www.stateofdigital.com/wp-content/uploads/2013/05/picard-data-meme.png)
Data Flow
![](https://s3.amazonaws.com/media-p.slid.es/uploads/10703/images/2838050/overall.jpg)
Projections
export function getSearchResults() {
return (state$:Observable<AppState>)=>
state$
.map((s:AppState)=> {
let bookIds = s.search.ids;
let entities = s.books.entities;
return bookIds.map(id => entities[id]);
})
}
export function getCollectionCnt() {
return (state$:Observable<AppState>)=>
state$
.map((s:AppState)=>{
return s.collection.ids.length
})
}
Access to data from smart component
with named function with the App State scope
this.books$ = store.let(getSearchResults());
this.collectionCnt$ = store.let(getCollectionCnt());
Data Flow
![](https://s3.amazonaws.com/media-p.slid.es/uploads/10703/images/2838050/overall.jpg)
What about the dumb components
![](https://a1-images.myspacecdn.com/images03/34/bb1b92a68c184fd1811735aece35337c/300x300.jpg)
![](http://www.oraclehome.com.br/wp-content/uploads/2011/08/512-odbc-administrator1.png)
Inter-Components
Data-Transfer Patterns
- Shared Service
- Events Bubbling
- Pub/Sub
- Contracts
![](http://cdn.xl.thumbs.canstockphoto.co.kr/canstock6901737.jpg)
Contracts
![](http://static1.squarespace.com/static/547ca21ee4b07ee9b5c55958/t/54e4df37e4b089e48e886c81/1424285504924/contract-lawyer-attorney-drafting-reviewing-negotiating-revising.jpg?format=1500w)
Dumb Components
@Input & @Output
![](https://s3.amazonaws.com/media-p.slid.es/uploads/10703/images/2835232/pass-plate.jpg)
Book-find
@Input() searchQuery$
@Input() searchPending$
@Input() books$
Book
@Input() book
@Output() collectionToggle$
Book-List
@Input() books
@Output() collectionToggle$
Book Component (dumb)
![](https://a1-images.myspacecdn.com/images03/34/bb1b92a68c184fd1811735aece35337c/300x300.jpg)
public collectionAdd$ = new Subject<AddOutput>().map(e => Object.assign(new AddOutput(), e));
public collectionRemove$ = new Subject<RemoveOutput>().map(e => Object.assign(new RemoveOutput(), e));
@Input() book: Book;
@Output() collectionToggle$: Observable<AddOutput|RemoveOutput> = this.collectionAdd$
.merge(this.collectionRemove$);
get id() {
return this.book.id;
}
get title() {
return this.book.volumeInfo.title;
}
get description() {
return this.book.volumeInfo.description;
}
get inCollection(): boolean {
return !!this.book.inCollection;
}
<paper-card [heading]="title">
<paper-button (click)="collectionRemove$.next(book)">Remove from collection</paper-button>
<paper-button (click)="collectionAdd$.next(book)">Add to collection</paper-button>
</paper-card>
Book-List Component (dumb)
![](https://a1-images.myspacecdn.com/images03/34/bb1b92a68c184fd1811735aece35337c/300x300.jpg)
@Output() collectionToggle$ = new Subject<AddOutput|RemoveOutput>();
@Input() books: Books;
onCollectionToggle(e: AddOutput|RemoveOutput) {
this.collectionToggle$.next(e);
}
<div class="book-list">
<book (collectionToggle$)="onCollectionToggle($event)"
*ngFor="let book of books" [book]="book"></book>
</div>
Book-Find Page (smart)
onCollectionToggle(e) {
if (e instanceof AddOutput) {
this.store.dispatch(this.collectionActions.addToCollection(e.id));
}
if (e instanceof RemoveOutput) {
this.store.dispatch(this.collectionActions.removeFromCollection(e.id));
}
}
...
<book-list [books]="books$ | async"
(collectionToggle$)="onCollectionToggle($event)"></book-list>
...
![](https://s3.amazonaws.com/media-p.slid.es/uploads/10703/images/2838050/overall.jpg)
![](https://a1-images.myspacecdn.com/images03/34/bb1b92a68c184fd1811735aece35337c/300x300.jpg)
![](http://www.relatably.com/m/img/perfect-memes/eb10d1d0e41d4f2980a8c84c403ae6cb3783136e79562904762ae08ad5fac841.jpg)
![](http://onlineslotsdirectory.com/wp-content/uploads/2015/01/slots-bonuses.jpg)
Application Architecture Ng2
By bretto
Application Architecture Ng2
- 774