Author Archives: admin

Angular2 – Handling forms: Reactive approach

Introduction to the Reactive Approach

  • Form is created programmatically and synchronized with the DOM

Reactive : Setup

  • In the app.module we need to comment out FormsModule and add ReactiveFormsModule

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
// import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    // FormsModule,
    HttpModule,
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts

import { NgForm, FormGroup } from '@angular/forms';
...
  reactiveForm: FormGroup;

Reactive: Creating a form in code

  • Out intention now is bind an HTML form to a Form object created programmatically.

app.component.html

  <h2>Reactive forms</h2>
  <div class="row">
    <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
      <form>
        <div class="form-group">
          <label for="candidate">Candidate</label>
          <input type="text" id="candidate" class="form-control">
        </div>
        <div class="form-group">
          <label for="haircolor">Hair color</label>
          <select id="haircolor" type="select" class="form-control">
            <option value="blonde">Blonde</option>
            <option value="brown">Brown</option>
          </select>
        </div>

        <button
        class="btn btn-primary"
        type="submit"
        >Submit</button>
      </form>
    </div>
  </div>

app.component.ts

  //Reactive approach
  reactiveForm: FormGroup;

  ngOnInit() {
    this.reactiveForm = new FormGroup({
      'candidate': new FormControl(null),
      'haircolor': new FormControl('blonde')
    });
  }

Reactive: Syncing HTML and form

  • The first thing is tells Angular not to detect form tags and use my FormGroup created in Typescript. We use formGroup and formControlName built-in directives.

app.component.html

...
      <form [formGroup]="reactiveForm">
...
          <input type="text" id="candidate"
            class="form-control"
            formControlName="candidate">
...
          <select id="haircolor" type="select" class="form-control"
            formControlName="haircolor">

Reactive: Submitting the form

  • Here we can use ngSubmit as in TD forms , but we dont need any local reference to the form. We already have reference to the form in the Form object created.

app.component.html

<form [formGroup]="reactiveForm" (ngSubmit)="onSubmitReactive()">

app.component.ts

  onSubmitReactive() {
      console.log('submitted! reactive', this.reactiveForm);
  }

Reactive: Adding Validation

  • We will use Validators class to pass static references to functions to execute on validation.
  • We will use an array of Validators for making multiple validations.

app.component.ts

  ngOnInit() {
    this.reactiveForm = new FormGroup({
      'candidate': new FormControl(null, Validators.required),
      'email': new FormControl(null, [Validators.required,Validators.email]),
      'haircolor': new FormControl('brown')
    });
  }

Reactive: Getting access to controls

app.component.html

<span class="help-block" 
 *ngIf="!reactiveForm.get('candidate').valid && reactiveForm.get('candidate').touched">Please enter a valid candidate</span>

Reactive: Grouping controls

app.component.ts

  ngOnInit() {
    this.reactiveForm = new FormGroup({
      'userData': new FormGroup({
        'candidate':new FormControl(null, Validators.required),
        'email':new FormControl(null, [Validators.required,Validators.email])
      }),
      'haircolor': new FormControl('brown')
    });
  }

app.component.html

...
      <form [formGroup]="reactiveForm" (ngSubmit)="onSubmitReactive()">
        <div formGroupName="userData">
          <div class="form-group">
            <label for="candidate">Candidate</label>
            <input type="text" id="candidate"
              class="form-control"
              formControlName="candidate">
            <span class="help-block"
              *ngIf="!reactiveForm.get('userData.candidate').valid && reactiveForm.get('userData.candidate').touched">Please enter a valid candidate</span>
          </div>
          <div class="form-group">
            <label for="email-reactive">Email</label>
            <input type="text" id="email-reactive"
              class="form-control"
              formControlName="email">
          </div>

        </div>

Reactive: Array of Form controls (FormArray)

  • We will add hobbies dynamically as an input array.
  • We will use formArray directive in the HTML and FormArray in the typescript.

app.component.html

        <div formArrayName="hobbies">
          <h3>Your hobbies</h3>
          <button class="btn btn-primary" (click)="onAddHobby()">Add Hobby</button>
          <div class="form-group"
            *ngFor="let control of reactiveForm.get('hobbies').controls; let i = index">
            <input type="text" class="form-control" [formControlName]="i">
          </div>
        </div>

app.component.ts

 ngOnInit() {
 this.reactiveForm = new FormGroup({
 'userData': new FormGroup({
 'candidate':new FormControl(null, Validators.required),
 'email':new FormControl(null, [Validators.required,Validators.email])
 }),
 'hobbies': new FormArray([]),
 'haircolor': new FormControl('brown')
 });
 }
onAddHobby() {
    const control = new FormControl(null);
    (<FormArray>this.reactiveForm.get('hobbies')).push(control);

  }

Reactive: Creating Custom validators

  • We will check candidate against a  list of forbidden user names.

app.component.ts

    this.reactiveForm = new FormGroup({
      'userData': new FormGroup({
        'candidate':new FormControl(null, [Validators.required, this.forbiddenNameValidation.bind(this)]),
        'email':new FormControl(null, [Validators.required,Validators.email])
      }),
      'hobbies': new FormArray([]),
      'haircolor': new FormControl('brown')
    });
  }
...
  forbiddenNameValidation(control:FormControl): {[s: string]: boolean} {
      if(this.forbiddenUsernames.indexOf(control.value) !== -1) {
        return {'nameIsForbidden': true};
      }
      return null;  //Valid
  }

NOTE: It is important return null on validator function as it is how Angular recognizes validation is ok.

NOTE: We need to make bind(this) in the validators array for the custom validation for tells Angular to get the correct reference of the form control.

Reactive: Using Error Codes

  • We will customize error messages for custom validations.
  • We will check errors property in the form.controls object.

app.component.html

<div class="form-group">
            <label for="candidate">Candidate</label>
            <input type="text" id="candidate"
              class="form-control"
              formControlName="candidate">
            <span class="help-block"
              *ngIf="!reactiveForm.get('userData.candidate').valid && reactiveForm.get('userData.candidate').touched">
              <span class="help-block" *ngIf="reactiveForm.get('userData.candidate').errors['nameIsForbidden']">
                This name is forbidden
              </span>
              <span class="help-block" *ngIf="reactiveForm.get('userData.candidate').errors['required']">
                Name is required
              </span>
            </span>
          </div>

Reactive: Creating a Custom Async Validator

  • Sometimes we want to reach out a server to make a validation
  • So we need to make an async operation , in this case we can pass to the FormControl constructor a third parameter telling Async Validators.
  • In the example we simulate an async operation with a timeout and check if email is test@test.com
...
'email':new FormControl(null, [Validators.required,Validators.email], this.forbiddenEmails)
...

  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;
 }

Reactive: Reacting to Status or Value Changes

  • We can monitor all the values and status changing in our form in every moment with two observables of the reactive forms: valueChanges and statusChanges.
  • We can hook on them:
  ngOnInit() {
    ...
    this.reactiveForm.valueChanges.subscribe(
      (value) => {console.log(value);}
    );
    this.reactiveForm.statusChanges.subscribe(
      (value) => {console.log(value);}
    );
  }

Reactive: Setting and Patching values

  • As in TD forms we can use setValue, patchValue and reset methods on the reactive form object.

Notes

Shortcut for property binding for strings
  • Usually property binding use single quotes:
    [pattern]="'*(0-9)'"
  • When we re using a string in a property binding we can type a simpler syntax:
    pattern="*(0-9)"
Patterns in template driven forms
  • We can add pattern matching in template driven forms with the HTML5 pattern.
  • For example we want numbers above 0 in this input field:
          <input
            type="text"
            id="amount"
            class="form-control"
            name="amount"
            ngModel
            required
            pattern="^[1-9]+[0-9]*$">

ds

Angular 2 – Using pipes to transform output and making http requests

Pipes are about transforming output

Using Pipes

  • Some pipes are coming built in in Angular:
          {{ server.instanceType | uppercase }} |
          {{ server.started | date}}

Parametrizing pipes

{{ server.started | date:’fullDate’ }}

Where to learn more about pipes

https://angular.io/docs/ts/latest/api/#!?query=pipe

  • For example , for the date pipe we can see all the options we have available:
    • 'medium': equivalent to 'yMMMdjms' (e.g. Sep 3, 2010, 12:05:08 PM for en-US)
    • 'short': equivalent to 'yMdjm' (e.g. 9/3/2010, 12:05 PM for en-US)
    • 'fullDate': equivalent to 'yMMMMEEEEd' (e.g. Friday, September 3, 2010 for en-US)
    • 'longDate': equivalent to 'yMMMMd' (e.g. September 3, 2010 for en-US)
    • 'mediumDate': equivalent to 'yMMMd' (e.g. Sep 3, 2010 for en-US)
    • 'shortDate': equivalent to 'yMd' (e.g. 9/3/2010 for en-US)
    • 'mediumTime': equivalent to 'jms' (e.g. 12:05:08 PM for en-US)
    • 'shortTime': equivalent to 'jm' (e.g. 12:05 PM for en-US)

