Angular 2
- Big community
- Popular framework
- Scales well
- Code share
XML
Code sharing
XML
JSON
Application level
Essent core modules
API Gateway
- Component-based
- Dependency injection
- Modules (Http, Forms, ...)
CoreModule
FeatureModule
FeatureModule
FeatureModule
SharedModule
Todos feature
AppComponent
AppModule
TodosComponent
TodosModule
TodosList
Component
TodoComponent
|- /app
| - /todos
| - /todo
| - todo.component.html
| - todo.component.scss
| - todo.component.spec.ts
| - todo.component.ts
| - /todos-list
| - todos-list.component.html
| - todos-list.component.scss
| - todos-list.component.spec.ts
| - todos-list.component.ts
| - todos.component.html
| - todos.component.scss
| - todos.component.spec.ts
| - todos.component.ts
| - todos.module.ts
| - app.component.html
| - app.component.scss
| - app.component.spec.ts
| - app.component.ts
| - app.module.ts
State management?
?
Observable
"The Observer and Observable interfaces provide a generalized mechanism for push-based notification, also known as the observer design pattern." -RxJS
const observable$ = Observable.of([1, 2, 3]);
observable$
.subscribe(
value => console.log(value),
error => console.error(error),
() => console.log('Completed')
);
// LOG: [1, 2, 3]
// LOG: Completed
observable$
.map(array => array.map(value => value + 1))
.subscribe(
value => console.log(value),
error => console.error(error),
() => console.log('Completed')
);
// LOG: [2, 3, 4]
// LOG: Completed
Subject
"An RxJS Subject is a special type of Observable that allows values to be multicasted to many Observers" - RxJs
const subject = new Subject();
subject
.subscribe(
value => console.log(value),
error => console.error(error),
() => console.log('Completed')
);
subject.next('foo'); // LOG: foo
subject.next('bar'); // LOG: bar
subject.complete(); // LOG: Completed
subject.next('foo');
const subject = new Subject();
const observable$ = subject.asObservable();
observable$
.subscribe(
value => console.log(value),
error => console.error(error),
() => console.log('Completed')
);
subject.next('foo'); // LOG: foo
subject.next('bar'); // LOG: bar
observable$.next('foo'); // Error
@Injectable()
export class TodosService {
todos$: Observable<Todo[]>;
private todosSubject: Subject<Todo[]>;
constructor(private http: Http) {
this.todosSubject = new Subject();
this.todos$ = this.todosSubject.asObservable();
}
get(): void {
this.http.get('assets/todos.json')
.map((res: Response) => res.json())
.subscribe(
(todos: Todo[]) => this.todosSubject.next(todos),
(error: Response) => console.error(error),
() => console.log('Completed')
);
}
}
Demo
Subject
todos$
Todos
Todos
Subject
todos$
Todos
Todos
Map
Subject
todos$
Todos (done)
Todos (!done)
Map
todosDone$
todosInProgress$
Map
Map
Todos
Subject
todos$
Todos
Scan
newTodo$
Map
Subject
Streams
are
awesome!
Tips state management
- Keep state in streams
- Expose state as an observable
- Manipulate state by executing actions that push data through streams.
- Never manually call error/complete on a stream you do not wish to close
Hot/Cold Observable
Hot
Cold
@Injectable()
export class TodosService {
todos$: Observable<Todo[]>;
private todosSubject: Subject<Todo[]>;
constructor(private http: Http) {
this.todosSubject = new Subject();
this.todos$ = this.todosSubject.asObservable();
}
get(): void {
this.http.get('assets/todos.json')
.map((res: Response) => res.json())
.subscribe(
(todos: Todo[]) => this.todosSubject.next(todos),
(error: Response) => console.error(error),
() => console.log('Completed')
);
}
}
Hot
@Component({
selector: 'q-todos',
templateUrl: 'todos.component.html',
styleUrls: ['todos.component.css']
})
export class TodosComponent implements OnInit {
todos: Todo[] = [];
constructor(private todosService: TodosService) {
}
ngOnInit() {
this.todosService.todos$
.subscribe(
(todos: Todo[]) => this.todos = todos
);
this.todosService.get();
}
}
Subscription
"A Subscription is an object that represents a disposable resource, usually the execution of an Observable." - RxJS
const subject = new Subject();
const observable$ = subject.asObservable();
const subscription = observable$
.subscribe(value =>
console.log(value)
);
subject.next('foo'); // LOG: foo
subscription.unsubscribe();
subject.next('bar');
Memory leaks
demo
localhost:4200
TodosComponent
.ngOnInit()
localhost:4200/other
TodosComponent
.ngOnDestroy()
Hot -> Cold
Tips observables
- Identify hot observables
- Keep track of hot subscriptions in components
- Unsubscribe hot subscriptions onDestroy()
- Make use of operators like .first() to convert a hot subscription to a cold one.
const idSubject = new BehaviorSubject(undefined);
const id$ = idSubject.asObservable()
.do(id => {
if(isNullOrUndefined(id)) {
throw badRequest('No id');
}
});
function getTodos() {
id$.first()
.map(id => {
return `
<Todo>
<Id value="${id}"></Id>
</Todo>
`
})
.switchMap(request => http.post('url', request))
.map(res => res.text())
.switchMap(xml => xmlToJs(xml))
.map(obj => objToTodos(obj))
.subscribe(
data => console.log(data.todos),
error => console.error(error)
);
}
getTodos() // ERROR: Response 'No id'
idSubject.next(1234);
getTodos() // Todos: [..]
Component families
How to keep components small and maintainable
<md-toolbar color="primary">
<span>List of to-dos</span>
</md-toolbar>
<md-list *ngIf="errorMessage === '' && !isLoading">
<md-list-item *ngFor="let todo of todos"
[class.done]="todo.done"
class="todo">
<h2 md-line>
{{ todo.description }}
<input type="checkbox"
[(ngModel)]="todo.done">
</h2>
<p md-line>
Created at: {{ todo.timestamp | date: 'd-M-yyyy H:mm:ss' }}
</p>
</md-list-item>
</md-list>
<h3 *ngIf="errorMessage !== '' && !isLoading">{{errorMessage}}</h3>
<md-spinner *ngIf="isLoading"></md-spinner>
Smart
Dumb
const routes: Route[] = [
{
path: 'todos',
component: TodosComponent,
children: [
{
path: '',
redirectTo: 'loading',
pathMatch: 'full'
},
{
path: 'loading',
component: TodosLoadingComponent
},
{
path: 'error',
component: TodosErrorComponent
},
{
path: 'list',
component: TodosListComponent
}
]
}
];
<!-- TodosComponent -->
<md-toolbar color="primary">
<span>List of to-dos</span>
</md-toolbar>
<router-outlet></router-outlet>
<!-- TodosListComponent -->
<md-list>
<md-list-item *ngFor="let todo of todos"
[class.done]="todo.done"
class="todo">
<h2 md-line>
{{ todo.description }}
<input type="checkbox"
[(ngModel)]="todo.done">
</h2>
<p md-line>
Created at: {{ todo.timestamp | date: 'd-M-yyyy H:mm:ss' }}
</p>
</md-list-item>
</md-list>
<!-- TodosErrorComponent -->
<h3>{{errorMessage}}</h3>
<!-- TodosLoadingComponent -->
<md-spinner></md-spinner>
What about sharing state?
Todo state service
demo
Smart
Dumb
Tips components
- Don't abuse the *ngIf directive
- Create a smart component with router-outlet
- Create dumb components (children of smart)
- Smart has access to the REST service and controls its children via a local state service
- State service contains subjects, observables and setter methods
Essent - Angular 2
By rachnerd
Essent - Angular 2
- 363