slides.com/gerardsans | @gerardsans
Spoken at 52 events in 18 countries
900
1K
Angular In Flip Flops
Future ready
Stable and performant
4.2 Animations
New angular.io website
4.3 HttpClient
Smaller, Faster and Easier to use
PWA support
SSR. Material components
Bazel. Build time tools
X . Y . Z
MAJOR MINOR PATCH
10th Aug
schematics/@angular
ng serve --serve-path custom
http://localhost:4200/custom
--missing-translation error
7th Sept
// validator function
const ctrl = new FormControl('', Validators.required);
// options argument
const form = new FormGroup({
password: new FormControl('')
passwordRepeat: new FormControl('')
}, {
validators: passwordMatchValidator,
asyncValidators: userExistsValidator
});
// arrays
const g = new FormGroup({
one: new FormControl()
}, [passwordMatchValidator]);
<form [ngFormOptions]="{updateOn: 'blur'}">
<!-- will update on blur-->
<input name="one" ngModel>
</form>
<form [ngFormOptions]="{updateOn: 'blur'}">
<!-- overrides and will update on change-->
<input name="one"
ngModel [ngModelOptions]="{updateOn: 'change', standalone: true}">
</form>
const c = new FormGroup({
one: new FormControl()
}, {updateOn: 'change'});
const c = new FormArray([
new FormControl()
], {updateOn: 'blur'});
const c = new FormControl(, {
updateOn: 'submit'
});
<form>
<!-- Requires formControlName, formControl, ngModel -->
<input type="number" ngModel [min]="2" [max]="100">
</form>
// { 'min': 2, 'actual': 1 }
// { 'max': 100, 'actual': 2000 }
import { Router, ActivationStart, ActivationEnd } from '@angular/router';
import 'rxjs/add/operator/filter';
@Component({
selector: 'my-app',
template: `<router-outlet></router-outlet>`
})
export class AppComponent {
constructor(private router: Router) {
router.events
.filter(e =>
e instanceof ActivationStart || e instanceof ActivationEnd)
.subscribe(e => console.log(e.toString()));
}
}
import { Router, RouterEvent } from '@angular/router';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/distinctUntilChanged';
@Component({
selector: 'my-app',
template: `<router-outlet></router-outlet>`
})
export class AppComponent {
constructor(private router: Router) {
router.events
.filter(e => e instanceof RouterEvent)
.filter(e => e.url == '/about')
.distinctUntilChanged((e1, e2) => e1.id == e2.id && e1.url == e2.url )
.subscribe(e => {
console.log(e.id, e.url);
});
}
}
// no en-US locale
import { registerLocaleData } from '@angular/common';
import localeEs from '@angular/common/locales/es';
registerLocaleData(localeEs);
// period data extras Eg: midnight, noon, morning, afternoon, evening, night
import { registerLocaleData } from '@angular/common';
import localeEs from '@angular/common/locales/es';
import localeEsExtra from '@angular/common/locales/extra/es';
registerLocaleData(localeEs, localeEsExtra);
// common usage using pre-defined format string
{{ d | date: 'shortDate' }} // en-US: 1/18/17
// specific locale
{{ d | date: 'shortDate': null: 'es' }} // es-ES: 18/1/17
// custom format string
{{ d | date: 'M/d/yy': null: 'es' }} // es-ES: 18/1/17
// number[:digitInfo[:locale]]
// n = 2.718281828459045
// digitInfo defaults to 1.0-3
{{ n | number }} // outputs: '2.718'
{{ n | number: '4.5-5' }} // outputs: '0,002.71828'
{{ n | number: '4.5-5': 'es' }} // outputs: '0.002,71828'
// currency[:currencyCode[:display[:digitInfo[:locale]]]]
// c = 1.3495
// display defaults to symbol
// digitInfo defaults to 1.2-2
{{ c | currency }} // outputs: '$1.35'
{{ c | currency: 'CAD' }} // outputs: 'CA$1.35'
{{ c | currency: 'CAD': 'code' }} // outputs: 'CAD1.35'
{{ c | currency: 'CAD': 'symbol-narrow': '1.2-2': 'es' }} // outputs: '1,35 $'
// percent[:digitInfo[:locale]]
// p = 0.718281828459045
// digitInfo defaults to 1.0-0
{{ p | percent }} // outputs: '72%'
{{ p | percent: '4.5-5' }} // outputs: '0,071.82818%'
{{ p | percent: '4.5-5': 'es' }} // outputs: '0.071,82818 %'
// without classes
http.get(url, { headers: {'X-Option': 'true'} });
http.get(url, { params: {'test': 'true'} });
// using classes
const headers = new HttpHeaders().set('X-Option', 'true');
http.get(url, { headers });
const params = new HttpParams().set('test', 'true');
http.get(url, { params });
// app.module.ts
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [HttpClientModule], ...
})
export class AppModule {}
// usersService.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import 'rxjs/add/operator/map';
@Injectable()
export class UsersService {
constructor(private http: HttpClient) { }
public get() {
return this.http.get(USERS_ENDPOINT)
.map(response => response.users);
}
}
{
"users": [
{
"id": 34,
"username": "spiderman",
"roles": ["admin", "user"],
"superuser": true
},
{
"id": 67,
"username": "batman",
"roles": ["user"]
}
]
}
// usersService.ts
interface User {
id: number;
username: string;
roles: Array<string>;
superuser?: boolean;
}
interface UsersResponse {
users: Array<User>;
}
export class UsersService {
public get() {
return this.http.get<UsersResponse>(USERS_ENDPOINT)
.map(response => response.users);
}
}
import { Component } from '@angular/core';
import { UsersService } from '../services/usersService';
@Component({
selector: 'users',
template: `<h1>Users</h1>
<tr *ngFor="let user of userslist | async">
<td>{{user.username}}</td>
</tr>`
})
export class Users {
private userslist;
constructor(users: UsersService) {
this.userslist = users.get();
}
}
// usersService.ts
@Injectable()
export class UsersService {
constructor(private http: HttpClient) { }
public post() {
const payload = {
userId: 1,
title: 'HttpClient',
body: 'In this article...',
};
return this.http.post('https://jsonplaceholder.typicode.com/posts', payload);
}
}
// users.service.ts
export class UsersService {
public post(payload) {
return new HttpRequest('POST', URL, payload, {
reportProgress: true,
})
}
}
// users.component.ts
usersService.post(payload).subscribe(event => {
if (event.type === HttpEventType.UploadProgress) {
const percentage = Math.round(event.loaded/event.total*100);
console.log(`${percentage}% uploaded.`);
} else if (event instanceof HttpResponse) {
console.log('Uploaded finished!');
};
});
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { BasicInterceptor } from './basic.interceptor.ts';
@NgModule({
providers: [{
multi: true, provide: HTTP_INTERCEPTORS, useClass: BasicInterceptor
}],
})
export class AppModule {}
// ./basic.interceptor.ts
import {Injectable} from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest }
from '@angular/common/http';
@Injectable()
export class BasicInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req);
}
}
// ./auth0.interceptor.ts
@Injectable()
export class Auth0Interceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>{
const clonedRequest = req.clone({
setHeaders: { authorization: localStorage.getItem('auth0IdToken') }
});
return next.handle(clonedRequest);
}
}
// ./logging.interceptor.ts
@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>{
return next.handle(req).do(event => {
if (event instanceof HttpResponse) {
const bodyLength = event.headers.get('Content-Length');
console.log(`Request: ${req.urlWithParams}-Response Size: ${bodyLength}`);
}
});
}
}
fadeIn
fadeOut
TRANSITIONS
STATE
STATE
fadeIn => fadeOut
fadeOut => fadeIn
fadeIn <=> fadeOut
void
*
void => * :enter
* => void :leave
void <=> *
STATE
STATE
sequence
group
time
animateChild
time
time
class="container"
class="item"
class="item"
query(selector)
query('.item')
class="container"
class="item"
class="item"
query('.item:enter')
class="container"
class="item"
class="item"
class="item"
added