Chaining multiple pipes

  • {{ server.started | date:'fullDate' | uppercase }}

    The order is important because the types couldnt be the same.

Creating a custom pipe

shorten.pipe.ts

import {Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'shorten'
})
export class ShortenPipe implements PipeTransform{
  transform(value: any) {
    if(value.length > 10) {
      return value.substring(0, 10) + '...';
    } else {
      return value.substring(0, 10);
    }
  }
}

app.component.html

          <strong>{{ server.name | shorten}}</strong> |

app.module.ts

@NgModule({
  declarations: [
    AppComponent,
    ShortenPipe
  ],

Parametrizing a custom pipe

shorten.pipe.ts

import {Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'shorten'
})
export class ShortenPipe implements PipeTransform{
  transform(value: any, limit: number) {
    if(value.length > limit) {
      return value.substring(0, limit) + '...';
    } else {
      return value;
    }
  }
}

app.component.html

          <strong>{{ server.name | shorten:15}}</strong> |

 

Example: creating a filter pipe

  • Lets filter the server listing by status by typing in a status input field.

myfilter.pipe.ts

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'myfilter'
})
export class MyfilterPipe implements PipeTransform {

  transform(value: any, filterString: any, propName: string): any {
    if(value.length === 0 || filterString === '') {
      return value;
    }

    const resultArray = [];
    for(const item of value) {
      if(item[propName] === filterString) {
        resultArray.push(item);
      }
    }
    return resultArray;
  }

}

app.component.html

<li
          class="list-group-item"
          *ngFor="let server of servers | myfilter:filteredStatus:'status'"
          [ngClass]="getStatusClasses(server)">

Pure and impure pipes

  • If we add servers on a filtered list , we won’t see the added servers at the beginning.
  • That is because filters are pure by default.
  • If we want to make filtering to recalculate on adding servers to the list, we need to specify in the annotation the pure attribute:

myfilter.pipe.ts

@Pipe({
  name: 'myfilter',
  pure: false
})

app.component.html

<button type="button" (click)="addServer()">Add Server</button>

app.component.ts

  addServer(){
    this.servers.push({
      instanceType: 'small',
      name: 'JAJAJAJ',
      status: 'stable',
      started: new Date(15, 1, 2017)
    });
  }

NOTE: We can have performance issue by using this option in large lists

Understanding the async pipe

  • Imagine we have values on the template that depends of async services.
  • Then we can tell Angular to convert Promises in strings when promises are resolved with the async pipe.

app.component.ts

  appStatus = new Promise((resolve,reject)=> {
    setTimeout(
      ()=> {
        resolve('stable');
      }
    ,2000);
  });

app.component.html

<h3>App status: {{ appStatus | async}}</h3>

* We see how app status is blank at the beginning , but after two seconds render ‘stable’

 
dd
f
df
dfd
fd
fd

Angular 2 – Handling forms

  • Regarding forms, what Angular does for us is giving us a object representation of the form in order to handle fetching data and form validation.
  • And also we will be able to handle form submit without reloading the page (as it is a SPA).

Captura de pantalla 2017-05-10 a las 9.30.46

Two approaches

  • There are two ways of handling forms with Angular. With Template -driven we will resolve most of the cases, but with the Reactive approach you will have full control of the form and it is useful for advanced cases.
  • In Template-driven approch we code all the logic in the template.

Captura de pantalla 2017-05-10 a las 9.35.14

TD: Creating the form and registering controls

  • We need to import FormModule in the app.module.ts

app.module.ts

import { FormsModule } from '@angular/forms';
...

imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  • Angular will detect form tags in your html to create the javascript representation, but we still need to indicate what controls of the form we want to manage. We get that with ngModel directive and giving a “name” attribute to the control. That name we will use it to fetch the value of that control,
            <input
              type="email"
              id="email"
              class="form-control"
              ngModel
              name="email">
  • Submit of the form managed by Angular is done by adding ngSubmit directive to the form tag and specifying the handler method. Otherwise the default behaviour will take place.
<form (ngSubmit)="onSubmit()">
  • With the ngForm directive we finally get the desired javascript representation of our form
<form (ngSubmit)="onSubmit(f)" #f="ngForm">
import { NgForm } from '@angular/forms';

 ...
 onSubmit(form: NgForm) {
    console.log('submitted!', form);
  }

Captura de pantalla 2017-05-10 a las 10.31.05

  • There are useful properties in NgForm object:
    • controls: what control we registered
    • dirty: flag to indicate of something changed
    • disabled
    • enabled
    • valid
    • invalid
    • touched
  • We also can use ViewChild annotation to get reference of a template reference and get form reference:
<form (ngSubmit)="onSubmit()" #f="ngForm">
@ViewChild('f') signupForm: NgForm;
...
onSubmit() {
 console.log('submitted!', this.signupForm);
 }

TD: Adding validation to check user input

  • In template driven forms we can add some validation in the tags. For example, we will add required to username input and required and email validation to email input:
               <input
              type="text"
              id="username"
              class="form-control"
              ngModel
              name="username"
              required>
...
             <input
              type="email"
              id="email"
              class="form-control"
              ngModel
              name="email"
              required
              email>
  • in the NgForm object submitted we will get the valid flag of the form and of the individuals controls set to true/false as a result of validation. What Angular does behind the scenes is adding some control classes to the DOM inputs, such as ‘ng-valid’ , ‘ng-touched’, etc

Built-in Validators & Using HTML5 Validation

Which Validators do ship with Angular?

Check out the Validators classhttps://angular.io/docs/ts/latest/api/forms/index/Validators-class.html – these are all built-in validators, though that are the methods which actually get executed (and which you later can add when using the reactive approach).

For the template-driven approach, you need the directives. You can find out their names, by searching for “validator” in the official docshttps://angular.io/docs/ts/latest/api/#!?query=validator – everything marked with “D” is a directive and can be added to your template.

Additionally, you might also want to enable HTML5 validation (by default, Angular disables it). You can do so by adding the ngNativeValidate  to a control in your template.

 

TD: Using the Form State

  • Lets disable the submit button on false validation (using the form local reference ‘f’):
<button 
        class="btn btn-primary"
        type="submit"
        [disabled]="!f.valid">Submit</button>
  • Also we would like to render in red invalid inputs on validation checks. We will use Angular classes:

app.component.css

input.ng-invalid.ng-touched {
  border: 1px solid red;
}

TD: Outputting validation error messages

  • Lets add a validation message for the email input using a local reference for the email input:
<input
              type="email"
              id="email"
              class="form-control"
              ngModel
              name="email"
              required
              email
              #email="ngModel">
            <span class="help-block" *ngIf="!email.valid && email.touched">Please select a valid email!</span>

TD: Setting default values with ngModel property binding

  • We want in the elect input set “pet” ad the default value. We use property binding in the ngModel directive.

app.component.ts

  defaultQuestion = "pet";

app.component.html

<select
            id="secret"
            class="form-control"
            [ngModel]="defaultQuestion"
            name="secret">
            <option value="pet">Your first Pet?</option>
            <option value="teacher">Your first teacher?</option>
          </select>

TD: Using ngModel with Two-Way Binding

  • Sometimes you want to react to changes in the input , so you need 2-way binding.
  • It is the case when some element in the template depends on other element. In this example we will print what user input in a textarea:

app.component.html

        <div class="form-group">
          <textarea name="questionAnswer" rows="10"
            class="form-control"
            [(ngModel)] = "answer">
          </textarea>
          <p>Yout reply is {{answer}}</p>
        </div>

app.component.ts

  answer='';
  • We get the value in the NgForm in the typescript and also in the template

TD: Grouping form controls

  • We can group some form controls under a same javascript object and be as another form control for checking engine. For example let group user data :
<form (ngSubmit)="onSubmit()" #f="ngForm">
        <div id="user-data"
        ngModelGroup="userData"
        #userData="ngModelGroup">
...
        <p *ngIf="!userData.valid && userData.touched">User data is not valid!</p>

TD: Handling Radio Buttons

          <div class="radio" *ngFor="let gender of genders">
            <label for="">
              <input type="radio"
                name="gender"
                id="gender_{{gender}}"
                ngModel
                [value]="gender"
                required>
                {{gender}}
            </label>
          </div>

app.component.ts

  genders= ['male', 'female'];

TD: Setting and patching form values

  • We can set the whole form to some value with two methods: setValue and patchValue
  • With setValue we overwrite the whole form. For example let set the form on clicking in the suggested user button:

app.component.html

          <button class="btn btn-default" type="button" (click)="suggestUserName()">Suggest an Username</button>

