There are no stupid questions,
only stupid answers
Errors, better way to do, idea ? Contribute !
Before 2003, the web was mostly...
Major browser version of Microsoft during 5 years !
And something change everything...
in 2004 ?
Supports Javascript | W3C | Standard API
Supports Javascript | W3C | Standard API
and wonderful tools for web developer
... so we have to adapt our website for each of them
... or we can use libraries to abstract complexity !
Javascript based
Support all browser with only one syntax
Javascript based
Support all browser with only one syntax
Javascript based
User Interface Library
Others prefers...
ActionScript executed in a plugin
From 1996 to 2016
(more or less...)
C#, .NET... executed in a plugin
From 2007 to 2011
JS generated from Java at compile-time !
Since 2006 and still here !
Since 2009, a lots of JS frameworks appear
Learning from previous generation mistakes...
... new frameworks rise
We have to built better, smarter and simpler application
We call it, Progressive-Web-App
Javascript is becoming the assembly language of the web
This is why we call it Angular
and this language is preferred (it simpler to use and learn)
Separate team, separate implementation, separate versions
So, a little example of Angular App
Demo time is generally when something goes wrong...
@Component
@NgModule
We will build the web-site of a book store...
... which is specialized into digital books about Angular
$ ng new <NAME_OF_PROJECT> --prefix bs --style sass
$ ng generate component <NAME_OF_ELEMENT>
$ ng g c <NAME_OF_ELEMENT>
Can be used to generate directive
, pipe
, service
, class
, interface
, enum
and module
$ ng serve
$ ng test
$ ng doc <SEARCH_WORD>
$ ng --help
All is about Component
EVERYWHERE !
<div> My title is {{ title }} </div>
We use standard HTML for property binding
<h3 [hidden]="isHidden" [class.active]="favoriteHero === hero" >
Your favorite hero is: {{ favoriteHero }}
</h3>
All the directive inducing a dom modification outside of the current element are called Structural Directive
<p *ngIf="condition">
condition is true so this block is shown.
</p>
<tr *ngFor="let movie of movies">{{movie.name}}</tr>
But Why ?
<template>
<div>A super template !</div>
</template>
This template isn't rendered by the browser but can be used
<p *ngIf="condition">
A super template !
</p>
<template [ngIf]="condition">
<p>
A super template !
</p>
</template>
So, the * is just a syntaxic sugar
And we can write our own Structural Directive !
We can define a template variable associated to a dom element
<video #player src="foo.mp4"></video>
<button (click)="player.play()"></button>
The brain of the component...
import { Component } from 'angular2/core';
@Component({
selector: 'todo',
template: `<h1>{{ title }}</h1>`
})
export class TodoComp {
title : String = 'A super presentation !!';
}
import {Component} from 'angular2/core';
import {MyService} from './myservice';
@Component({
selector: 'todo',
template: `<h1>{{ title }}</h1>`
})
export class TodoComp {
title : String = 'A super presentation !!';
constructor(private myService: MyService){} // DI by constructor
}
import { Component, OnInit } from 'angular2/core';
import { MyService } from './myservice';
@Component({
selector: 'todo',
template: `<h1>{{ title }}</h1> <emitter></emitter>`,
})
export class TodoComp implements OnInit {
title : String = 'A super presentation !!';
constructor(private myService: MyService){}
ngOnInit() {
console.log('The Component is initialised');
}
}
We have now a component, but we want to communicate with other element
import {Component, Input} from 'angular2/core';
@Component({
selector: 'counter'
template: `...`
})
export class CounterCmp {
@Input() counterValue = 0;
}
<counter [counterValue]="10"></counter>
import {Component, Input} from 'angular2/core';
@Component({
selector: 'counter'
template: `...`
})
export class CounterCmp {
@Input('start') counterValue = 0;
}
<counter [start]="10"></counter>
import {Component, Input} from 'angular2/core';
@Component({
selector: 'counter'
template: `...`
})
export class CounterCmp {
@Input('start') counterValue = 0;
@Output() stateEmitter = new EventEmitter<State>();
}
<counter [start]="10" (stateEmitter)="show($event)"></counter>
import {Component, Input} from 'angular2/core';
@Component({
selector: 'counter'
template: `...`
})
export class CounterCmp {
@Input('start') counterValue = 0;
@Output('finish') stateEmitter = new EventEmitter<State>();
finish() {
this.stateEmitter.emit(myState);
}
}
<counter [start]="10" (finish)="show($event)"></counter>
<input [(ngModel)]="foo.bar">
This syntax is a mix between property and event bindings
@Component({
selector: 'style-comp',
templateUrl: '<div class="my-comp">An encapsulated comp</div>',
styles: [`.my-comp { background: green; }`]
})
export class CssComp {}
@Component({
selector: 'style-comp',
templateUrl: '<div class="my-comp">An encapsulated comp</div>',
styleUrls: ['./css.component.css']
})
export class CssComp {}
We can encapsulate everything
and we can do it by multiple way
import {ViewEncapsulation} from 'angular2/core';
@Component({
selector: 'style-comp',
templateUrl: '<div class="my-comp">An encapsulated comp</div>',
styles: [`.my-comp { background: green; }`],
encapsulation: ViewEncapsulation.Emulated // Default behaviour
})
export class CssComp {}
Which is the default encapsulation system
<head>
<style>.my-comp[_ngcontent-1] {background: green;}</style>
</head>
<style-comp _ngcontent-0 _nghost-1>
<div class="my-comp" _ngcontent-1>
An encapsulated comp
</div>
</style-comp>
Which uses the Shadow-DOM
encapsulation: ViewEncapsulation.Native
<style-comp>
#shadow-root
| <style>
| .my-comp { background: green; }
| </style>
| <div class="my-comp">An encapsulated comp</div>
</style-comp>
Take care of Side effect !
encapsulation: ViewEncapsulation.None
<head>
<style>.my-comp {background: green;}</style>
</head>
<style-comp _nghost-1>
<div class="my-comp">
An encapsulated comp
</div>
</style-comp>
The house of a feature, Unit of compilation
Functionality independent in access and logic, like
The NgModule will be the entry-point of a lib in Angular
NgModule property
@NgModule({
declarations: [],
providers: [],
entryComponents: [],
bootstrap: [],
imports: [],
exports: [],
schemas:[]
})
export class AppModule { }
NgModule sample
@NgModule({
declarations: [
AppComponent, FlavorsComponent
],
imports: [
/* Main */ BrowserModule, FormsModule, HttpModule,
/* Routes */ AppRoutesModules,
/* Shared */ SharedModule,
/* Features */ OrderModule
],
providers: [],
bootstrap: [ AppComponent ]
})
export class AppModule { }
NgModule
Declarations
@NgModule({
declarations: [ AppComponent, FlavorsComponent ]
})
export class DeclarationModule{}
Declare all element available in this module for Compilation
NgModule
Providers
@NgModule({
providers: [ FooService ]
})
export class ProviderModule{}
It allows to define the list of element available for Dependency Injection (DI)
NgModule
EntryComponents
@NgModule({
entryComponents: [ AppComponent ]
})
export class EntryComponentsModule{}
Specifies a list of components that should be compiled when this module is defined
NgModule
Bootstrap
@NgModule({
bootstrap: [ AppComponent ]
})
export class BootstrapModule{}
Defines the components that should be bootstrapped when this module is bootstrapped
They will be added automatically to EntryComponents
NgModule
Imports & Exports
@NgModule({
exports: [ ASpecialComponent ]
declarations: [ AnoTSoSpecialComponent ]
})
export class ExportsModule{}
@NgModule({
imports: [ ExportsModule ]
})
export class ImportsModule{}
Allow to import all exported element from another NgModule
NgModule
Schemas
@NgModule({
schemas: [ NO_ERRORS_SCHEMA ]
})
export class BootstrapModule{}
Allow to configure the behavior of the Angular Compiler (SchemaMetadata)
NO_ERRORS_SCHEMA
: totally lax CUSTOM_ELEMENTS_SCHEMA
: only custom elementsCommonModule
@NgModule({
declarations: [COMMON_DIRECTIVES, COMMON_PIPES],
exports: [COMMON_DIRECTIVES, COMMON_PIPES],
providers: [
{provide: NgLocalization, useClass: NgLocaleLocalization},
],
})
class CommonModule {}
The module that includes all the basic Angular directives like NgIf, NgFor, ...
BrowserModule
@NgModule({
providers: [
BROWSER_SANITIZATION_PROVIDERS, {provide: ErrorHandler, useFactory: errorHandler, deps: []},
{provide: DOCUMENT, useFactory: _document, deps: []},
{provide: EVENT_MANAGER_PLUGINS, useClass: DomEventsPlugin, multi: true},
{provide: EVENT_MANAGER_PLUGINS, useClass: KeyEventsPlugin, multi: true},
{provide: EVENT_MANAGER_PLUGINS, useClass: HammerGesturesPlugin, multi: true},
{provide: HAMMER_GESTURE_CONFIG, useClass: HammerGestureConfig},
{provide: DomRootRenderer, useClass: DomRootRenderer_},
{provide: RootRenderer, useExisting: DomRootRenderer},
{provide: SharedStylesHost, useExisting: DomSharedStylesHost},
{provide: AnimationDriver, useFactory: _resolveDefaultAnimationDriver}, DomSharedStylesHost,
Testability, EventManager, ELEMENT_PROBE_PROVIDERS, Title
],
exports: [CommonModule, ApplicationModule]
})
class BrowserModule {}
The ng module for the browser which exports CommonModule
FormsModule
@NgModule({
declarations: TEMPLATE_DRIVEN_DIRECTIVES,
providers: [RadioControlRegistry],
exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES]
})
class FormsModule {}
All element needed for form declaration in HTML
ReactiveFormsModule
@NgModule({
declarations: [REACTIVE_DRIVEN_DIRECTIVES],
providers: [FormBuilder, RadioControlRegistry],
exports: [InternalFormsSharedModule, REACTIVE_DRIVEN_DIRECTIVES]
})
class ReactiveFormsModule {}
All element needed for form declaration in code-style
And use DI everywhere !
@Injectable
export class MyService {}
import {MyService} from './my.service.ts';
...
providers : [ MyService ]
import {Component} from 'angular2/core';
import {MyService} from './my.service.ts';
@Component({...})
export class TodoComp {
constructor(private myService: MyService){}
}
providers : [
MyService // TypeProvider
{ provide: 'withValue', useValue: 'Foo !' } // ValueProvider
{ provide: 'withClass', useClass: MyService } // ClassProvider
{ provide: 'aliasing', useExisting: MyService } // ExistingProvider
{ provide: 'withFactory',
useFactory: (l: string) => l.split(' ')[0],
deps: ['withValue']
} // FactoryProvider
]
import {Component, Inject} from 'angular2/core';
import {MyService} from './my.service.ts';
@Component({...})
export class TodoComp {
constructor(@Inject('withClass') private myService: MyService){}
}
Like every platform, it should have solid basis
Angular has 3 elements as basis
Zones are simply an Execution context for Javascript
[Available outside Angular for any JS app]
[Proposed as official spec for Javascript]
Dependency Injection system
[Available outside Angular for any JS app]
Lazy event stream
[Available outside Angular for any JS app]
Zones are simply an Execution context for Javascript
Zones are simply an Execution context for Javascript
It helps to handle asynchronous code in JS
Extract from Dart Lang, alternative to Domains in NodeJS
let cf = (name) => () => console.log(name);
var a = cf('a'), b = cf('b'), c = cf('c'), d = cf('d');
a();
setTimeout(b, 0);
setTimeout(c, 0);
d();
Results to
a
d
b
c
All the async tasks are added to the event queue and executed when other tasks are finished
start();
a();
setTimeout(b, 0);
setTimeout(c, 0);
d();
stop();
Results :
// Start
a
d
// Stop
b // Missed
c // Missed
“Observables are lazy event streams which can emit zero or more events, and may or may not finish.”
An Observable is a structure highly functional
foo.bar$ // An Observable
.filter(v => v.length > 0)
.map(v => removeSpaces(v))
.map(v => v.toUpperCase())
.subscribe(
v => $scope.result = v /* An Observer */
);
Yes, and it's very fun !
Observable are immutable, composable and reusable
let obs$ = Observable.from(1, 2, 3, 4, 5);
obs$.subscribe(x => console.log(x)); // 1, 2, 3, 4, 5
let evenObs$ = obs$.filter(x => x % 2 == 0 ));
evenObs$.subscribe(x => console.log(x)) // 2, 4
evenObs$.map(x => x*2).subscribe(x => console.log(x)) // 4, 8
The injector of Angular is inspirited by Dagger
Injector
@NgModule
Each element in provider
of @NgModule
will be a singleton available in all loaded element
@Component
A component has it's own provider
which allow to have singleton between the current element and its children
@NgModule
An async @NgModule
always have its own injector with every elements of providers
as new singleton !
import { RouterModule } from '@angular/router';
But we need to register it with a parameter...
const routes: Routes = [
{ path: 'foo', component: FooComp },
{ path: 'others', component: OthersComp,
data: { title: 'Others List' } },
{ path: '', redirectTo: '/foo', pathMatch: 'full' },
{ path: '**', component: PageNotFoundComponent }
];
@NgModule({
imports: [
RouterModule.forRoot(appRoutes)
...
],
...
})
export class RouteModule {}
<header> ... </header>
<main>
<router-outlet></router-outlet>
</main>
<footer> ... </footer>
We can now create multi level routes and share page context
const routes: Routes = [
{ path: 'users', component: UsersComp
children : [
{ path: '', component: UsersListComp },
{ path: ':id', component: UsersDetailComp },
]
},
];
router-outlet
<!-- UsersComp Template -->
<user-module-header></user-module-header>
<router-outlet></router-outlet>
<user-module-footer></user-module-footer>
<!-- UsersListComp Template -->
<ul>
<li></li>
<li></li>
</ul>
router-outlet
<header> ... </header>
<main>
<router-outlet>
<user-module-header></user-module-header>
<router-outlet>
<ul>
<li></li>
<li></li>
</ul>
</router-outlet>
<user-module-footer></user-module-footer>
</router-outlet>
</main>
<footer> ... </footer>
In HTML, like a simple <a>
<a routerLink="/users">Users</a>
<a [routerLink]="['/users']">Users</a>
<a [routerLink]="['/users', 1]">User #1</a>
<a [routerLink]="['/users', 1, 'stats']">User #1 stats</a>
<a [routerLink]="['/users', 1, {foo:'bar'}]">User #1 with params</a>
We can use the component logic in typscript to navigate
constructor(private router: Router) {}
redirectTo(user: User) {
this.router.navigate(['/users', user.id]);
}
Router Service documentation
Component
We can access the current route by DI in our component
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
this.route.data
.map(d => <any>d.flavors)
.subscribe(f => this.flavors = f);
}
ActivatedRoute documentation
ActivatedRoute
ActivatedRoute
ActivatedRoute
From the first router-outlet to the last component leaf
Use Observable
to handle modification
interface ActivatedRoute {
url : Observable<UrlSegment[]>
params : Observable<Params>
queryParams : Observable<Params>
fragment : Observable<string>
data : Observable<Data>
paramMap : Observable<ParamMap>
queryParamMap : Observable<ParamMap>
...
}
So, moving to another URL with the same component doesn't trigger a destroy & construct...
... it just pushes a new value into the Observable
Allow access control over specific route
const routes: Routes = [
path: 'users',
canLoad: [CanLoadUsers],
component: UsersListComp
];
@Injectable()
class CanLoadUsers implements CanLoad {
constructor(private permissions: Permissions, private currentUser: User) {}
canLoad(route: Route): boolean {
if (route.path === "contacts") {
return this.permissions.canLoadUsers(this.currentUser);
} else {
return false;
}
}
}
The CanLoad
method can return different value
interface CanLoad {
canLoad(route: Route): Observable<boolean>|Promise<boolean>|boolean
}
Allow to fetch data before the component init itself
@Injectable()
export class FooResolver implements Resolve<Foo> {
constructor(private fooService: FooService) { }
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Foo> {
return this.fooService.findOne(+route.params.id);
}
}
We define a resolver for a specific route
export const routes: Routes = [
{
path : 'foo', component: FooComponent,
resolve: {
foos: FooResolver
}
}
];
And we can access its value like this
@Component({ ... })
export class FooComponent implements OnInit {
foos: Array<Foo>
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
this.route.data
.map(d => d.foos)
.subscribe(f => this.foos = f);
}
}
Http
ServiceTo communicate with 'outside' world, we only have the Http
service
So, no WebSocket nor Server-Sent Event for now...
Http
ServiceThis element should be imported from this own module HttpModule
import {HttpModule} from '@angular/http';
@NgModule({
declarations: [...],
imports: [ HttpModule ],
providers: [...]
})
export class FeatureModule { }
import {Http} from '@angular/http';
@Injectable()
export class BookService {
constructor(private http: Http) { }
}
class Http {
constructor(_backend: ConnectionBackend, _defaultOptions: RequestOptions)
request(url: string|Request, options?: RequestOptionsArgs) : Observable<Response>
get(url: string, options?: RequestOptionsArgs) : Observable<Response>
post(url: string, body: any, options?: RequestOptionsArgs) : Observable<Response>
put(url: string, body: any, options?: RequestOptionsArgs) : Observable<Response>
delete(url: string, options?: RequestOptionsArgs) : Observable<Response>
patch(url: string, body: any, options?: RequestOptionsArgs) : Observable<Response>
head(url: string, options?: RequestOptionsArgs) : Observable<Response>
options(url: string, options?: RequestOptionsArgs) : Observable<Response>
}
GET
request in a Service
import {Http} from '@angular/http';
import 'rxjs/add/operator/map'
@Injectable()
class FooService {
constructor(private http: Http) {}
findOne(): Observable<Foo> {
return this.http.get('/a/fake/url/which/returns/foo/in/json')
.map(res => res.json())
}
}
import {FooService} from './foo.service.ts';
@Component({...})
class FooComponent implements OnInit {
foo: Foo;
constructor(private fooService: FooService) {}
ngOnInit() {
this.fooService.findAll()
.subscribe(f => this.foo = f);
}
}
Thanks to DI, Zones and amazing testing framework
describe("What I globally test", () => {
it("should do something", () => {
const value = 12;
expect(value).toEqual(12);
});
it("should do something else", () => {
expect(2**4).toEqual(16);
});
});
beforeEach | afterEach
describe("What I globally test", () => {
let testValue: number;
beforeEach(() => {
testValue = generateNumber();
});
it("should do something..", () => { .. });
afterEach(() => { .. });
});
beforeAll | afterAll
describe("What I globally test", () => {
beforeAll(() => { .. });
it("should do something..", () => { .. });
afterAll(() => { .. });
});
describe
describe("What I globally test", () => {
beforeEach(() => { .. });
describe("sub scrope test", () => {
beforeEach(() => { .. });
it("should do one thing", () => { .. });
it("should do another thing", () => { .. });
});
describe("another sub scrope test", () => {
beforeEach(() => { .. });
it("should do different thing", () => { .. });
it("should do an original thing", () => { .. });
});
});
expect
-ation
it("should check the equality", () => {
expect(6*2).toEqual(12); //True
});
expect
and matchers
it("should check the equality", () => {
expect({foo: 'bar'}).toEqual({foo: 'bar'}); // True
});
it("should do a check equality", () => {
expect({foo: 'bar'}).toBe({foo: 'bar'}); // False
});
it("...", () => {
expect(true).toBeTruthy();
expect(false).toBeFalsy();
expect(windows).toBeDefined();
expect(null).toBeNull();
expect(pi).toBeCloseTo(3.1, 2);
expect(['foo', 'bar']).toContain('foo');
expect('01-02-03-04-05').toMatch(/\d{2}-\d{2}-\d{2}-\d{2}-\d{2}/);
expect(6).toBeGreaterThan(5);
expect(5).toBeLessThan(6);
expect(aFunctionWhichThrow).toThrow(new Error("Unexpected error!"));
});
it("...", () => {
expect('336$').not.toMatch(/\d{3}€/);
});
let worldSimplertMock = { findOne: () => { .. } };
let jasmineMock = jasmine.createSpyObj('name',['findOne','reset']);
let jasmineMock = jasmine.createSpyObj('name',['findOne','reset']);
...
expect(jasmineMock.findOne).toHaveBeenCalled();
expect(jasmineMock.findOne).toHaveBeenCalledWith(12);
@NgModule({
declarations: [ ComponentToTest ]
providers: [ MyService ]
})
class AppModule { }
TestBed.configureTestingModule({
declarations: [ ComponentToTest ],
providers: [ MyService ]
});
// Get instance during a BeforeEach call
let service = TestBed.get(MyService);
TestBed.configureTestingModule({
declarations: [ ComponentToTest ],
providers: [ MyService ]
});
it('should return ...', inject([MyService], service => {
service.foo();
}));
describe('Service: LanguagesService', () => {
beforeEach(() => TestBed.configureTestingModule({
providers: [ LanguagesService ]
}));
it('should return languages', inject([LanguagesService], svc => {
expect(svc.get()).toContain('en');
}));
})
let fixture: ComponentFixture<AppComponent> // Full component
let component: AppComponent; // Pure Component instance
let el: DebugElement; // DOM element with helper method
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [AppComponent]
}).compileComponents()
});
beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
el = fixture.debugElement;
});
abstract class ComponentFixture {
debugElement; // test helper
componentInstance; // to access properties and methods
nativeElement; // to access raw DOM element
debugElement; // to access DOM element with helper
detectChanges(); // trigger component change detection
autoDetectChanges(); // change the behavior of the
// component change detection
whenStable(): Promise // return a resolved promise
// when all changes are done
}
class DebugElement extends DebugNode {
properties : {[key: string]: any}
attributes : {[key: string]: string}
query(predicate: Predicate<DebugElement>) : DebugElement
queryAll(predicate: Predicate<DebugElement>) : DebugElement[]
triggerEventHandler(eventName: string, eventObj: any)
}
it('should render `Hello World!`', async(() => {
greeter.name = 'World';
//trigger change detection
fixture.detectChanges();
fixture.whenStable().then(() => {
// Allow to wait this resolution
expect(element.querySelector('h1').innerText)
.toBe('Hello World!');
expect(de.query(By.css('h1')).nativeElement.innerText)
.toBe('Hello World!');
});
}));
fakeAsync()
@Component({..})
export class WelcomeComponent implements OnInit {
greetings: string;
ngOnInit() {
setTimeout(() => this.greetings = 'Hello there!', 3000);
}
}
fakeAsync()
it('should have a greeting message', fakeAsync(() => {
fixture.detectChanges();
tick(3000);
fixture.detectChanges();
expect(element.querySelector('p').textContent)
.toBe('Hello there!');
}));
flush() (since 4.2)
it('should have a greeting message', fakeAsync(() => {
fixture.detectChanges();
flush();
fixture.detectChanges();
expect(element.querySelector('p').textContent;)
.toBe('Hello there!');
}));