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