app.component.ts

  suggestUserName() {
    const suggestedName = 'Superuser';
    this.signupForm.setValue({
        userData: {
          username: suggestedName,
          email:''
        },
        secret:'pet',
        questionAnswer:'',
        gender:'male'
    });
  }
  • With patchValue on the form property of the NgForm object we don’t touch other values in the form , only the specified ones:
    this.signupForm.form.patchValue({
        userData: {
          username: suggestedName,
        },
        gender:'male'
    });

TD: Using form data

  • We will print out the information user input on submission:

app.component.html

  <div class="row" *ngIf="submitted">
    <div class="col-xs-12">
      <h2>Your submitted data is:</h2>
      <ul>
        <li>Username: {{user.username}}</li>
        <li>Email: {{user.email}}</li>
        <li>Gender: {{user.gender}}</li>
        <li>Secret question: {{user.secret}}</li>
      </ul>
    </div>
  </div>

app.component.ts

  user = {
    name: '',
    email: '',
    gender: '',
    secret: '',
  };

...

  onSubmit() {
    console.log('submitted!', this.signupForm);
    this.submitted = true;
    this.user.name = this.signupForm.value.userData.username;
    this.user.email = this.signupForm.value.userData.email;
    this.user.gender = this.signupForm.value.gender;
    this.user.secret = this.signupForm.value.secret;
  }

TD: Resetting forms

this.signupForm.reset();
  • You can also pass values to reset method to reset to specific values.

 

Angular 2 – Observables

  • Observables are another way of handling async tasks. Angular2 use them a lot.
  • Usually are objects wrapping some data source which emits events, such as button clicks , https requests, etc.

Captura de pantalla 2017-05-09 a las 17.51.10

Analyzing a Built in observable

  • We already used observables when subscribing to changes int the route params:
    this.route.params.subscribe(
      (params: Params)=> {
        this.id = +params['id'];
        this.editMode = !isNaN(this.id);
      }
    );

Building and using a first simple Observable

  • We will make a observable which print a number in the console every second.
  • This is a built-in utility in rxjs package
...
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
  ...
ngOnInit() {
    const myNumbers = Observable.interval(1000);
    myNumbers.subscribe(
      (number: number) => {
        console.log(number);
      }
    );
  }

Building and using a custom observable from scratch

  • Let make a custom observable which prints to console after 2 seconds , 4 seconds and complete on five seconds.
    const myObservable = Observable.create((observer: Observer<string>) => {
      setTimeout(()=> {
        observer.next('first package');
      },2000);
      setTimeout(()=> {
        observer.next('second package');
      },4000);
      // setTimeout(()=> {
      //   observer.error('error ocurred!');
      // },5000);
      setTimeout(()=> {
        observer.complete();
      },5000);
      setTimeout(()=> {
        observer.next('third package');
      },6000);
    });

    myObservable.subscribe(
      (data:string)=> { console.log(data); },
      (error:string)=> { console.log(error); },
      ()=> { console.log('completed'); },
    );

NOTE: third package is not printed because it is emitted after observable is completed (dead).

Unsubscribe!

  • We must not forget clean up our subscriptions on leaving the component. Otherwise we could lead to a memory leak. We will do it in the destroy lifecycle method.
...
  subscription1: Subscription;
  subscription2: Subscription;
...
    this.subscription1 = myNumbers.subscribe(
      (number: number) => {
        console.log(number);
      }
    );

    this.subscription2 = myObservable.subscribe(
      (data:string)=> { console.log(data); },
      (error:string)=> { console.log(error); },
      ()=> { console.log('completed'); },
    );
...
  ngOnDestroy() {
    this.subscription1.unsubscribe();
    this.subscription2.unsubscribe();
  }
...

Where to learn more

Using Subject to pass and listen to data

  • Subject is a object from Rxjs package that is Observable and Observer at the same instance.
  • It is very useful in cross component communication instead of emit events

user.service.ts

import {Subject} from 'rxjs/Subject';
...
export class UsersService {
  userActivated = new Subject();
...

app.component.html

      <hr>
      <h4>Using Subjects (from RxJs)</h4>
      <p>
        User is {{userActivated? '(activated)':''}}
        <button (click)="onActivate()">Activate user</button>
      </p>

app.component.ts

...
  userActivated = false;

  constructor(
     private usersService: UsersService ,
...
  ngOnInit() {

    this.subscription3 = this.usersService.userActivated.subscribe(
      (text: string)=> {
        if(text) {
          this.userActivated = true;
        }
    });
...
  onActivate() {
    this.usersService.userActivated.next('dfdfdfd');
  }

Understanding observable operators

  • Observable operators return observables , so you can chain them.
  • In http://reactivex.io/rxjs/ , in the Observable object you can check all operators available.
  • Lets make an example using map operator to print double numbers in the console
    const myNumbers = Observable.interval(1000).map(
      (data:number)=>{
        return data*2;
      }
    );

ds

ds

ds

d

sd

Angular 2 – Routing

Setting up and loading routes

  • We set up two routes:
    • http://localhost:4200/page1
    • http://localhost:4200/page2

app.module.ts

import { Routes, RouterModule } from '@angular/Router';
import { Page1Component } from './page1/page1.component';
import { Page2Component } from './page2/page2.component';

const appRoutes: Routes = [
  // { path: '', Page1Component},
  { path: 'page1', component: Page1Component},
  { path: 'page2', component: Page2Component}
];

...
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    RouterModule.forRoot(appRoutes)
  ],

...

app.component.html

      <router-outlet></router-outlet>

Navigating with Router links

  • Using routerLink directive we avid reloading of the page

app.component.html

<h5>Routing</h5>
 <ul class="nav nav-tabs">
 <li role="presentation" class="active"> <a routerLink="/">Home</a></li>
 <li role="presentation"> <a routerLink="/page1">Page 1</a></li>
 <li role="presentation"> <a [routerLink]="['/page2']">Page 2</a></li>
 </ul>
 ...
 
 <router-outlet></router-outlet>

Understanding navigation paths

  • In routerLinks we can define relative paths to current path:
    <a routerLink="page1">Page 1</a>
  • or absolute paths: 
    <a routerLink="/page1">Page 1</a>
  • or navigate as a folder structure:
    <a routerLink="../page1">Page 1</a>

Styling Active Router Links

      <ul class="nav nav-tabs">
        <li role="presentation" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}"> <a routerLink="/">Home</a></li>
        <li role="presentation" routerLinkActive="active"> <a routerLink="/page1">Page 1</a></li>
        <li role="presentation" routerLinkActive="active"> <a [routerLink]="['/page2']">Page 2</a></li>
      </ul>

Loading routes programmatically

import { Router } from '@angular/Router';
...
goto(target) {
   this.router.navigate([target]);
 }

using relative paths with navigation method:

constructor(private route: ActivatedRoute , private router: Router){}
...
this.router.navigate([target], {relativeTo: this.route});

Passing parameters to routes

app.module.ts

const appRoutes: Routes = [
 // { path: '', Page1Component},
 { path: 'page1', component: Page1Component},
 { path: 'page2', component: Page2Component},
 { path: 'page2/:id/:name', component: Page2Component}
];

page2.component.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/Router';
@Component({
 selector: 'app-page2',
 templateUrl: './page2.component.html',
 styleUrls: ['./page2.component.css']
})
export class Page2Component implements OnInit {

user: {id: number , name: string};

constructor(private route: ActivatedRoute) { }

ngOnInit() {
 this.user = {
 id: this.route.snapshot.params['id'],
 name: this.route.snapshot.params['name']
 };
 }
}

Subscribing to changes in the route params

this.route.params.subscribe(
 (params: Params) => {
 this.user.id = params.id;
 this.user.name = params.name;
 }
 );

NOTE: The subscription on the params will remain while we are in teh page2 component; Angular2 will automatically remove the subscription on destroy of the component. Just in case you need to unsubscribe for observables we show yo what angular does:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/Router';
import { Subscription } from 'rxjs/Subscription';

@Component({
  selector: 'app-page2',
  templateUrl: './page2.component.html',
  styleUrls: ['./page2.component.css']
})
export class Page2Component implements OnInit, OnDestroy{

  user: {id: number , name: string};
  paramsSubscription: Subscription;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.user = {
      id: this.route.snapshot.params['id'],
      name: this.route.snapshot.params['name']
    };

    this.paramsSubscription = this.route.params.subscribe(
      (params: Params) => {
        this.user.id = params['id'];
        this.user.name = params['name'];
      }
    );
  }

  ngOnDestroy() {
    this.paramsSubscription.unsubscribe();
  }

}

Passing Query Parameters and Fragments

  • From the html, this code
<a
  [routerLink]="['/page2']"
  [queryParams]="{allowEdit: '1', otro: 'jaja'}"
  fragments="myfragment">
    Navigate to Page2 with query params and fragments
</a>

will go to http://localhost:4200/page2?allowEdit=1&otro=jaja

  • From the typescript code:
  gotoWithQueryParamsAndFragments(target) {
    this.router.navigate([target], {relativeTo: this.route, queryParams: {allowEdit:'1'}, fragment: 'myfragment'});
  }

