Angular CLI = Installation - **Npm** - package manager ```bash= npm install -g npm ``` **NodeJS** - Server side framework https://nodejs.org/en/ **ng-cli** - Angular CLI prompt ```bash= sudo npm uninstall -g angular-cli @angular/cli npm cache clean sudo npm install -g @angular/cli ``` **Bootstrap** - CSS framework Install inside the project's folder ```bash= npm install --save bootstrap@3 ``` Add the bootstrap’s path to .angular-cli.json `../node_modules/bootstrap/dist/css/bootstrap.min.css` This does 2 things: add the import to the html head Angular launches the & app via bootstrap app.module. Debugging - **Inspect** - `CTRL + SHIFT + I` Pause debugger to get values **Sources** Typescript is not running at the browser, Javascript is! we need to access Typescript in order to debug. - Pause in debugger and check for values `sources > webpack > src > app …` **Augury chrome extension:** - App analyzer - Component properties - Component hierarchy graph Project handling - **Create new project** ```bash= ng new project-name cd project-name ng serve --port=4200 #localhost:4200 --poll=2000 # enables files watching in ms ``` **create new component** ```bash= ng generate component component-name ng g c component-name ``` **create a nested component** ```bash= ng g c auth/signup --spec false # to disable generation of component’s test file ``` **Optimize the app** Angular built in tool for production ```bash= ng build --prod --aot ``` Ahead of Time Compilation (compiled on server instead on browser's accessing, part of the process is compiling the HTML files into JS since it is faster to access JS then DOM) ++Running an imported project fails (ng serve)?++ You seem to not be depending on "@angular/core". This is an error. inside project folder run: `npm install` <hr> <hr> <hr> Angular Basics = General structure - #### String interpolation `{{ serverId }}` #### Ng-Model Connects components together. #### Template 1. Short HTML snippet (wrap with tilda parenthesis for multiple rows). ```htmlmixed template: '<p>Hello</p>'` ``` 2. Connecting to HTML file. ```htmlmixed templateUrl: './app.component.html' ``` ### Attribute ```typescript= // `<app-server></app-server>` selector: 'app-server' // `<div class="app-server">` selector: '[app-server]' // dot (.) like in css selector: '.app-server' ``` #### Data Binding Button disabled dynamically according to allowButton property ```htmlmixed= <button class="button" [disabled]="allowButton"> Add Server </button> ``` #### Event binding Button onclick ```htmlmixed= <button class="button" (click)="changePrint()"> {{ printProperty }} </button> ``` Examples: The MDN (Mozilla Developer Network) offers nice lists of all properties and events of the element you're interested in. Googling for YOUR_ELEMENT properties or YOUR_ELEMENT events should yield nice results. #### Two way binding React to changes in different location on code by listening on events 1. Import formsModule to enable it. 2. Add #### Two Way ```htmlmixed= <input type="text" class="form-control" [( ngModel )] = "serverName"> ``` #### One way ```htmlmixed= <input type="text" class="form-control" (input)= "onUpdate($event)"> <p>{{ serverName }}</p> ``` 3. Add Listener function ```typescript= onUpdate(event: Event){ //Event is TS type this.serverName = (<HTMLInputElement>.event.target).value; } ``` * Communication between components? * Create a Shared folder - for sharing elements between features. (shoppingList & Recipe) <hr> Syntax - #### Typescript typing With (but not nessesary before TS infers it) `serverID: number = 17` #### Esx function ```typescript= SetTimeout( funcion, ms) setTimeout( () ==> { this.property = true; }, 2000); ``` #### Array ```typescript= servers: ['serverA', 'serverB']; // add to array this.servers.push('name') // get - shallow copy this.servers.slice('name') ``` #### Date `new Date()` #### Operators `=== is like ==` except it doesn't accept type conversion `true == 1` but `true (!) === 1` <hr> CSS Encapsulation - #### View encapsulation enable/disable css attribute on other components. add to the component decodator @ `encapsulation:ViewEncapsulation.None' child css is globally now `encapsulation:ViewEncapsulation.Emulated' encapsulated <hr> Models - Vanilla TypeScript class ```typescript= export class Recipe{} ``` - object - can be instanceiated - can make array of objects `Recipe[]` #### Two ways to construct a model (will do the same) ```typescript= export class Ingredient { public name: string; public amount: number; constructor(name: string, amount: number){ this.name = name; this.amount = amount; } } ``` or ```typescript= export class Ingredient { constructor(public name: string,public amount: number){} } ``` Pipes - * Find all options in https://angular.io/api?query=pipe A way to write display-value transformations that you can declare in your HTML. Outputs a property in a different way without changing its value. ```htmlmixed // Max --> MAX {{ username | uppercase }} // Mon Aug 08 1920 00:00:00 GMT +0200 (CEST) --> Aug 8, 1920 {{ server.started | date }} ``` #### Configure a pipe By adding a colon with a value in camelCase (multiple attributes can be added in a chain colons, pipes can be chained also with the pipe symbol `|` in between) ```htmlmixed {{ server.started | date:'fullDate' }} ``` * Order is important #### Create new pipe (with configureable parameter) ```shell ng g pipe ``` ```typescript= import { Pipe ,PipeTransform } from '@angular/core'; @Pipe({ name: 'shorten' }) export class ShortenPipe implements PipeTransform { transform(value: any, limit: number) { return value.substr(0,limit); } } ``` ```htmlmixed {{ name | shorten:5 }} ``` * Pure & Impure pipes Updating a filter on every change, but data is not updated every time, this saves us usage. (video #233) * Async pipes - changes the output on async `{{ status | async }}` * Sort pipe ```typescript= transform(value: any, propName: string): any { return value.sort((a, b) => { if(a[propName] > b[propName]) return 1; else return -1; }); } ``` <hr> Deployment on server = * Optimize the app * Angular built in tool for production - minifies code. * Ahead of Time Compilation (compiled on server instead on browser's accessing, part of the process is compiling the HTML files into JS since it is faster to access JS then DOM) `ng build --prod --aot` * Set the correct <base> element for `example.com/my-app`, configure `<base href="/my-app/">` or with the CLI `ng build --prod --aot --base-href /my-app/` * Make sure the server always returns `index.html` In case of an error the server doesn't know the routes, returning `index.html` lets angular the chance to take over and return a built-in error page. * On AWS console (free for 12 month) upload `dist/*` (build output files). Now just enter the URL given in Static website hosting. ___ Components = A section in the app, that we can reuse in the app or in other apps. Binding components - * Exposing component property to the a **Parent** component (in JS a property is accesible only from inside the class) * Add a decorator ```typescript= @Input() element: { type: string, name: string, content: string } ``` And import `Input` from angular/core Inside the Parent component: On Parent HTML: element is the property name of the child component ```htmlmixed= <app-server-element *ngFor="let serverElement of serverElements" [element]="serverElement" ></app-server-element> ``` ```typescript= // binds element component to the name srvElement // (to be used outside the app) @Input('srvElement') element:... ``` * listen to a object creation on Parent component (when function that creates in the child it is called, implement it in the parent function done after its done in child) * In child.ts - pass `serverCreated` to be listened from Parent, event emitter - create events, of course import output and emitter, in the <> specify a type. ```typescript= @Output() serverCreated = new EventEmitter<{ServerName: string, serverContent: string}>(); @Output() blueprintCreated = new EventEmitter<{ServerName: string, serverContent: string}>(); newServerName = ''; newServerContent = ''; constructor() { } ngOnInit() { } onAddServer() { this.serverCreated.emit({ ServerName: this.newServerName, serverContent: this.newServerContent, }); } ``` in Parent.ts - get `serverData`, a js object from `$event` and add to array ```typescript= onServerAdded(serverData: {ServerName: string, serverContent: string}) { this.serverElements.push({ type: 'server', name: serverData.ServerName, content: serverData.serverContent }); } onBlueprintAdded(blueprintDate: {ServerName: string, serverContent: string}) { this.serverElements.push({ type: 'blueprint', name: blueprintDate.ServerName, content: blueprintDate.serverContent }); } ``` parent HTML - listen on: `serverCreated`, run method `onServerAdded`, get the object binding `$event` ```htmlmixed= <app-cockpit (serverCreated)="onServerAdded($event)" (blueprintCreated)="onBlueprintAdded($event)" ></app-cockpit> ``` Other ways to data bind: 1. Local reference: substance to databinding - eliminate the need for property. get access to a element in the template and use it or pass it to typescript code 2. Projecting content into a component Extract component's code to parent (for reusabillity). inside component template replace this ```htmlmixed= <p> <strong *ngIf="element.type === 'server'" style="color: red"> {{ element.content }}</strong> <em *ngIf="element.type === 'blueprint'"> {{ element.content }}</em> </p> ``` with `<ng-content></ng-content>` to enable component code hooking inside it's declaration (`<server-element>HERE</server-element>`) ___ Directives = Structure directives (with *ngDirectiveName) - * cannot make multiple structure directive on one HTML element #### ngIf ```htmlmixed <p *ngIf="serverCreated"> Server created!</p> // for multiple elements use ngIf without the * <ng-template [ngIf]="!authService.isAuthenticated()"> <li><a routerLink="/signup">Register</a></li> <li><a routerLink="/signin">Login</a></li> </ng-template> ``` If-else ```htmlmixed= <p *ngIf="serverCreated"; else noServer> Server created!</p> <ng-template #noServer> <p> No server </p> </ng-template> ``` #### ngFor ```htmlmixed <servers-app *ngFor="let server of servers"> </servers-app> ``` get iteration index ```htmlmixed= let server of servers; let i=index ``` #### ngSwitch Great solution to replace a bunch of `ngIf`s <hr> Attribute directives - #### ngStyle We will use property binding on this directive ```htmlmixed <p [ngStyle]="{backgroundColor: getColor()}">{{ server }} </p> //camelCase ``` ```htmlmixed= <p [ngStyle]="{'background-color: getColor()'}"> {{ server }} </p>//CSS style GetColor(){ Return this.status === 'online ? 'green' : 'red'; } ``` **ngClass** dynamically add CSS class. create it on component and condition it in html. in order for this to work in ngFor, condition ngClass with object's field and not entire component. (ass3) > component.ts ```typescript= styles: [' .online{ color: white;} '] ``` > component.html ```htmlmixed= <p [ngClass]="{online: serverStatus === 'online'}"> Server </p> ``` <hr> ### Creating new Directives * Manually or with `ng g directive 'name'` * better creating it with renderer * More actions beside setStyle - https://angular.io/api/core/Renderer2 * Creating a structural directive is also possible - see on course 1. create the folder & file (as in image) 2. create the class ```typescript= import { Directive, ElementRef, OnInit } from "@angular/core"; @Directive({ selector: '[appBasicHighlight]' }) export class BasicHighlightDirective implements OnInit { constructor(private elementRef: ElementRef){ } ngOnInit() { this.elementRef.nativeElement.style.backgroundColor = 'green'; } } ``` or with render: ```typescript= constructor(private elRef: ElementRef, private renderer: Renderer2) { } ngOnInit(){ this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'pink') } ``` Listen to host events: `mouseenter/mouseleave` is the event name ```typescript= @HostListener('mouseenter') mousehover(eventData: Event){ this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'lightGreen') } @HostListener('mouseleave') mouseleave(eventData: Event){ this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'pink') } ``` Bind to host events - *Stronger*: bind the event `style.backgroundColor` to the element property (on the right). must set an initial value to avoid error. ```typescript= export class BetterHighlightDirective implements OnInit{ @HostBinding('style.backgroundBolor') backgroundColor: string = 'transparent'; constructor(private elRef: ElementRef, private renderer: Renderer2) { } @HostListener('mouseenter') mousehover(eventData: Event){ this.backgroundColor = 'lightGreen'; } @HostListener('mouseleave') mouseleave(eventData: Event){ this.backgroundColor = 'ping'; } } ``` Adding private is a TS shourcut for make this element a property of the class and automatically assign the value given on constructions Also, can add a CSS class by listening to click (on course) 3. add to app declaration - just like with components ```typescript= import { BasicHighlightDirective } from './basic-highlight/basic-highlight.directive'; @NgModule({ declarations: [ AppComponent, BasicHighlightDirective, ``` 4. use it: ```htmlmixed= <p appBasicHighlight> Style me! </p> ``` ___ Elements = #### Button * type button: The button is a clickable button submit: The button is a submit button (submits form-data) reset: The button is a reset button (resets the form-data to its initial values) #### Lifecycle hooks: 1. ngOnChanges - called when binded object is changed 2. ngOnInit - called when object is instantiated 3. ngDoChange - called on each change detection run (in development mode, called twice on object instantiation) 4. ngAfterContentInit - called only once, when ng-content is is projected 5. ngAfterContentChecked - called after each change detection cycle 6. ngAfterViewInit - like the above 7. ngAfterViewChecked - like the above 8. ngOnDestroy - called when object is deleted. * They all should be imported from core and implemented by the component ```typescript= export class ServerElementComponent implements OnInit, OnDestroy { ... ``` ```typescript= import { Component, OnInit, OnDestroy } from '@angular/core'; ``` * To access DOM elements with @ViewChild, we can see the content only after (6) was called * To access DOM content with @ContentChild, we can see the content only after (4) was called * setInterval() - to perform an action on time intervals. ___ Services = * Optimize the app when multiple components use pretty much the same service. * Do it by injecting the service to the component * Hierarchical Injection - if we provide in: `AppModule`: same instance of service is available Application-wide `AppComponent`: available for all components (but not for other services) `AnyOtherComponent`:` only for him To use it in the hierarchical way - provide only on Parent module but keep constructor on childs. * Handle users for example - holds database on actions on it --- 1. The service - plain Javascript class that contain function that serves ``` export class LoggingService{ logStatusChange(status: string){ console.log("status has changed" + status); } } ``` 2. Instantiate the service - get Angular to do this for us. ``` @Component({ providers: [LoggingService] }) export class AppComponent{ constructor(private loggingService: LoggingService) {} onSomeFunction{ this.loggingService.logStatusChange('status changed'); } } ``` 3. add to app.module declarations (like components) --- * Inject service to a service Add a decorator to the service `@Injectable()` * Communication between components through a service (Listening to events) On the Service ```typescript= statusUpdated = new EventEmitter<string>(); ``` Emit on component A ```typescript= this.accountService.statusUpdated.emit(status); ``` Listen on Component B ```typescript= this.accountService.statusUpdate.subscribe( (status: string) => alert('New Status' + status) ); ``` ___ Models = Vanilla TypeScript class ```typescript= export class Recipe{} ``` - object - can be instanceiated - can make array of objects `Recipe[]` #### Two ways to construct a model (will do the same) ```typescript= export class Ingredient { public name: string; public amount: number; constructor(name: string, amount: number){ this.name = name; this.amount = amount; } } ``` or ```typescript= export class Ingredient { constructor(public name: string,public amount: number){} } ``` ___ Routes = > app.module.ts ```typescript= import { Routes } from '@angular/router'; // declare routes const appRoutes: Routes =[ { path: '', component: HomeComponent } { path: 'users', component: UsersComponent } { path: 'servers', component: ServersComponent } //in case we want the opening page to redirect to servers - we will do: { path: '', redirectTo: '/servers', pathMatch: 'full' } // pathMatch must be added or else this cause an error: // because '' matches every route ] // register the routes to our angular app imports: [ RouterModule.forRoot(appRoutes) ] ``` when `localhost:4200/users` reached, `UserComponent` is called this register the routes to our angular app > template.html ```htmlmixed= <li role="presentation"> <a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" > Home </a> </li> <li role="presentation"> <a routerLink="/users" routerLinkActive="active" > Users </a> </li> <li role="presentation"> <a [routerLink]="['/users','10','Anna']" // `/users/10/Anna` routerLinkActive="active" > Users </a> </li> <router-outlet> </router-outlet> ``` `routerLink` - Avoid reloading on each route activation `routerLinkActive` - Mark as active only when contains this path `routerLinkActiveOptions` - only at this exact path `/` mark as active Trigger route from within our code - load a route automatically on button click - 1. Inject router into the component 2. Call a function from the button ```htmlmixed= <button class="btn btn-primary" (click)="onLoadServers"> Load Servers </button> ``` ```typescript= import { Router, ActivatedRoute } from `@angular/router`; constructor( private router: Router private route: ActivatedRoute) { } onLoadServers() { this.router.navigate(['/servers']); // absolute path. navigate takes an array of elements in the path this.router.navigate(['servers'], {relativeTo: this.route}); // let navigate know on what route it is currently in // reload the page // results with /servers/servers } ``` Passing parameter to Routes - > app.module ```typescript= { path: 'users/:id/:name', component: UsersComponent } // dynamic id, name ``` > app.component.ts ```typescript= ngOnInit() { this.user = { id: this.route.snapshot.params[id], name: this.route.snapshot.params[name] // retrieve id and name into component dynamically from route path } // Or with + to convert string from route to number const id =+this.route.snapshot.params['id']; this.user = this.UserService.getUser(id); // observer - deal with asynchronous events and // be fired whenever new data is sent to this route // not executed on ngOnInit this.route.params.subscribe( (params: Params) => { this.user.id = params['id']; this.user.name = params['name']; } ); } ``` * If we add our own observer --> we need to unsubscribe via onDistroy (on course) Query parameters, Fragment (Pass & retrieve) - `localhost:4200/users/1/Max?mode=editing#loading` Route to an edit page and decide if allowed ### Pass ```typescript { path: 'servers/:id/edit', component: EditServerComponent } ``` ```htmlmixed <a [routerLink]="['/servers', server.id,'edit']" [queryParams]="{allowEdit: '1'}" fragment="loading" routerLinkActive="active" > Edit Server </a> </li> ``` ### Retrieve > app.component.ts > ```typescript this.route.snapshot.queryParams; this.route.queryParams.subscribe(); ``` ### Nested Router load menu next to current menu ```typescript= const appRoutes: Routes =[ { path: 'servers', component: ServersComponent, children: [ { path: '/:id', component: ServersComponent } { path: '/:id/edit', component: EditServersComponent } ] } ] ``` ```htmlmixed= <router-outlet></router-outlet> // instead of app-servers, app-servers-edit components ``` Wildcard Route - For Routes we don't have - 404/Redirect page ```typescript= { path: 'page-not-found', component: PageNotFoundComponent } { path: '**', redirectTo: '/page-not-found' } ``` `**` always put last in Routes Cleaner code - Creatring a router mdoule and remove routing from app.module > app.module ```typescript= import { AppRoutingModule } from './app-routing.module.ts'; imports: [ AppRoutingModule ] ``` > app-routing.module.ts ```typescript= import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import {HomeComponent } from '/home/home.component'; // import all Components const appRoutes: Routes =[ { path: '', component: HomeComponent } { path: 'users', component: UsersComponent } { path: 'servers', component: ServersComponent } ] @NgModule({ imports: [ RouterModule.forRoot(appRoutes) ], exports: [RouterModule] }) export class AppRoutingModule { } ``` Guards - Enables access to routes only for logined users > auth-guard.service.ts (dont forget importing to `app.module.ts`) ```typescript= import { CanActivate, ActivatedRouterSnapshot, RouterStateSanpshot } from `@angular/core`; import { AuthService } from './auth.service'; @Injectable() export class AuthGuard implements CanActivate, CanActivateChild { constructor( private authService: AuthService private router: Router) {} canActivate(route: ActivateRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean { return his.authService.isAuthenticated().then( (authenticated: boolen) => { if (authenticated) return true; else this.router.navigate(['/']); // navigate home or just return false }); } // To guard only the childs of this route canActivateChild(route: ActivateRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean { return this.canActivate(route,state); } ``` > auth.service.ts (dummy service - in realworld, here we access the server) ```typescript= export class AuthService { loggedIn = false; isAuthenticated() { const promise = new Promise( (resolve, reject) => { setTimeout( () => { resolve(this.loggedIn); }, 800); //ms } ); return promise; } login(){ this.loggedIn = true; } logout(){ this.loggedIn = false; } } ``` > app-routing.module.ts ```typescript= // Inside the array, enter all guards should be used on this route { path: 'users', canActivate: [AuthGuard], component: UsersComponent } // Guard childs, add this in parent { path: 'users', canActivateChild: [AuthGuard], component: UsersComponent } ``` Enable Login with AuthService - > home.component.html ```htmlmixed= <button class="btn btn-default" (click)="onLogin()"> Login </button> <button class="btn btn-default" (click)="onLogout()"> Logout </button> ``` > home.component.ts ```typescript= constructor(... private authService: AuthService) { } onLogin() { this.authService.login(); } onLogout() { this.authService.logout(); } ``` no visual indication yet, but content is now available only for logged in users. Control wheter you're allowed to leave a Route - like leaving without saving/submitting. > edit.server.ts ```typescript= import { Observeable } from 'rxjs/Observable'; export class EditServerComponent implements CanComponentDeactivate { allowEdit = false; changesSaved = false; onUpdateServer(){ this.changesSaved = true; } canDeactivate() { if (!this.allowEdit) return true; // onInit we binded serverName with server.name // serverName: bound property to input // server.name: previous value if (this.serverName !== this.server.name || this.serverStatus !== this.server.status && !this.changesSaved) return confirm('Do you want to discard changes?'); else return true; } } ``` **Add a new File** > can-deactivate-guard.service.ts ```typescript= export interface CanComponentDeactivate { canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean; } //interface, therefore needs to be implmeneted. export class CanDeactivateGuard implements CanDeacivate<CanComponentDeactivate>{ canDeactivate(component: CanComponentDeactivate, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot): // nextState? - Optional argument // Function's returned value is the below Observable<boolean> | Promise<boolean> | boolean { return component.canDeactivate(); } } ``` > app-routing.module.ts ```typescript= { path: 'users', component: UsersComponent, canDeactivate: [CanDeactivateGuard]} ``` and don't forget to provide service in `app.module.ts` Get dynamic data to a Route - (Previeus chapter deals with static data) * Resolver (service) - run some code before the Route is rendered * Preload some data from the server before accesing/displaying route * Alternative - Load instantly and present a spinner. * Create a new service to retrieve data with a resolver instead of via params. * Important when using asynchronis data > server-resolver.service.ts ```typescript= import { Resolve } from '@angular/router'; interface Server { id: number; name: string; status: string; } @Injectable export class ServerResolver implements Resolve<Server> { constructor(private serversService: ServersService) resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Server> | Promise<Server> | Server { //'+' - cast to integer return this.serversService.getServer(+route.params['id']) } } ``` > app-routing.module.ts ```typescript= { path: 'users', component: UsersComponent, resolve: {server: ServerResolver} } ``` this will map the data it gives us back > server.component.ts ```typescript= ngOnInit() { this.route.data.subscribe( (data: Data) => { this.server = data['server'] } ); } ``` Hash Configuration - On real server, the URL is parsed by the web server first, not by angular. Adding `#` to the URL - `localhost:4200/#/servers` informs the server to parse only the first part of the URL `localhost:4200/` and let Angular parse the rest `/servers` > app-routing.module.ts ```typescript= imports: [ RouterModule.forRoot(appRoutes, {useHash: true}) ] ``` <!-- <hr> --> ### Avoid reloading the page when clicking on elements 1. Remove `href="#"` - this calls the web server 2. Add ```CSS style="cursor: pointer;" ``` to view clickable when hovering over the element. ### New item / Edit item page > recipe-edit.component.ts ```typescript= export class RecipeEditComponent implements OnInit { id: number; editMode = false; constructor(private route: ActivatedRoute) { } ngOnInit() { this.route.params .subscribe( (params: Params) => { // retrieve id dynamically this.id = +params['id']; // if route has id parameter - then we are in Edit mode // else: we are creating a New item this.editMode = params['id'] != null; } ); } } ``` > app-routing.module.ts ```typescript= { path: 'new', component: RecipeEditComponent }, { path: ':id', component: RecipeDetailComponent }, ``` * Must put `new` before `:id` to avoid error - Routing does first match prefix (`localhost:4200/recipe/new` will fail because `id=new` not identified as valid id) * Both `New` and `Edit` are handled within same component - can be tell apart by `editMode` property. ___ Observables = Observables provide support for passing messages between publishers and subscribers in your application. <hr> #### Observable Various data sources - events, Http requests, Triggered in code (button) Passing adata to the observer upon asynchronous events. #### Observer Listen to an observable in order to execute our code * Handle Data * Handle Error * Handle Completion - doesn't have to complete (button can be clicked many times) In Routing, the **Observer** subscribe to changes in Params, Here Angular is emiting the change in URL and deliver the data change to Params. Subscribe method can get 3 arguments (callbacks): handle data, error and completion. Create an Observable - > component.ts ```typescript= import { Observable } from 'rxjs/Observable'; import 'rxjs/Rx'; import { Observer } from 'rxjs/Observer'; // The observable is created on init. // gets a code that is executed right away (OnInit). ngOnInit() { // we will pass string argument to the observer const myObservable = Observable.create((observer: Observer<string>) => { setTimeout(() => { // next() - pass regular package observer.next('first package'); }, 2000); setTimeout(() => { // error() - pass error package observer.error('this does not work'); }, 4000); setTimeout(() => { // complete() - pass event completed observer.complete(); }, 5000); setTimeout(() => { observer.next('third package'); }, 6000); }); // The observer is listening to asynchronous events // emited by the observable and handles them this.customObsSubscription = myObservable.subscribe( (data: string) => { console.log(data); }, (error: string) => { console.log(error); }, () => { console.log('completed'); } ); } ``` ```typescript= // Infinite observable const myNumbers = Observable.interval(1000); myNumbers.subscribe( (number: number) => { console.log(number); } ); ``` This will cause memory leak since there's an active subscription, and it will keep running even after leaving home.component (destroyed) Therefore we should unsubscribe onDestroy ```typescript= export class HomeComponent implements OnInit, OnDestroy { numbersObsSubscription : Subscription; ngOnInit() { const myNumbers = Observable.interval(1000); this.numbersObsSubscription = myNumbers.subscribe( (number: number) => { console.log(number); } ); } ngOnDestroy() { this.numbersObsSubscription.unsubscribe(); } } ``` #### Many ways to create Observables RxJs Documentation - ReactiveX.io/rxjs/ (We only used `create()` and `inverval()`) ### Subject Observer & Observable in one convenient object. Alternative to `emit` when communicating between compoenents * `new Subject()` instead of `new EventEmitter()` * `next()` instead of `emit()` * must be unsubscribed when destroyed Allows you to push it to emit new data during your code > users.service.ts ```typescript= import { Subject } from 'rxjs/Subject'; export class UsersService { // Creating the Observable userActivated = new Subject(); } ``` > user.component.ts ```typescript= // this function is executed on button click // inside user.component.html, which then passes // a data package to the observer onActivated() { this.usersService.userActivated.next(); } ``` > app.component.ts ```typescript= export class AppComponent implements OnInit { user1Activated = false; user2Activated = false; constructor(private usersService: UsersService) { } ngOnInit() { this.usersService.userActivated.subscribe( (id: number) => { if (id === 1) this.user1Activated = true; if (id === 2) this.user2Activated = true; } ); } } ``` Observable Operators - Allows you to transform the data you recieve and stay indise the observable ```typescript= // Here we change the data we get from the interval // therefore, we pass a tranformation of it. const myNumbers = Observable.interval(1000) .map( (data: number) => { return data * 2; } ); myNumbers.subscribe( (number: number) => { console.log(number); } ); ``` ___ Forms = Better way to handle data input from user (than local reference) Template-Driven - * Angular creates a Javascript representation of the form, when it detects `<form> </form>` >app.module.ts ```typescript import { FormsModule } from '@angular/forms'; ``` * ngForm: an object created by angular according to the html element + few properties/directive of the form. * Angular adds dynamically directives like `ng-dirty / ng-valid / ng-dirty` etc. to this object when the value inside an input element is changed/valid * ### Style the input according to the state of this directives. #### Red Border for invalid value ```css= input.ng-invalid.ng-touched { border: 1px solid red; } ``` * ### Validators: * Template - https://angular.io/api?type=directive * Reactive - https://angular.io/docs/ts/latest/api/forms/index/Validators-class.html * Enable HTML5 validation (by default, Angular disables it). Add the `ngNativeValidate` to a control in the template. * `ngModel`: a. `ngModel` - no binding, just letting Angular know that this input is a control b. `[ngModel]` - one way binding, to select a default value c. `[(ngModel)]` - two way binding, to use the dynamic changes. * #### Radio buttons - only one option can be selected ```typescript genders = ['male', 'female']; ``` ```htmlmixed= <div class="radio" *ngFor="let gender of genders"> <label> <input type="radio" name="gender" ngModel [value]="gender" required> {{ gender }} </label> ``` </div> * Extract data from a form - to present on submit Need to add the fields to the component's property and bind to it * There is an option to `reset()` to object (or specific fields) > component.ts ```typescript= // Defining `(onSubmite)` to be triggered whenever the HTML submit element is clicked. // (its default value was to access the server, but we use angular to react since it is a single page app) // #f - is the form name to access from `component.ts` <form (ngSubmit)="onSubmit()" #f="ngForm"> <div id="user-data" // ngModelGroup - gives us access to the object's data created by this form ngModelGroup="userData" #userData="ngModelGroup"> // `form-group` - to gather few inputs to one component <div class="form-group"> <label for="username">Username</label> <input type="text" id="username" class="form-control" ngModel name="username" // required input for the form to be valid required> </div> <button class="btn btn-default" type="button" (click)="suggestUserName()">Suggest an Username</button> <div class="form-group"> <label for="email">Mail</label> <input type="email" id="email" class="form-control" ngModel name="email" required email // #email - local reference to the email data - to use its properties - valid,thouched #email="ngModel"> // Alert message on invalid value <span class="help-block" *ngIf="!email.valid && email.touched">Please enter a valid email!</span> </div> </div> // Entire form control <p *ngIf="!userData.valid && userData.touched">User Data is invalid!</p> ``` ### Change the object created by the form ```typescript= export class AppComponent { // @viewChild - Provides a reference to elements or components in the view @ViewChild('f') signupForm: NgForm; suggestUserName() { const suggestedName = 'Superuser'; // setValue - pass entire object to be set on the form this.signupForm.setValue({ userData: { username: suggestedName, email: '' }, secret: 'pet', questionAnswer: '', gender: 'male' }); // patchValue - set a specific field data this.signupForm.form.patchValue({ userData: { username: suggestedName } }); } } ``` Reactive - > app.module.ts ```typescript import { ReactiveFormsModule } from '@angular/forms'; ``` > app.component.ts ```typescript= export class AppComponent implements OnInit { genders = ['male', 'female']; // Reactive form signupForm: FormGroup; forbiddenUsernames = ['Chris', 'Anna']; constructor() {} ngOnInit() { // connecting controls to the HTML elements this.signupForm = new FormGroup({ 'userData': new FormGroup({ // Arguments: 1) default value (null) // 2) validators (or array of them), passing a reference // 3) Async validators 'username': new FormControl(null, // bind this since we use it in the validator [Validators.required, this.forbiddenNames.bind(this)]), 'email': new FormControl(null, [Validators.required, Validators.email], this.forbiddenEmails) }), 'gender': new FormControl('male'), 'hobbies': new FormArray([]) }); // this.signupForm.valueChanges.subscribe( // (value) => console.log(value) // ); this.signupForm.statusChanges.subscribe( (status) => console.log(status) ); this.signupForm.setValue({ 'userData': { 'username': 'Max', 'email': 'max@test.com' }, 'gender': 'male', 'hobbies': [] }); this.signupForm.patchValue({ 'userData': { 'username': 'Anna', } }); } onSubmit() { console.log(this.signupForm); this.signupForm.reset(); } onAddHobby() { // cast to `FormArray` and push into it const control = new FormControl(null, Validators.required); (<FormArray>this.signupForm.get('hobbies')).push(control); } // can be written here or in an external like 'CustomValidators' forbiddenNames(control: FormControl): {[s: string]: boolean} { if (this.forbiddenUsernames.indexOf(control.value) !== -1) { return {'nameIsForbidden': true}; } // by returning null - let Angular know it is valid return null; } forbiddenEmails(control: FormControl): Promise<any> | Observable<any> { const promise = new Promise<any>((resolve, reject) => { setTimeout(() => { if (control.value === 'test@test.com') { resolve({'emailIsForbidden': true}); } else { resolve(null); } }, 1500); }); return promise; } } ``` #### Connecting an HTML element to the typescript control Control: `formControlName="email"` Control Group: `formGroupName="userData"` Control Array: `formArrayName="hobbies"` > component.html ```htmlmixed= <form [formGroup]="signupForm" (ngSubmit)="onSubmit()"> // Reactive: name of parent control (above 'username' & 'email') <div formGroupName="userData"> <div class="form-group"> <label for="username">Username</label> <input type="text" id="username" <!-- connecting this element to the typescript control --> formControlName="username" class="form-control"> // get access to a control in the form <span *ngIf="!signupForm.get('userData.username').valid && signupForm.get('userData.username').touched" class="help-block"> // Error code <span *ngIf="signupForm.get('userData.username').errors ['nameIsForbidden']">This name is invalid!</span> <span *ngIf="signupForm.get('userData.username').errors ['required']">This field is required!</span> </span> </div> <div class="form-group"> <label for="email">email</label> <input type="text" id="email" formControlName="email" class="form-control"> <span *ngIf="!signupForm.get('userData.email').valid && signupForm.get('userData.email').touched" class="help-block">Please enter a valid email!</span> </div> </div> <div class="radio" *ngFor="let gender of genders"> <label> <input type="radio" formControlName="gender" [value]="gender">{{ gender }} </label> </div> <div formArrayName="hobbies"> <h4>Your Hobbies</h4> // Button to add a control to the form <button class="btn btn-default" type="button" (click)="onAddHobby()">Add Hobby</button> <div class="form-group" *ngFor="let hobbyControl of signupForm.get('hobbies').controls; let i = index"> <input type="text" class="form-control" [formControlName]="i"> </div> </div> <span *ngIf="!signupForm.valid && signupForm.touched" class="help-block">Please enter valid data!</span> <button class="btn btn-primary" type="submit">Submit</button> </form> ``` #### Validator for an integer (built in) ```htmlmixed // property binding short way pattern="^[1-9]+[0-9]*$" ``` #### On edit mode * Populate selected item data to the form's fields (video #208) * Change 'Add' button to 'Update' ```htmlmixed <button> {{ editMode ? 'Update' : 'Add'}} </button> ``` ___ Http & Authentication = <hr> Http = * Requests to do some calculation on the server * Access the database must reload the app * Http is a solution that keeps our app a SPA #### Create a service to deal with Http > server.service.ts ```typescript= import { Injectable } from '@angular/core'; import { Headers, Http, Response } from '@angular/http'; import 'rxjs/Rx'; import { Observable } from 'rxjs/Observable'; @Injectable() export class ServerService { constructor(private http: Http) {} storeServers(servers: any[]) { // Adding Header to an Http request const headers = new Headers({'Content-Type': 'application/json'}); // This link is for the file in the database return this.http.put('https://udemy-ng-http.firebaseio.com/data.json', servers, {headers: headers}); } getServers() { return this.http.get('https://udemy-ng-http.firebaseio.com/data') // `map` - Centerlize the data received from the server // for usage all over the app's components // Does it by wrapping it with an Observable .map( (response: Response) => { // Angular automatically turn it to JS object via `response.json()` const data = response.json(); for (const server of data) { server.name = 'FETCHED_' + server.name; } return data; } ) // Error Handling - when accessing database (data instead of data.json) // Passing an observable like on data get method .catch( (error: Response) => { return Observable.throw('Something went wrong'); } ); } getAppName() { return this.http.get('https://udemy-ng-http.firebaseio.com/appName.json') .map( (response: Response) => { return response.json(); } ); } } ``` * Usually `POST` appends data to the database and `PUT` overrides it. ```typescript return this.http.post('https://udemy-ng-http.firebaseio.com/data.json', servers, {headers: headers}); ``` * Angular threat this requests with Observables * ` extract the data from json file to JS object > app.component.ts ```typescript= import { Component } from '@angular/core'; import { Response } from '@angular/http'; import { ServerService } from './server.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { appName = this.serverService.getAppName(); servers = [ { name: 'Testserver', capacity: 10, id: this.generateId() } ]; constructor(private serverService: ServerService) {} onAddServer(name: string) { this.servers.push({ name: name, capacity: 50, id: this.generateId() }); } // Http response data handling (App usage) onSave() { this.serverService.storeServers(this.servers) .subscribe( (response) => console.log(response), (error) => console.log(error) ); } onGet() { this.serverService.getServers() .subscribe( (servers: any[]) => this.servers = servers, (error) => console.log(error) ); } } ``` <hr> Authentication = On SPA there's no open session between client and server. Instead, the server send a token for the client to attach on future requests. * JWT - JSON Web Token https://jwt.io/introduction/ ### Create SignIn/SignOut pages 1. App should initialized the connection to the backend * Install firebase (for using it's SDK and libraries) `ng install --save firebase` > app.component.ts ```typescript= import * as firebase from 'firebase'; ngOnInit() { firebase.initializeApp({ apiKey: "AIzaSyDGDTtGNC4FkKpSe85qb2wLHzNi2xkxFy8", authDomain: "ng-recipe-book.firebaseapp.com" }); } ``` 2. Create a service to handle authentication with FireBase SDK > auth.service.ts ```typescript= import { Router } from '@angular/router'; import * as firebase from 'firebase'; import { Injectable } from '@angular/core'; @Injectable() export class AuthService { token: string; constructor(private router: Router) {} signupUser(email: string, password: string) { firebase.auth().createUserWithEmailAndPassword(email, password) .catch( error => console.log(error) ) } // On SignIn, save the token received for future requests signinUser(email: string, password: string) { firebase.auth().signInWithEmailAndPassword(email, password) .then( response => { this.router.navigate(['/']); firebase.auth().currentUser.getToken() .then( (token: string) => this.token = token ) } ) .catch( error => console.log(error) ); } logout() { firebase.auth().signOut(); this.token = null; } getToken() { firebase.auth().currentUser.getToken() .then( (token: string) => this.token = token ); return this.token; } isAuthenticated() { return this.token != null; } } ``` 3. Creating the forms > signup.html ```htmlmixed= <div class="row"> <!-- Bootstrap classes to adjust form looks --> <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2"> <form (ngSubmit)="onSignup(f)" #f="ngForm"> <div class="form-group"> <label for="email">Mail</label> <input type="email" id="email" name="email" ngModel class="form-control"> </div> <div class="form-group"> <label for="password">Password</label> <input type="password" id="password" name="password" ngModel class="form-control"> </div> <button class="btn btn-primary" type="submit" [disabled]="!f.valid">Sign Up</button> </form> </div> </div> ``` > signup.ts ```typescript= export class SignupComponent implements OnInit { constructor(private authService: AuthService) { } ngOnInit() {} onSignup(form: NgForm) { const email = form.value.email; const password = form.value.password; this.authService.signupUser(email, password); } } ``` 4. Use the authentication to access data * Verify Backend read & write guard (mandatory token) * Use the token when calling get/set. > data-storage.ts ```typescript storeRecipes() { const token = this.authService.getToken(); return this.http.put('https://ng-recipe-book.firebaseio.com/recipes.json?auth=' + token, this.recipeService.getRecipes()); } ``` 5. Creating Auth-Guard to prevent accessing a route while not authenticated * Create the below guard and then add it to the Routes table on 'app.module' > auth-guard.ts ```typescript= import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { Injectable } from '@angular/core'; import { AuthService } from './auth.service'; @Injectable() export class AuthGuard implements CanActivate { constructor(private authService: AuthService) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { return this.authService.isAuthenticated(); } } ``` ___