it will go to http://localhost:4200/page2?allowEdit=1#myfragment

  • For retrieving them:
console.log('Init page 2 queryParams and fragments', this.route.snapshot.queryParams, this.route.snapshot.fragment);

and also , as  they are observables we can subscribe to their changes.

NOTE: To convert a string into a number there is a sneaky way: to prepend a ‘+’ operator:

> +'42'
42

Setting up nested routes

  • We set up ‘/page2/nested’ route. This route will be child of /page2 and in the page 2 html we insert “router-outlet” to nest this child views.

app.module.ts

const appRoutes: Routes = [
  // { path: '', Page1Component},
  { path: 'page1', component: Page1Component},
  { path: 'page2', component: Page2Component, children: [
    { path: 'nested', component: NestedComponent}
  ]},
  { path: 'page2/:id/:name', component: Page2Component}

];

page2.component.html

<p>
  page2 works!
</p>
<p>
  User Id: {{user.id}}
</p>
<p>
  User name: {{user.name}}
</p>

<p>
  <a [routerLink]="['/page2', '23', 'INDIRA']">Navigate to Page2/23/INDIRA</a>
</p>

<p>
<a
  [routerLink]="['/page2']"
  [queryParams]="{allowEdit: '1', otro: 'jaja'}"
  fragments="myfragment">
    Navigate to Page2 with query params and fragments
</a>
</p>

<hr>
Nested routes under page2
<router-outlet></router-outlet>

Preserve query parameters on navigation to other route

    this.router.navigate([target], {relativeTo: this.route, queryParamsHandling: 'preserve'});

Redirecting and wildcard routes

We want redirect /page3 to /not-found

and we want to get all unknown routes to /not-found

app.module.ts

const appRoutes: Routes = [
  // { path: '', Page1Component},
  { path: 'page1', component: Page1Component},
  { path: 'page2', component: Page2Component, children: [
    { path: 'nested', component: NestedComponent}
  ]},
  { path: 'page2/:id/:name', component: Page2Component},
  { path: 'not-found', component: PageNotFoundComponent},
  { path: 'page3', redirectTo: '/not-found'},
  { path: '**', redirectTo: '/not-found'}

];

NOTE: the wildcard rule is evaluated at the end because it is in the bottom of the rules. If we put it at the top , all routes will go to not-found.

NOTE 2: Important: Redirection Path Matching
 

In our example, we didn’t encounter any issues when we tried to redirect the user. But that’s not always the case when adding redirections.

By default, Angular matches paths by prefix. That means, that the following route will match both /recipes  and just /

{ path: '', redirectTo: '/somewhere-else' }

Actually, Angular will give you an error here, because that’s a common gotcha: This route will now ALWAYS redirect you! Why?

Since the default matching strategy is "prefix" , Angular checks if the path you entered in the URL does start with the path specified in the route. Of course every path starts with ''  (Important: That’s no whitespace, it’s simply “nothing”).

To fix this behavior, you need to change the matching strategy to"full" :

{ path: '', redirectTo: '/somewhere-else', pathMatch: 'full' }

Now, you only get redirected, if the full path is ''  (so only if you got NO other content in your path in this example).

Outsourcing the router configuration

  • We will get out route configuration from app.module.ts to a proper module:

app-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/Router';

import { Page1Component } from './page1/page1.component';
import { Page2Component } from './page2/page2.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { NestedComponent } from './nested/nested.component';

const appRoutes: Routes = [
  // { path: '', Page1Component},
  { path: 'page1', component: Page1Component},
  { path: 'page2', component: Page2Component, children: [
    { path: 'nested', component: NestedComponent}
  ]},
  { path: 'page2/:id/:name', component: Page2Component},
  { path: 'not-found', component: PageNotFoundComponent},
  { path: 'page3', redirectTo: '/not-found'},
  { path: '**', redirectTo: '/not-found'}

];

@NgModule({
  imports: [
    RouterModule.forRoot(appRoutes)
  ],
  exports: [RouterModule]

})
export class AppRoutingModule {
}

app.module.ts

...
import { AppRoutingModule } from './app-routing.module';
...

  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    AppRoutingModule
  ],

...

Protecting Routes with canActivate

  • We want to protect some routes to only logged in users. We will use what Angular calls guards. 
  • They are some services which are called before some routes we set up to.
  • We will simulate an AuthService to loggin in /out

app-routing.module.ts

const appRoutes: Routes = [
  { path: '', component: Page1Component},
  { path: 'page1', component: Page1Component},
  { path: 'page2', canActivate: [AuthGuard], component: Page2Component, children: [
    { path: 'nested', component: NestedComponent}
  ]},
  { path: 'page2/:id/:name', canActivate: [AuthGuard], component: Page2Component},
  { path: 'not-found', component: PageNotFoundComponent},
  { path: 'page3', redirectTo: '/not-found'},
  { path: '**', redirectTo: '/not-found'}

];

auth-guard.service.ts

import {Injectable} from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot,Router } from '@angular/Router';
import {Observable} from 'rxjs/Observable';

import { AuthService } from './auth.service';

@Injectable()
export class AuthGuard implements CanActivate {

  constructor(private authService: AuthService, private router: Router) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    return this.authService.isAuthenticated().
    then((authenticated: boolean)=> {
      if(authenticated) {
        return true;
      } else {
        this.router.navigate(['/']);
      }
    });
  }

}

auth.service.ts

export class AuthService {
  loggedIn = false;

  isAuthenticated() {
    const promise = new Promise((resolve,reject)=> {
      setTimeout(()=> {
        resolve(this.loggedIn);
      },800);

    });

    return promise;
  }

  login() {
    this.loggedIn = true;
  }

  logout() {
    this.loggedIn = false;
  }
}

With canActivateChild interface we can protect only the children of a route

Controlling navigation with canDeactivate

  • We will control when leaving /page route with some change to get confirmation from user with a javascript confirm

/page1/can-deactivate-guard.service.ts

import { Observable } from 'rxjs/Observable';
import { CanDeactivate, ActivatedRouteSnapshot , RouterStateSnapshot } from '@angular/router';

export interface CanComponentDeactivate {
  canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}

export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
  canDeactivate(component: CanComponentDeactivate,
                currentRoute: ActivatedRouteSnapshot,
                currentState: RouterStateSnapshot,
                nextState?: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    return component.canDeactivate();
  }
}

app.routing.module.ts

  ...
{ path: 'page1', component: Page1Component, canDeactivate: [CanDeactivateGuard]},
...

/page1/page1.component.ts

...
export class Page1Component implements OnInit, CanComponentDeactivate {
...
  canDeactivate(): Observable<boolean> | Promise<boolean> | boolean {
    console.log('JESSSS confirmLeavingValue', this.confirmLeavingValue);
    if(!this.confirmLeavingValue){
      return true;
    } else {
      return confirm('Do u want to discard the changes?');
    }
  }

app.module.ts

...
  providers: [AccountsService, LoggingService, CounterService , UsersService, AuthService, AuthGuard, CanDeactivateGuard],
...

Passing static data to a Route

  • We want to have a error page generic for any type of error.
  • And we pass the message to show by static data in the route.

app-routing.module.ts

...
{ path: 'not-found', component: ErrorPageComponent, data: {errorMessage: 'Page not found'}},
 // { path: 'not-found', component: PageNotFoundComponent},
...

error-page.component.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Data } from '@angular/Router';

@Component({
  selector: 'app-error-page',
  templateUrl: './error-page.component.html',
  styleUrls: ['./error-page.component.css']
})
export class ErrorPageComponent implements OnInit {

  errorMessage: string;
  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.errorMessage = this.route.snapshot.data['errorMessage'];
    this.route.data.subscribe(
      (data: Data)=> {
        this.errorMessage = data['errorMessage'];
      }
    );
  }

}

error-page.component.html

<h3> {{ errorMessage }}</h3>

Resolving dynamic data with the resolve guard

  • On page2/nested route we will simulate resolve data (a Server data) from a promise before nested component is loaded

example-resolver.service.ts

import {Resolve} from '@angular/Router';
import {Injectable} from '@angular/core';
import {Observable} from 'rxjs/Observable';

interface Server {
  id: number;
  name: string;
  status: string;
}

@Injectable()
export class ExampleResolver implements Resolve<Server> {

  constructor() {}

  resolve(): Observable<Server> | Promise<Server> | Server {

    return new Promise((resolve, reject) => {
      setTimeout(function(){
        resolve({
          id:1,
          name:'Señor potato',
          status:'single'
        });
      }, 250);
    });

  }

}

app-routing.module.ts

{ path: 'nested', component: NestedComponent, resolve: {server: ExampleResolver}}

nested.component.ts

import { Component, OnInit } from '@angular/core';

import {ActivatedRoute, Data} from '@angular/Router';

@Component({
  selector: 'app-nested',
  templateUrl: './nested.component.html',
  styleUrls: ['./nested.component.css']
})
export class NestedComponent implements OnInit {

  server: {id:number , name:string, status:string};

  constructor(private route:ActivatedRoute) { }

  ngOnInit() {
    this.route.data.subscribe(
      (data: Data) => {
        this.server = data['server'];
      }
    );
  }

}

nested.component.html

  <h4>Server</h4>
  <ul>
    <li>Id: {{ server.id }}</li>
    <li>Name: {{ server.name }}</li>
    <li>Status: {{ server.status }}</li>
  </ul>

Understanding Location strategies

  • Routes like ‘/page1’ or ‘/page2’ wont work on real web servers , they will get not found error because the server will look for page 1 or page 2 folder.
  • There is a chance to use hash mode (http://localhost:4200/#/page2) in the routes of our app:

app-routing.module.ts

@NgModule({
  imports: [
    RouterModule.forRoot(appRoutes, {useHash: true})
  ],
  exports: [RouterModule]

})

dfd

fd

fd

fd
gfgfd

 

fdf

df

df

df

df

d

Angular 2 – Using services and dependency injection

Injecting a custom service into components

  • A Service is created when we need a centralized component which serves some functionality to multiple components. Also we would expect to be a singleton instance.

app.component.html

      <button type="button" (click)="testLoggingService('one')" name="button">Test Logging Service One</button>
      <button type="button" (click)="testLoggingService('two')" name="button">Test Logging Service Two</button>

app.component.ts

import { Component } from '@angular/core';
import { LoggingService } from './logging.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [LoggingService]
})
export class AppComponent {
  myCondition = true;
  value=10;

  constructor(private LoggingService: LoggingService){}
  testLoggingService(msg) {
    this.LoggingService.logStatusChange(msg);
  }
}

logging.service.ts

export class LoggingService {
  logStatusChange(status: string) {
    console.log('A server JAJAJA JIJIJIJ JOJOJOJ' + status);
  }
}

Angular has a Hierarchical Injector for dependencies:

Captura de pantalla 2017-04-28 a las 11.41.31

WARNING: If we declare in [providers] a service in a child component which is declared in the parent component, we are overwritting the parent one , so they are two different instances.

For using a service declared in the parent component we still need to define it as a parameter in the constructor but we don’t need to declare it in the [providers] decorator.

The highest level to declare a dependency is in the AppModule.

Injecting Services into Services

  • We inject both services into AppModule.

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { BasicHighlightDirective } from './basic-highlight.directive';
import { BetterHighlightDirective } from './better-highlight.directive';
import { UnlessDirective } from './unless.directive';
import { AccountsService } from './accounts.service';
import { LoggingService } from './logging.service';

@NgModule({
  declarations: [
    AppComponent,
    BasicHighlightDirective,
    BetterHighlightDirective,
    UnlessDirective
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [AccountsService, LoggingService],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.html

      <button type="button" (click)="addAccount()" name="button">Add Account</button>
      <button type="button" (click)="removeAccount()" name="button">Remove Account</button>
      <ul>
       <li *ngFor="let account of accounts">Account #{{account.id}}</li>
      </ul>

app.component.ts

import { Component } from '@angular/core';
import { AccountsService } from './accounts.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  myCondition = true;
  value=10;
  accounts = this.AccountsService.accounts;

  constructor(private AccountsService: AccountsService){}
  addAccount() {
    this.AccountsService.addAccount();
  }
  removeAccount() {
    this.AccountsService.removeAccount();
  }
}

accounts.service.ts

import { Injectable } from '@angular/core';
import { LoggingService } from './logging.service';

@Injectable()
export class AccountsService {
  accounts = [];

  constructor(private logginService: LoggingService){}

  addAccount() {
    const newId = this.accounts.length;
    this.accounts.push({
      date: new Date().getTime(),
      id: newId
    });
    this.logginService.log('Account #' + newId + ' added!');
  }

  removeAccount() {
    this.accounts.splice(this.accounts.length-1,1);
    this.logginService.log('Account removed!');
  }
}

Cross Components Communication using services

accounts.service.ts

...
  sendMessage = new EventEmitter<string>();
...

component-one.component.ts

...
  constructor(private accountsService: AccountsService) { }
...

  emitEvent() {
    this.accountsService.sendMessage.emit('Hola caracola!!');
  }

component-one.component.html

<p>
  <button type="button" (click)="emitEvent()" name="button">Component One emits event to Component Two</button>
</p>

component-two.component.ts

import { Component, OnInit } from '@angular/core';
import { AccountsService } from '../accounts.service';

@Component({
  selector: 'app-component-two',
  templateUrl: './component-two.component.html',
  styleUrls: ['./component-two.component.css']
})
export class ComponentTwoComponent implements OnInit {

  receivedMsg:string = 'No message received yet';

  constructor(private accountsService: AccountsService) {
    this.accountsService.sendMessage.subscribe(
      (message: string) => {
        this.receivedMsg = message;
      }
    );
  }

  ngOnInit() {
  }

}

component-two.component.html

<p>
  Component Two receives event --> Message:{{receivedMsg}}
</p>

ds

dsd

sd

sd

sd

sd

sds

f

Angular 2 – Directives Deep dive

Attribute vs Structural Directives

  • In attribute directives you never modify the DOM structure , just change some behaviour in the element where it is added to. I looks like a normal HTML attribute (with databinding or property binding).
  • In Structural directives the DOM is modified. It also looks like a normal HTML attribute but it has a leading *. Affects the whole area in the DOM (elements get added / removed)

Creating a custom directive

app.component.html

<p appBasicHighlight>this is a basic highlight test</p>

basic-highlight.directive.js

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';
  }

}

Shortcut on ng cli to create a directive:

ng g d basic-highlight

Using the Renderer to build a better attribute directive

The problem with the former approach is that when we access the native element directly we are giving up on Angular’s DOM abstraction and miss out on the opportunity to be able to execute also in none-DOM environments such as: native mobile, native desktop, web worker or server side rendering.

Remember that Angular is a platform, and the browser is just one option for where we can render our app.

So what you do is to give this responsibility to the Renderer class.

better-highlight.directive.ts

import { Directive, Renderer2, OnInit, ElementRef } from '@angular/core';

@Directive({
  selector: '[appBetterHighlight]'
})
export class BetterHighlightDirective implements OnInit {

  constructor(private elRef: ElementRef, private renderer: Renderer2) { }

  ngOnInit() {
    this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'blue');
  }
}

Using HostListener to listen directive events

better-highlight.directive.ts

import { Directive, Renderer2, OnInit, ElementRef, HostListener } from '@angular/core';

@Directive({
  selector: '[appBetterHighlight]'
})
export class BetterHighlightDirective implements OnInit {

  constructor(private elRef: ElementRef, private renderer: Renderer2) { }

  ngOnInit() {
    // this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'blue');
  }

  @HostListener('mouseenter') mouseenter() {
    this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'blue');
  }
  @HostListener('mouseleave') mouseleave() {
    this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'transparent');
  }
}

Using HostBinding to Bind to element properties

better-highlight.directive.ts

import { Directive, Renderer2, OnInit, ElementRef, HostListener, HostBinding } from '@angular/core';

@Directive({
  selector: '[appBetterHighlight]'
})
export class BetterHighlightDirective implements OnInit {

  @HostBinding('style.backgroundColor') backgroundColor: string = 'transparent';

  constructor(private elRef: ElementRef, private renderer: Renderer2) { }

  ngOnInit() {
    // this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'blue');
  }

  @HostListener('mouseenter') mouseenter() {
    // this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'blue');
    this.backgroundColor = 'blue';
  }
  @HostListener('mouseleave') mouseleave() {
    // this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'transparent');
    this.backgroundColor = 'transparent';
  }
}

Binding to directive properties

better-highlight.directive.ts

import { Directive,
  Renderer2,
  OnInit,
  Input,
  ElementRef,
  HostListener,
  HostBinding } from '@angular/core';

@Directive({
  selector: '[appBetterHighlight]'
})
export class BetterHighlightDirective implements OnInit {

  @Input() defaultColor: string = 'transparent';
  @Input() highlightColor: string = 'blue';

  @HostBinding('style.backgroundColor') backgroundColor: string = 'transparent';

  constructor(private elRef: ElementRef, private renderer: Renderer2) { }

  ngOnInit() {
    this.renderer.setStyle(this.elRef.nativeElement, 'background-color', this.defaultColor);
  }

  @HostListener('mouseenter') mouseenter() {
    // this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'blue');
    this.backgroundColor = this.highlightColor;
  }
  @HostListener('mouseleave') mouseleave() {
    // this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'transparent');
    this.backgroundColor = this.defaultColor;
  }
}

app-component.html

      <p appBetterHighlight [defaultColor]="'yellow'" [highlightColor]="'red'" >this is a Better highlight test</p>

Structural directives: What happens behind the scenes

  • For example *ngIf directive it is translated in a ng-template with ngIf attribute behind the scenes:
<div *ngIf="condition"> ... </div>
<ng-template [ngIf]="condition"></ng-template>

Building a custom structural directive

unless.directive.ts

import { Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';

@Directive({
  selector: '[appUnless]'
})
export class UnlessDirective {

  @Input() set appUnless(condition: boolean) {
    if(condition) {
      this.vcRef.createEmbeddedView(this.templateRef);
    } else {
      this.vcRef.clear();
    }

  }
  constructor(private templateRef: TemplateRef<any>, private vcRef: ViewContainerRef) { }

}

app.component.html

      <p *appUnless="myCondition">Hola caracola (custom structural directive)</p>

Undestanding ngSwitch

      <div [ngSwitch]="value">
        <p *ngSwitchCase="5">Value is 5</p>
        <p *ngSwitchCase="10">Value is 10</p>
        <p *ngSwitchCase="15">Value is 15</p>
      </div>

gg

fg

fg

fg

fg

fg

gf

gf

gf

gf

g

gf

Angular2 – Introduction Notes

Angular CLI

  • Before install check latest nodejs version (>=v7.7.1)
  • npm install -g @angular/cli
  • Angular Command Line Interface for workflow commons tasks.
  • More info in https://github.com/angular/angular-cli/wiki
  • Create a new app skeleton:
  • ng new my-first-app
  • Init local development server:
  • ng serve

Integrating Bootstrap

  • Install bootstrap npm package
  • npm install --save bootstrap
  • Edit .angular-cli.json:
  •       "styles": [
            "../node_modules/bootstrap/dist/css/bootstrap.min.css",
            "styles.css"
          ],

How AngularJs is loaded and started

  • index.html
  • <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <title>MyFirstApp</title>
      <base href="/">
    
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link rel="icon" type="image/x-icon" href="favicon.ico">
    </head>
    <body>
      <app-root>Loading...</app-root>
    </body>
    </html>
  • main.ts
  • import { enableProdMode } from '@angular/core';
    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    
    import { AppModule } from './app/app.module';
    import { environment } from './environments/environment';
    
    if (environment.production) {
      enableProdMode();
    }
    
    platformBrowserDynamic().bootstrapModule(AppModule);
  • app/app.module.ts
  • import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    import { HttpModule } from '@angular/http';
    
    import { AppComponent } from './app.component';
    
    @NgModule({
      declarations: [
        AppComponent
      ],
      imports: [
        BrowserModule,
        FormsModule,
        HttpModule
      ],
      providers: [],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
  • app/app.component.ts
  • import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent {
      title = 'Dicen que dicen los que dicen que dicen!';
      name = 'El nuevo marketing de Podemos es la trama';
    }

     

Creating a custom component

  • Will create a new ‘app-server’ component to be place into main ‘app-root’ component
  • app/server/server.component.ts
  • import { Component } from '@angular/core';
    
    @Component ({
      selector: 'app-server',
      templateUrl: './server.component.html'
    })
    export class ServerComponent {}
  • app/server/server.component.html
  • <h3>Server Component</h3>
  • app/app.module.ts
  • import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    import { HttpModule } from '@angular/http';
    
    import { AppComponent } from './app.component';
    import { ServerComponent } from './server/server.component';
    
    @NgModule({
      declarations: [
        AppComponent,
        ServerComponent
      ],
      imports: [
        BrowserModule,
        FormsModule,
        HttpModule
      ],
      providers: [],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
  •  app/app.component.html
  • <input type="text" [(ngModel)]="name">
    <p>{{ name }}</p>
    <button type="button" class="btn btn-primary" name="button">Kaka de la vaca</button>
    <hr>
    <app-server></app-server>

Create a component with CLI

  • We can generate a new component from the terminal:
ng generate component servers
ng g c servers
  • it creates servers/servers.component.ts and related files

Databinding / String interpolation / Property binding

  • servers/servers.component.ts
  • import { Component, OnInit } from '@angular/core';
    
    @Component({
      selector: 'app-servers',
      templateUrl: './servers.component.html',
      styleUrls: ['./servers.component.css']
    })
    export class ServersComponent implements OnInit {
      allowNewServer = false;
      constructor() {
        setTimeout(() => {
          this.allowNewServer = true;
        },2000)
    
      }
    
      ngOnInit() {
      }
    
    }
  • servers/servers.component.html
  • <app-server></app-server>
    <app-server></app-server>
    <button type="button" class="btn btn-primary" [disabled]='!allowNewServer'>Add Server</button>
  • server.component.ts
  • import { Component } from '@angular/core';
    
    @Component ({
      selector: 'app-server',
      templateUrl: './server.component.html'
    })
    export class ServerComponent {
      serverId:number = 77;
      serverStatus:string = 'offline';
    
      getServerStatus() {
        return this.serverStatus;
      }
    }
  • server.component.html
  • <h3>Server {{serverId}} is {{getServerStatus()}}</h3>

Event Binding

  • servers/servers.component.html
  • <app-server></app-server>
    <app-server></app-server>
    <button
      type="button"
      class="btn btn-primary"
      [disabled]='!allowNewServer'
      (click)='onCreateServer()'>Add Server</button>
    {{createButtonClickedMessage}}
  • servers/servers.component.ts
  • import { Component, OnInit } from '@angular/core';
    
    @Component({
      selector: 'app-servers',
      templateUrl: './servers.component.html',
      styleUrls: ['./servers.component.css']
    })
    export class ServersComponent implements OnInit {
    
      allowNewServer = false;
      createButtonClickedMessage:string = 'No he pulsado el botón';
    
      constructor() {
        setTimeout(() => {
          this.allowNewServer = true;
        },2000)
    
      }
    
      onCreateServer() {
        this.createButtonClickedMessage = 'Ey , he pulsado el botón!'
      }
    
    
      ngOnInit() {
      }
    
    }

Built-in directives: ngIf, ngFor, ngStyle, ngClass

  • ngIf and ngFor are structural directives because changes the DOM, so will write them with asterisk: *:
  • app.component.html
  <button type="button" (click)="togglePassword()">Display Details</button>
  <p *ngIf="showPassword">Secret password: de danone</p>

  <div *ngFor="let log of logs">
    <p [ngStyle]="{backgroundColor: getColor(log.id)}" [ngClass]="{blanco: log.id > 4}">{{log.text}}</p>
  </div>
  • app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  // styleUrls: ['./app.component.css']
  styles: [`
    .blanco {
      color:#fff;
    }
  `]
})
export class AppComponent {
  logs=[];
  username='';
  usernameIsEmpty = true;
  resetUsername() {
    this.username = '';
  }
  showPassword=false;

  togglePassword() {
    this.showPassword=!this.showPassword;
    this.logs.push({
      text: 'clicked on ' + new Date().getTime(),
      id: this.logs.length
    });
  }

  getColor(id) {
    return (id < 5)? 'white':'blue';
  }

}
NOTE: All components , not depending of the nesting , are being referenced in the app.module.ts

Two ways of assigning a model value to a html property

  • Normal string interpolation
<img src="{{recipe.imagePath}}">
  • Property binding
<img [src]="recipe.imagePath"

A shorcut for defining models

export class Ingredient {
  constructor(public name:string, public amount:number) {}
}

this is equivalent to assign the parameters to the “this” attributes of the object.

Debugging

  • Typescript are not executed by the browser , it is compiled to regular javascript. But we can make debugging in the browser console:

Captura de pantalla 2017-04-15 a las 19.47.07

in the ‘Source’ tab we can inspect all the webpack items, including ts files. That is because of using sourcemaps.

  • There is a Chrome Extension called Augury ti inspect the angular app more in deep:

Captura de pantalla 2017-04-15 a las 19.56.06

Binding to custom properties: communication from parent component to child component / Input decorator

Child: Odd component

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-odd',
  templateUrl: './odd.component.html',
  styleUrls: ['./odd.component.css']
})
export class OddComponent implements OnInit {

  @Input() count: number;
  constructor() { }

  ngOnInit() {
  }

}

Parent : app.component

      <app-odd *ngFor="let odd of oddArray" [count]="odd"></app-odd>

Binding to custom events: communication from child component to parent component / Output decorator

Child component: game-control.component.ts

import { Component, OnInit,EventEmitter,Output } from '@angular/core';

@Component({
  selector: 'app-game-control',
  templateUrl: './game-control.component.html',
  styleUrls: ['./game-control.component.css']
})
export class GameControlComponent implements OnInit {
  intervalRef;
  count:number = 0;
  @Output() startGameEvent = new EventEmitter<{count: number}>();

  constructor() { }

  ngOnInit() {
  }

  onStartGame() {
    this.intervalRef = setInterval(() => {
      this.startGameEvent.emit({
        count: this.count++
      });
    },1000);
  }

  onStopGame() {
    clearInterval(this.intervalRef);
  }

}

Parent component: app.component

  <app-game-control (startGameEvent)="onStartGameClicked($event)"></app-game-control>

Understanding View Encapsulation

  • All components have their own css file and all the classes defined in that file only applies to that component and NOT are spread to their children.

Using local references in templates / ViewChild decorator

  • You can create local references in the templates to be only accesible for the component

https://angular.io/docs/ts/latest/guide/template-syntax.html#!#ref-vars

Projecting content into components with ng-content

  • With <ng-content></ng-content> you can reference the html snippet included between the start tag and the end tag of the component declaration.

Lifecycle hooks

ngIf example with else to show ng-template

<div class="row">
  <div class="col-md-5">
    <app-recipe-list (selectRecipeEvent2)="onRecipeSelected($event)"></app-recipe-list>
  </div>
  <div class="col-md-7">
    <app-recipe-detail
    *ngIf="recipeSelected; else infoText"
    [recipe]="recipeSelected"></app-recipe-detail>
    <ng-template #infoText>
      <p>Please select a recipe:</p>
    </ng-template>
  </div>
</div>

Ecmascript 6: Modules

[Fuente: http://exploringjs.com/es6/ch_modules.html]

16.1 Overview

JavaScript has had modules for a long time. However, they were implemented via libraries, not built into the language. ES6 is the first time that JavaScript has built-in modules.

ES6 modules are stored in files. There is exactly one module per file and one file per module. You have two ways of exporting things from a module. These two ways can be mixed, but it is usually better to use them separately.

16.1.1 Multiple named exports

There can be multiple named exports:

//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
    return x * x;
}
export function diag(x, y) {
    return sqrt(square(x) + square(y));
}

//------ main.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5

You can also import the complete module:

//------ main.js ------
import * as lib from 'lib';
console.log(lib.square(11)); // 121
console.log(lib.diag(4, 3)); // 5

16.1.2 Single default export

There can be a single default export. For example, a function:

//------ myFunc.js ------
export default function () { ··· } // no semicolon!

//------ main1.js ------
import myFunc from 'myFunc';
myFunc();

Or a class:

//------ MyClass.js ------
export default class { ··· } // no semicolon!

//------ main2.js ------
import MyClass from 'MyClass';
const inst = new MyClass();

Note that there is no semicolon at the end if you default-export a function or a class (which are anonymous declarations).

16.1.3 Browsers: scripts versus modules

Scripts Modules
HTML element <script> <script type="module">
Default mode non-strict strict
Top-level variables are global local to module
Value of thisat top level window undefined
Executed synchronously asynchronously
Declarative imports (importstatement) no yes
Programmatic imports (Promise-based API) yes yes
File extension .js .js

 

16.2 Modules in JavaScript

Even though JavaScript never had built-in modules, the community has converged on a simple style of modules, which is supported by libraries in ES5 and earlier. This style has also been adopted by ES6:

  • Each module is a piece of code that is executed once it is loaded.
  • In that code, there may be declarations (variable declarations, function declarations, etc.).
    • By default, these declarations stay local to the module.
    • You can mark some of them as exports, then other modules can import them.
  • A module can import things from other modules. It refers to those modules via module specifiers, strings that are either:
    • Relative paths ('../model/user'): these paths are interpreted relatively to the location of the importing module. The file extension .js can usually be omitted.
    • Absolute paths ('/lib/js/helpers'): point directly to the file of the module to be imported.
    • Names ('util'): What modules names refer to has to be configured.
  • Modules are singletons. Even if a module is imported multiple times, only a single “instance” of it exists.

This approach to modules avoids global variables, the only things that are global are module specifiers.

16.2.1 ECMAScript 5 module systems

It is impressive how well ES5 module systems work without explicit support from the language. The two most important (and unfortunately incompatible) standards are:

  • CommonJS Modules: The dominant implementation of this standard is in Node.js (Node.js modules have a few features that go beyond CommonJS). Characteristics:
    • Compact syntax
    • Designed for synchronous loading and servers
  • Asynchronous Module Definition (AMD): The most popular implementation of this standard is RequireJS. Characteristics:
    • Slightly more complicated syntax, enabling AMD to work without eval() (or a compilation step)
    • Designed for asynchronous loading and browsers

The above is but a simplified explanation of ES5 modules. If you want more in-depth material, take a look at “Writing Modular JavaScript With AMD, CommonJS & ES Harmony” by Addy Osmani.

16.2.2 ECMAScript 6 modules

The goal for ECMAScript 6 modules was to create a format that both users of CommonJS and of AMD are happy with:

  • Similarly to CommonJS, they have a compact syntax, a preference for single exports and support for cyclic dependencies.
  • Similarly to AMD, they have direct support for asynchronous loading and configurable module loading.

Being built into the language allows ES6 modules to go beyond CommonJS and AMD (details are explained later):

  • Their syntax is even more compact than CommonJS’s.
  • Their structure can be statically analyzed (for static checking, optimization, etc.).
  • Their support for cyclic dependencies is better than CommonJS’s.

The ES6 module standard has two parts:

  • Declarative syntax (for importing and exporting)
  • Programmatic loader API: to configure how modules are loaded and to conditionally load modules

16.3 The basics of ES6 modules

There are two kinds of exports: named exports (several per module) and default exports (one per module). As explained later, it is possible use both at the same time, but usually best to keep them separate.

16.3.1 Named exports (several per module)

A module can export multiple things by prefixing its declarations with the keyword export. These exports are distinguished by their names and are called named exports.

//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
    return x * x;
}
export function diag(x, y) {
    return sqrt(square(x) + square(y));
}

//------ main.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5

There are other ways to specify named exports (which are explained later), but I find this one quite convenient: simply write your code as if there were no outside world, then label everything that you want to export with a keyword.

If you want to, you can also import the whole module and refer to its named exports via property notation:

//------ main.js ------
import * as lib from 'lib';
console.log(lib.square(11)); // 121
console.log(lib.diag(4, 3)); // 5

The same code in CommonJS syntax: For a while, I tried several clever strategies to be less redundant with my module exports in Node.js. Now I prefer the following simple but slightly verbose style that is reminiscent of the revealing module pattern:

//------ lib.js ------
var sqrt = Math.sqrt;
function square(x) {
    return x * x;
}
function diag(x, y) {
    return sqrt(square(x) + square(y));
}
module.exports = {
    sqrt: sqrt,
    square: square,
    diag: diag,
};

//------ main.js ------
var square = require('lib').square;
var diag = require('lib').diag;
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5

16.3.2 Default exports (one per module)

Modules that only export single values are very popular in the Node.js community. But they are also common in frontend development where you often have classes for models and components, with one class per module. An ES6 module can pick a default export, the main exported value. Default exports are especially easy to import.

The following ECMAScript 6 module “is” a single function:

//------ myFunc.js ------
export default function () {} // no semicolon!

//------ main1.js ------
import myFunc from 'myFunc';
myFunc();

An ECMAScript 6 module whose default export is a class looks as follows:

//------ MyClass.js ------
export default class {} // no semicolon!

//------ main2.js ------
import MyClass from 'MyClass';
const inst = new MyClass();

There are two styles of default exports:

  1. Labeling declarations
  2. Default-exporting values directly
16.3.2.1 Default export style 1: labeling declarations

You can prefix any function declaration (or generator function declaration) or class declaration with the keywords export default to make it the default export:

export default function foo() {} // no semicolon!
export default class Bar {} // no semicolon!

You can also omit the name in this case. That makes default exports the only place where JavaScript has anonymous function declarations and anonymous class declarations:

export default function () {} // no semicolon!
export default class {} // no semicolon!
16.3.2.1.1 Why anonymous function declarations and not anonymous function expressions?

When you look at the previous two lines of code, you’d expect the operands of export default to be expressions. They are only declarations for reasons of consistency: operands can be named declarations, interpreting their anonymous versions as expressions would be confusing (even more so than introducing new kinds of declarations).

If you want the operands to be interpreted as expressions, you need to use parentheses:

export default (function () {});
export default (class {});
16.3.2.2 Default export style 2: default-exporting values directly

The values are produced via expressions:

export default 'abc';
export default foo();
export default /^xyz$/;
export default 5 * 7;
export default { no: false, yes: true };

Each of these default exports has the following structure.

export default «expression»;

That is equivalent to:

const __default__ = «expression»;
export { __default__ as default }; // (A)

The statement in line A is an export clause (which is explained in a later section).

16.3.2.2.1 Why two default export styles?

The second default export style was introduced because variable declarations can’t be meaningfully turned into default exports if they declare multiple variables:

export default const foo = 1, bar = 2, baz = 3; // not legal JavaScript!

Which one of the three variables foo, bar and baz would be the default export?

16.3.3 Imports and exports must be at the top level

As explained in more detail later, the structure of ES6 modules is static, you can’t conditionally import or export things. That brings a variety of benefits.

This restriction is enforced syntactically by only allowing imports and exports at the top level of a module:

if (Math.random()) {
    import 'foo'; // SyntaxError
}

// You can’t even nest `import` and `export`
// inside a simple block:
{
    import 'foo'; // SyntaxError
}

16.3.4 Imports are hoisted

Module imports are hoisted (internally moved to the beginning of the current scope). Therefore, it doesn’t matter where you mention them in a module and the following code works without any problems:

foo();

import { foo } from 'my_module';

16.3.5 Imports are read-only views on exports

The imports of an ES6 module are read-only views on the exported entities. That means that the connections to variables declared inside module bodies remain live, as demonstrated in the following code.

//------ lib.js ------
export let counter = 3;
export function incCounter() {
    counter++;
}

//------ main.js ------
import { counter, incCounter } from './lib';

// The imported value `counter` is live
console.log(counter); // 3
incCounter();
console.log(counter); // 4

How that works under the hood is explained in a later section.

Imports as views have the following advantages:

  • They enable cyclic dependencies, even for unqualified imports (as explained in the next section).
  • Qualified and unqualified imports work the same way (they are both indirections).
  • You can split code into multiple modules and it will continue to work (as long as you don’t try to change the values of imports).

16.3.6 Support for cyclic dependencies

Two modules A and B are cyclically dependent on each other if both A (possibly indirectly/transitively) imports B and B imports A. If possible, cyclic dependencies should be avoided, they lead to A and B being tightly coupled – they can only be used and evolved together.

Why support cyclic dependencies, then? Occasionally, you can’t get around them, which is why support for them is an important feature. A later section has more information.

Let’s see how CommonJS and ECMAScript 6 handle cyclic dependencies.

16.3.6.1 Cyclic dependencies in CommonJS

The following CommonJS code correctly handles two modules a and b cyclically depending on each other.

//------ a.js ------
var b = require('b');
function foo() {
    b.bar();
}
exports.foo = foo;

//------ b.js ------
var a = require('a'); // (i)
function bar() {
    if (Math.random()) {
        a.foo(); // (ii)
    }
}
exports.bar = bar;

If module a is imported first then, in line i, module b gets a’s exports object before the exports are added to it. Therefore, b cannot access a.foo in its top level, but that property exists once the execution of a is finished. If bar() is called afterwards then the method call in line ii works.

As a general rule, keep in mind that with cyclic dependencies, you can’t access imports in the body of the module. That is inherent to the phenomenon and doesn’t change with ECMAScript 6 modules.

The limitations of the CommonJS approach are:

  • Node.js-style single-value exports don’t work. There, you export single values instead of objects:
      module.exports = function () { ··· };
    

    If module a did that then module b’s variable a would not be updated once the assignment is made. It would continue to refer to the original exports object.

  • You can’t use named exports directly. That is, module b can’t import foo like this:
      var foo = require('a').foo;
    

    foo would simply be undefined. In other words, you have no choice but to refer to foo via a.foo.

These limitations mean that both exporter and importers must be aware of cyclic dependencies and support them explicitly.

16.3.6.2 Cyclic dependencies in ECMAScript 6

ES6 modules support cyclic dependencies automatically. That is, they do not have the two limitations of CommonJS modules that were mentioned in the previous section: default exports work, as do unqualified named imports (lines i and iii in the following example). Therefore, you can implement modules that cyclically depend on each other as follows.

//------ a.js ------
import {bar} from 'b'; // (i)
export function foo() {
    bar(); // (ii)
}

//------ b.js ------
import {foo} from 'a'; // (iii)
export function bar() {
    if (Math.random()) {
        foo(); // (iv)
    }
}

This code works, because, as explained in the previous section, imports are views on exports. That means that even unqualified imports (such as bar in line ii and fooin line iv) are indirections that refer to the original data. Thus, in the face of cyclic dependencies, it doesn’t matter whether you access a named export via an unqualified import or via its module: There is an indirection involved in either case and it always works.

React-LeafletJS

[Fuente: https://github.com/PaulLeCam/react-leaflet/blob/master/docs/Getting%20started.md]

Sobre React-Leaflet

Esta librería proporciona una abstracción de Leaflet en forma de React components.

No reemplaza a Leaflet, sólo se basa en los React’s lifecycle methods para invocar los handlers relevantes de Leaflet. Puedes leer más información sobre el proceso del ciclo de vida en How it works. Por favor asegúrate que entiendes todos los core concepts y limitations para evaluar si esta librería es apropiada para tus necesidades.

React-Leaflet permite convertir este ejemplo de código de la documentación de Leaflet:

 

import L from 'leaflet';

const position = [51.505, -0.09];
const map = L.map('map').setView(position, 13);

L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
  attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

L.marker(position).addTo(map)
  .bindPopup('A pretty CSS3 popup. <br> Easily customizable.');

a componentes React:

import React from 'react';
import { render } from 'react-dom';
import { Map, Marker, Popup, TileLayer } from 'react-leaflet';

const position = [51.505, -0.09];
const map = (
  <Map center={position} zoom={13}>
    <TileLayer
      url='http://{s}.tile.osm.org/{z}/{x}/{y}.png'
      attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
    />
    <Marker position={position}>
      <Popup>
        <span>A pretty CSS3 popup.<br/>Easily customizable.</span>
      </Popup>
    </Marker>
  </Map>
);

render(map, document.getElementById('map-container'));

Notar que el  <Map> component crea su propio <div> container para el mapa, no está linkado a un nodo existente.

Se puede ver este ejemplo en this jsfiddle.

Leaflet setup

If you are not familiar with Leaflet, make sure you read its quick start guide before using this library.
You will notably need to add its CSS to your page to render the map properly, and set the height of the <Map>container.

All components are React wrappers for Leaflet elements and layers, they need a map instance and therefore must be included in a top-level <Map> component.

Installation

Using npm

npm install react-leaflet@next

React, ReactDOM and Leaflet are peer dependencies, if you haven’t already installed them you can use:

npm install leaflet@1.0.0-rc.2 react react-dom react-leaflet@next

Cómo funciona

Conceptos claves

React-Leaflet usa los métodos del ciclo de vida de React (React’s lifecycle methods) para invocar a los handlers relevantes de Leaflet, lo cual tiene unas cuentas consecuencias:

DOM rendering

React no renderiza layers de Leaflet en el DOM, este renderizado es hecho por Leaflet por si mismo. React sólo renderiza el map container dentro del Map component y potenciales elementos <div> vacíos en componentes que tengán varios hijos.

Component properties

Las properties que se pasan a los componentes son utilizados para crear la intancia Leaflet correspondiente cuando el componente sea montado. Cuando se añada un componente, todas estas propiedades deben ser soportadas si la versión de Leaflet la soporta, pero no serán actualizadas en el UI cuando cambian a menos que sean referenciados en la documentación como dynamic.

Component context

React-Leaflet utiliza el API context de React (context API) para fabricar los elementos Leaflet disponibles a otros elementos que lo necesiten.

Si creas custom components, necesitas acceder a las siguientes instancias desde el contexto:

  • map: The Leaflet.Map instance created by the <Map> component.
  • layerContainer: The containing layer, for example a LayerGroup instance. Defaults to the map value if no other container is set.
  • popupContainer: The layer that could contain a popup.

Lifecycle process

  1. El Map de nivel más alto renderiza un <div> vacío para contener el mapa.
  2. El método componentDidMount() de Map instancia un Leaflet.Map() para el <div> creado con las component properties y setea la instancia en ese estado. Esta instancia es hecha disponible en el contexto.
  3. El método render() de Map es ejecutado otra vez, esta vez renderinzando sus componentes hijos.
  4. Para cada componente hijo, se invocará el método componentWillMount() e instanciará la instancia Leaflet correspondiente para este elemento utilizando las propiedades del componente y su contexto.
  5. El método render() es invocado par cada hijo, que retornará o null o renderizará sus propios hijos.
  6. Cuando el método componentDidUpdate de un componente es invocado , actualiza su intancia Leaflet de acuerdo a sus propiedades dinámicas soportadas.
  7. Cuando el método componentWillMount de un componente es llamado, elimina su layer del map.

Limitations

  • Leaflet hace llamadas directas al DOM cuando es cargado, por lo tanto esta librería no es compatible con server-side rendering.
  • Los componentes expuestos son abstracciones para los Leaflet layers, y no de elementos DOM. Algunos de ellos tienen propiedades que pueden ser actualizadas directamente invocando los métodos setters expuestos por Leaflet mientras otros deben ser completamente reemplazados , configurando un valor único en su propiedad key de forma que pueda ser manejado apropiadamente por el algoritmo de React.

dsd
f
df
df
df
df
df
df
df
dfd
fd
fd
fd
fd
fd
fd
fd
fd
fdf
df
df
df
df
df