08 iulie 2020

Pași pentru folosire branch itrac

> git checkout -b feature/itrac-11111 master  -- crearea unui branch nou cu id-ul de pe itrac, din master
> git add .
> git commit -m "itrac-11111 your message"
> git push --set-upstream origin feature/itrac-11111

Bitbucket -> Repositories -> navigare repo corespunzător -> Branches -> Pull request

01 iulie 2020

Validare xml cu xsd in Java

private static void initialize() throws SAXException { 
    final SchemaFactory factory = 
        SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 
    final InputStream xsdStream = XmlDocumentValidator.class.getClassLoader()
        .getResourceAsStream(SCHEMA_PATH);            
    final Schema schema = factory.newSchema(new StreamSource(xsdStream)); 
    validator = schema.newValidator();      
    validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); 
    validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); 

public static boolean validateSchema(Resource resource) { 
    try (InputStream inputStream = resource.getInputStream()) { 
        if (validator == null) { 
            initialize(); 
        } 
        validator.validate(new StreamSource(inputStream)); 
        return true; 
    } catch (final SAXException | IOException e) { 
        LOGGER.error("Schema validation failed", e); 
        return false; 
    } 
}

26 iunie 2020

Reveng tool pentru actualizarea claselor java corelate cu baza de date

1. modificare hibernate-reveng.xml, adaugare declaratii:
<schema-selection match-schema="SCHEMA_NAME" match-table="TABLE_NAME" />
<table name="TABLE_NAME" schema="SCHEMA_NAME">
  <primary-key>
    <key-column name="PK_COLUMN_NAME" />
  </primary-key>
</table>

2. Realizarea conexiunii cu BD (hibernate.properties)
hibernate.connection.driver_class=oracle.jdbc.OracleDriver
hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
hibernate.connection.url=jdbc:oracle:thin:@//URL:PORT/DB_NAME
hibernate.connection.username=OWNER
hibernate.connection.password=PASS

3. Rularea plugin-ului
> mvn clean generate-sources -P reveng

4. Commit fisiere modificate, punctual

15 aprilie 2020

Obtinerea resurselor de test in Java

Presupunem un director cu calea: src/test/resources/fileprocessor/source

1. ClassLoader:

final String SOURCE_PATH = "fileprocessor/source";
sourceFolder = new File(this.getClass().getClassLoader().getResource(SOURCE_PATH).getFile());


2. Obtinerea caii relativ la proiect:

private static final String SOURCE_PATH = "./target/test-classes/fileprocessor/source";
sourceFolder = new File(SOURCE_PATH);
P.S. Nu folosim pe Bamboo file.getAbsolutePath() !!!

11 aprilie 2020

Angular - ziua 10 - Componente dinamice

Componente dinamice - sunt create la runtime, momentul cand vor trebui sa fie afisate este controlat programatic, cu *ngIf sau cu Dynamic Component Loader

Dynamic Component Loader: componenta este adaugata la DOM prin cod (imperativ) (vs. declarativ, printr-un selector, la *ngIf)
Totusi, varianta prin *ngIf este mai usoara si cea recomandata.

Exemplu cu *ngIf pentru afisarea unei ferestre de eroare:

1. se creeaza o componenta noua, numita de exemplu AlertComponent, cu satelitii aferenti, si se importa in app.module.ts langa celelalte componente.
@Component({
  selector: 'app-alert',
  templateUrl: './alert.component.html',
  styleUrls: ['./alert.component.css']
})

2. In componenta se adauga:
@Input() message: string; // string settable from outside
@Output() closeEvent = new EventEmitter<void>(); // emits on onClose() for the outside

3. In Html, fereastra de alerta are un buton cu textul "Close", se adauga: (click)="onClose()" pentru a declansa emitter-ul din Typescript.

4. Componenta de alert este folosita in componenta din care vrem s-o chemam, cu *ngIf, trimitandu-i drept input [message] o variabila, iar pe output-ul (closeEvent) devenit input in acea componenta, se merge pe o metoda onHandleError din componenta curenta:
<app-alert [message]="error" *ngIf="error" (closeEvent)="onHandleError()"></app-alert>

5. In onHandleError() avem grija sa schimbam conditiile pentru care *ngIf este true, deci putem seta this.error = null acolo.

----

Varianta programatica ( mai grea, pentru acelasi scenariu )

1. Se creeaza o componenta noua, adnotata cu @Directive, care va reprezenta definitia unui obiect in DOM (de fapt gata sa fie introdus in DOM). ViewContainerRef reprezinta "un container unde se pot adauga una sau mai multe view-uri la componenta"

@Directive({
  selector: '[appPlaceholder]'})
export class PlaceholderDirective {
  constructor(public viewContainerRef: ViewContainerRef) {
  }
}

2. In Html-ul componentei unde vrem sa atasam componenta dinamica, declaram (este doar o declaratie, nu apare in DOM):
<ng-template appPlaceholder></ng-template> 

3. In TypeScript-ul componentei de mai sus vom realiza adaugarea programatica. Dar intai declaram variabilele clasei:
@ViewChild(PlaceholderDirective) alertHost: PlaceholderDirective;
private closeSub: Subscription;

4. Se injecteaza in constructor: private componentFactoryResolver: ComponentFactoryResolver

Intr-o metoda de showErrorAlert():
5. Se obtine din alertHost proprietatea de tipul ViewContainerRef declarata, se face clear() pe ea

6. Din "fabrica" de componente se obtine sub-fabrica de tipul AlertComponent:
const alertCompFactory = this.componentFactoryResolver.resolveComponentFactory(AlertComponent);

7. Se creeaza componenta dinamica cu ajutorul ei si se populeaza campul message cu ceva ce ar trebui sa primim ca parametru in metoda:
const componentRef = hostViewContainerRef.createComponent(alertCompFactory);
componentRef.instance.message = errorResponse;

8. Se face subscribe pe pe proprietatea closeEvent a componentei, care se salveaza intr-o subscriptie:
this.closeSub = componentRef.instance.closeEvent.subscribe(() => {
  this.closeSub.unsubscribe();
  hostViewContainerRef.clear();
});

Asadar, cand se apasa pe "Close", subscriptia in curs se termina, iar componenta se "curata" astfel disparand de pe ecran.

9. Metoda de mai sus este chemata pe ramura de tratare a erorii dupa login/logout.

08 aprilie 2020

Angular - ziua 9 - Autentificare

Autentificare si protejarea rutelor

- RESTful API is stateless => nu se poate folosi sesiunea, ci: clientul va trimite datele de identificare, iar serverul raspunde cu un token. Clientul salveaza acel token si il foloseste in cereri ulterioare catre server.

- Pregatire server Firebase:
{
  "rules": {
    ".read": 'auth != null',
    ".write": 'auth != null'
  }
}
Authentication -> Set up sign-in method -> Email/password -> Enable

Lucrul cu cereri de autentificare in Firebase:
- se copiaza url-ul generic pentru sign up de la Firebase (la momentul de fata este https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=)
- se adauga la el API key-ul aflat in project overview (Web API Key)
- se trimite o cerere de POST catre acel url nou-format, cu datele: {email: your-email, password: your-password, returnSecureToken: true}
- cererea de POST trebuie sa fie de tipul: post<AuthResponseData>, unde tipul raspunsului este definit de Firebase si este tradus de noi intr-o interfata Typescript astfel:
interface AuthResponseData {
  kind: string;
  idToken: string;
  email: string;
  refreshToken: string;
  expiresIn: string;
  localId: string;
  registered?: boolean; // optional: only for the sign in request
}
- se procedeaza asemanator si pentru log in - folosind celalalt URL specific (https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=) si aceeasi cheie


Variabile cu underscore

export class User {
  constructor(public email: string, public id: string,
              private _token: string, private _tokenExpirationDate: Date) {
  }

  get token() {
    if (!this._tokenExpirationDate || new Date() > this._tokenExpirationDate) {
      return null;
    }
    return this._token;
  }
}

Ce inseamna:
- se poate accesa pe variabila user.token pentru a executa metoda de get
- user.token nu se poate asigna
- metoda get token (getter) defineste pasii pentru obtinerea token-ul


Construirea unui utilizator (Sign up):

return this.httpClient
  .post<AuthResponseData>(url, this.getAuthData(email, password))
  .pipe(catchError(this.handleError), tap(resData => {
          // resData contine expiresIn, adica nr de secunde pt care e valabil token-ul
         const expirationDate = new Date(new Date().getTime() + +resData.expiresIn * 1000);
        // resData mai contine si id-ul local in Firebase, cat si token-ul
        const user = new User(resData.email, resData.localId, resData.idToken, expirationDate);
        this.user.next(user);
}));

// unde user: Subject<User>;

- se procedeaza si la login la fel ca la signup.


Operatori rxjs (lista completa aici)

* pipe - un fel de stream() din Java 8, poate primi ca argument alti operatori rxjs, se poate chema pe un Observable, ca rezultat aduce fluxul de valori

* tap - un fel de forEach din Java, se foloseste cu pipe ...  ex: clicks.pipe(tap(ev => console.log(ev))); Poate avea efecte laterale, daca schimbi ceva in ev.

* map - asemanator cu map din Java, primeste o singura valoare din sir si poate face ceva cu ea, fara efecte laterale ... ex: from([1, 2, 3]).pipe(map(item => item + 2)).subscribe(item => console.log(item));

* take - primeste ca argument un numar N, emite doar primele N valori emise de Observabil, nu asculta la infinit ...  ex: interval(1000).pipe(take(3)).subscribe(x => console.log(x)); // 0, 1, 2

* exhaustMap - asteapta primul Observable sa termine, apoi ia urmatoarea valoare (???)
ex: const exhaustSub = firstInterval.pipe( 
        exhaustMap(f => { console.log(''); return secondInterval; }) 
      );


return this.authService.user.pipe(take(1), exhaustMap(user => {
  const urlWithToken = this.URL + '?auth=' + user.token;
  return this.httpClient.get<Recipe[]>(urlWithToken);
})

BehaviorSubject - un Subject care da acces si la valoarea anterioara emisa (cu next), chiar daca nu a fost facut subscribe la acel moment (este folosit cu exhaustMap)
Exemplu: user = new BehaviorSubject<User>(null); // null e prima valoare initiala


Interceptori

- se poate folosi pentru a atasa fiecarei cereri token-ul de care are nevoie

- se creeaza ca serviciu:

@Injectable()
export class AuthInterceptorService implements HttpInterceptor {

  constructor(private authService: AuthService) {
  }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return this.authService.user.pipe(take(1),
      exhaustMap(user => {
        if (!user) {
          return next.handle(req); // unchanged
        }
        // adds token to all outgoing requests
        const modifiedReq = req.clone({params: new HttpParams().set('auth', user.token)});
        return next.handle(modifiedReq);
      })
    );
  }
}

- se adauga in app.module.ts -> providers, ca: {provide: HTTP_INTERCEPTORS, useClass: AuthInterceptorService, multi: true}

Logoutthis.user.next(null);
De asemenea se curata Local Storage de orice informatie despre user: localStorage.clear();
Alternativ, daca vrem sa stergem doar informatia adaugata explicit despre user: localStorage.removeItem('userData');

Auto-login: prin cookies sau prin Local Storage API furnizat de browser

Pentru varianta Local Storage API:
1. se cheama in Typescript: localStorage.setItem('userData', JSON.stringify(user)); pentru a salva datele despre utilizator la momentul crearii lui
2. intr-o metoda autoLogin() din serviciul de autentificare, se  obtin datele inapoi prin: JSON.parse(localStorage.getItem('userData')); Se reconstituie utilizatorul si se emite BehaviorSubject-ului user (recomandabil daca apeland token() nu se primeste null, deci daca inca are token-ul valid).
3. in app.component.ts se implementeaza OnInit, iar in ngOnInit() se cheama metoda autoLogin() din serviciul de autentificare

Auto-logout: la expirarea token-ului (default dupa o ora in cazul Firebase).

1. se creeaza o metoda in serviciul de autentificare in care, in plus, salvam timer-ul intr-o variabila globala:
autoLogout(duration: number) {
  this.tokenExpirationTimer = setTimeout(() => {
    this.logout();
  }, duration);
}

2. aceasta metoda este chemata de fiecare data cand se face login sau auto-login in serviciu

3. in metoda de logout() avem grija sa distrugem timer-ul:
if (this.tokenExpirationTimer) {
  clearTimeout(this.tokenExpirationTimer);
}
this.tokenExpirationTimer = null;


Protejarea rutelor - unele pagini nu a ar trebui sa fie accesibile utilizatorilor nelogati

1. se creeaza un nou serviciu care implementeaza CanActivate. Metoda canActivate() va returna un boolean, corespunzand permisiunii de a accesa o anumita pagina (true pentru utilizatorii logati).

@Injectable({providedIn: 'root'})
export class AuthGuard implements CanActivate {

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

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot)
                    : Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {

      return this.authService.user.pipe(map(user => !!user)); // true if user exists, false otherwise
  }
}

2. in app-routing.module.ts, pe linia componentei care ne intereseaza se adauga: canActivate: [AuthGuard]

3. Pentru redirectare in functie de permisiune, se adauga:

return this.authService.user.pipe(
  map(user => !!user),  // true if user exists, false otherwise  tap(auth => {
    if (!auth) {
      this.router.navigate(['/auth']);
    }
  }));

Alternativ cu UrlTree (un alt output posibil al metodei canActivate()):

return this.authService.user.pipe(
  take(1),
  map(user => {
    const isAuth = !!user;
    if (isAuth) {
      return true;
    }
    return this.router.createUrlTree(['/auth']);
  }));

!!! take(1) s-a folosit pentru a lua doar ultimele valori introduse, pentru ca garda sa nu continue sa asculte la infinit

01 aprilie 2020

Angular - ziua 8 - Cereri HTTP

Cereri HTTP
Angular poate comunica prin intermediul HTTP Requests/Responses catre un Server (API - REST, GraphQL) care la randul sau comunica cu o baza de date.
Documentatia oficiala aici.

Cererea HTTP:
- URL
- HTTP verb: POST, GET, PUT
- Headers (metadata): {"Content-Type" : "application/json"}
- Body: {title : "New Post"}

Solutie de serviciu backend:  Firebase de la Google
- Realtime database (start in test mode)
- URL-ul furnizat poate fi URL-ul la care se pot trimite cereri din Angular

Pasi pentru a folosi HttpClient:
1. app.module.ts -> imports -> HttpClientModule

2. HttpClient se injecteaza in constructorul clasei unde dorim sa-l folosim

3. Trimiterea unei cereri de POST: this.httpClient.post(url: String, postData: object); 
// in functie de url, Firebase va aseza datele intr-o structura de dosare
// postData va fi convertit automat intr-un json

!!! post() intoarce un Observable. Simpla chemare a lui post() nu va declansa nimic, dupa principiul: daca nimeni nu este interesat (= subscription), nu se executa. Asadar:

this.httpClient.post(url: String, postData: object).subscribe(responseData => {
  // do something
});
// nu este nevoie de unsubscribe() pentru ca este un Observable administrat de Angular


GET
this.httpClient.get(url).subscribe(posts => {
  // do something
});


Prelucrarea datelor:

this.httpClient
  .get(url)
  .pipe(map(post => {
    // prelucreaza & return data
  }))
  .subscribe(posts => {
    // do something
  });


Tipizare:

this.httpClient
  .get<{ [key: string]: Post }>(url)
  .pipe(map(post => {
    // process & return data
  }))
  .subscribe(posts => {
    // do something
  });

- unde: [key: string] inseamna orice proprietate cu rol de cheie (nu ii cunoastem numele, este generata la intamplare de Firebase), iar Post este tipul valorii furnizate de aceasta bucata de date
- tipizarea este valabila si pentru alte metode, cum ar fi post()

Se recomanda ca actiunile Http sa se realizeze in servicii.
- fie adnotate cu @Injectable({providedIn: 'root'})
- fie se inscriu in app.module.ts -> providers
- se poate face subscribe concomitent in serviciu (pentru ca actiunea sa se execute), si in componenta care cheama serviciul (doar daca o intereseaza raspunsul)


DELETE
this.httpClient.delete(url, object).subscribe( () => { // empty response
  // here you can set up some flags
});

!!! Pentru separarea intre componenta/serviciu, in serviciu se face delete (fara subscribe), iar in componenta se menajeaza variabilele.


Tratarea erorilor:

error: String;

// ...

this.httpClient.get(url).subscribe(posts => {
  // do something
  this.error = null;
}, error => {
  this.error = error.message;
});

Alt mod de a trata erorile, in serviciu:

error = new Subject<string>();

this.httpClient.post(url, postData).subscribe(responseData => {
  // ..
}, error => {
  this.error.next(error.message);
});

In componenta, in ngOnInit():
this.service.error.subscribe(errorMessage -> {
  this.error = errorMessage;
});

// in acest caz se cuvine sa faci unsubscribe, in ngOnDestroy()


Operatorul catchError:

this.httpClient
  .get(url)
  .pipe(map(post => {
    // prelucreaza & return data
  }), catchError(errorRes => {
    // generic error handling task, like logging / analytics
    throwError(errorRes); // it needs to be able to reach subscribe again
  }))
  .subscribe(posts => {
    // do something
  });


Antete, query params
this.httpClient.get(url, {
  headers: new HttpHeaders({'Custom-Header': 'Hello'}),
  params: new HttpParams().set('print', 'pretty') // echivalent cu url/page.json?print=pretty
}); // pt GET antetul trimis va fi al doilea argument

Alternativ, pentru query params:
let searchParams = new HttpParams(); // obiect imutabil
searchParams = searchParams.append('print', 'pretty');
searchParams = searchParams.append('custom', 'key');
// params: searchParams 

Observare:
this.httpClient.post(url, data, {observe: 'response'}); // response, body, events
this.httpClient
  .delete(url, {observe: 'events'})
  .pipe(tap(event => { // tap nu intrerupe felul cum decurge observabilul, primeste doar events
    if (event.type === HttpEventType.Response) { // verifica daca raspunsul s-a primit
      console.log(event.body);
    }
  })); 

Schimbarea tipului de raspuns:
this.httpClient
  .delete(url, {
    observe: 'events',
    responseType: 'json' // text, blob, etc
  })


Interceptori

export class AuthInterceptorService implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    // next e o functie
    console.log('Request is on its way');
    return next.handle(req); // let the request leave the app
  }
}

In app.module.ts -> providers -> [{provide: HTTP_INTERCEPTORS, useClass: AuthInterceptorService, multi: true}]

Manipulare Request (care este imutabil):
const modifiedRequest = req.clone({
  headers: req.headers.append('Auth': 'xyz')
});
return next.handle(modifiedRequest);

Interceptori pentru raspuns - in return se manipuleaza raspunsul:
return next.handle(req).pipe(tap(event => {
  if (event.type === HttpEventType.Response) {
    // console.log (event.body);
  }
}));

30 martie 2020

Angular - ziua 7 - Pipes

Pipes
- transforma output
- lista de pipe-uri oferite de Angular se poate consulta aici

// Html: 
<p> {{ username | uppercase }} </p>
// alte exemple de pipes: lowercase, dateTime, date, etc.
<p> {{ user.date | date:'fullDate' }} </p>

Pipe-uri inlantuite:
<p> {{ user.date | date:'fullDate' | uppercase }} </p>

Pipe-uri personalizate:
@Pipe({
  name: 'shorten'
})
export class ShortenPipe implements PipeTransform {
  transform(value: any) {
    return value.substr(0, 10) + '...';
  }
}

// se adauga in app.module.ts -> declarations

// Html:
<p> {{ name | shorten  }} </p>

Parametrizare:
@Pipe({
  name: 'shorten'
})
export class ShortenPipe implements PipeTransform {
  transform(value: any, limit: number) { // se pot adauga mai multe filtre, ca parametri
    if (value.length > limit) {
      return value.substr(0, limit) + '...';
    }
    return value;
  }
}

// Html:
<p> {{ name | shorten:5  }} </p>

Generare sablon pentru un pipe personalizat:
> ng generate pipe

!!! Filtrul, odata aplicat, nu este rulat din nou daca se schimba datele (Array sau Object), din motive de performanta. 
Pentru a forta sa se aplica filtrul, se adauga:
@Pipe({
  name: 'shorten',
  pure: false
})

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

// Html
<h2> {{ appStatus }} </h2> // va afisa [object Object], nu se uita daca obiectul se transforma dupa 2 sec

Pentru update dupa 2 secunde (merge si pentru Observables):
<h2> {{ appStatus | async }} </h2>

24 martie 2020

Angular - ziua 6 - Formulare

Formulare - 2 abordari:
- bazata pe template - Angular deduce obiectul formularului, din DOM
- reactiva - formularul este creat in mod programatic si este sincronizat cu DOM-ul

TEMPLATE

In app.module.ts, @NgModule -> imports -> FormsModule

<form (ngSubmit)="onSubmit(f)" #f="ngForm">
  <input type="text" id="username" name="username" ngModel>
</form>

In TypeScript:

onSubmit (form: NgForm) { // NgForm este un formular creat automat de Angular
     console.log(form.value['username']);
    // sau form.value.username
}

Altfel - daca vrei sa accesezi formularul nu numai la completarea lui, ci si inainte:
@ViewChild('f') signupForm: NgForm;
// ngModel aplicat pe tag-ul html iti da voie sa recuperezi obiectul in TypeScript
// onSubmit() poate deveni acum fara parametru de intrare

Cateva din alte proprietati furnizate de NgForm:
- dirty: daca formularul are vreun camp schimbat
- enabled, disabled
- valid, invalid
- touched: daca a fost facut click pe formular

Validare input:
- adaugare required in fisierul html, pe input-ul dorit
- alti validatori: email, lista completa aici => validitatea se va reflecta in atributele din NgForm
- Angular adauga dinamic clase css pe input-uri care reflecta starea, folosindu-le putem stiliza input-urile pentru a avertiza utilizatorul:

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

Afisare mesaje de eroare:
<form (ngSubmit)="onSubmit(f)" #f="ngForm">
    <input type="text" id="myemail" name="myemail" ngModel #email="ngModel">
</form>
<span class="help-block" *ngIf="!email.valid && email.touched">Please enter valid email</span>

Inserare valori initiale:
defaultQuestion='pet';

<select id='secret' [ngModel]="defaultQuestion" name="secret">
  <option value="pet">What was your first pet?</option>
  <option value="teacher">What was your first teacher?</option>
</select>


Valori predefinite in TypeScript

@ViewChild('f') myForm: NgForm;
this.myForm.setValue ({
  username: 'myChoice',
  secret: 'pet'
  // trebuiesc adaugate toate campurile
});

// suprascrie doar campurile care ne intereseaza:
this.myForm.form.patchValue({
  userData: {
    username: 'something'
  }
});

Curatare formular: this.myForm.reset();

Two-way binding:
<textarea name="questionAnswer" rows="3" [(ngModel)]="answer"/>
<p> Your reply: {{answer}} </p>
// unde answer trebuie sa fie declarat ca empty string in TypeScript


ngModelGroup - pentru a se aplica la mai mult de un singur camp

<div ngModelGroup="userData" #userData="ngModelGroup">
<!-- campuri -->
</div>
<p *ngIf="!user.Data.valid && userData.touched">User data invalid!</p>


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

ABORDAREA REACTIVA

- nu mai este nevoie de import FormsModule, se inlocuieste cu ReactiveFormsModule

signupForm: FormGroup;

ngOnInit() {
  this.signupForm = new FormGroup({
    'username': new FormControl(null),
    'email': new FormControl(null),
    'gender': new FormControl('male')
  });
}

onSubmit() { }

// Html:
<form (ngSubmit)="onSubmit()" [formGroup]="signupForm">
    <input type="text" id="username" formControlName="username">
    <input type="text" id="email" formControlName="email">
    <input type="radio" id="gender" formControlName="gender">
</form>

Validare prin metoda reactiva:  

this.signupForm = new FormGroup({
    'username': new FormControl(null, Validators.required), // referinta la metoda de validare
    'email': new FormControl(null, [Validators.required, Validators.email]),
    'gender': new FormControl('male')
});

Afisarea validarilor
<span *ngIf="!signupForm.get('username').valid && signupForm.get('username').touched">Please enter a valid username!</span>

Grupare reactiva
- elementele se aseaza intr-un <div formGroupName="userData">
- se actualizeaza span cu noua cale catre elementul validat:
<span *ngIf="!signupForm.get('userData.username').valid && signupForm.get('userData.username').touched">Please enter a valid username!</span>


FormArray









<div formArrayMode="hobbies">
  <h4>Your hobbies</h4>
  <button type="button" (click)="onAddHobby()">Add hobby</button>
  <div class="form-group" *ngFor="let hobbyControl of getControls(); let i = index">
    <input type="text" [formControlName]="i">
  </div>
</div>

// TypeScript:
ngOnInit() {
  this.signupForm = new FormGroup({
    'username': new FormControl(null),
    'email': new FormControl(null),
    'gender': new FormControl('male'),
    'hobbies': new FormArray([])
  });
}

getControls() {
  return (<FormArray>this.signupForm.get('hobbies')).controls;
}
onAddHobby() {
  const control = new FormControl(null, Validators.required);
  (<FormArray>this.signupForm.get('hobbies')).push(control); // explicit casting
}

Creare propriii validatori

forbiddenNames(control: FormControl): {(s: string): boolean} { // format output: {'mesaj de eroare': true}
  if (this.forbiddenUsernames.indexOf(control.value) !== -1) {
    return {'name is forbidden': true};
  }
  return null; // validation is successful
}

this.signupForm = new FormGroup({
    'username': new FormControl(null, [Validators.required, this.forbiddenNames.bind(this)]),
    'email': new FormControl(null, [Validators.required, Validators.email]),
    'gender': new FormControl('male'),
    'age': new FormControl(null, Validators.pattern(/^[1-9]+[0-9]+$'/))
});
// binding-ul este pt a ne asigura ca this in metoda este ceea ce trebuie sa fie

Angular adauga erorile pe fiecare Control -> errors:
<span *ngIf="signupForm.get('userData.username').errors['name is forbidden']">This name is invalid!</span>

Validatori asincroni

forbiddenEmails(control: FormControl): Promise<any> | Observable<any> {
  const promise = new Promise<any>((resolve, reject) => {
    setTimeout(() -> {
      if (control.value === 'someAddress@yahoo.com') {
        resolve({'emailIsForbidden': true});
      } else {
        resolve(null);
      }
    }, 1500); // simulare asincronicitate
  });
  return promise;
}

// Html:
this.signupForm = new FormGroup({
    'username': new FormControl(null, [Validators.required, this.forbiddenNames.bind(this)]),
    'email': new FormControl(null, [Validators.required, Validators.email], this.forbiddenEmails.bind(this)), // al 3-lea arg este rezervat pt validatori asincroni
    'gender': new FormControl('male')
});

// TypeScript:
this.signupForm.valueChanged.subscribe(value => console.log(value));
this.signupForm.statusChanged.subscribe(status=> console.log(status)); // VALID, INVALID, PENDING (async)

setValue() si patchValue() sunt in continuare disponibile pe obiectul FormGroup.
reset() de asemenea.

19 martie 2020

Angular - ziua 5 - Observables

Observable - o sursa de date; declansat de un eveniment creat de utilizator; declansat de Http requests; poate fi declansat programatic;
--------
-- Observable --> .... stream .... -> Observer
--------
Observer-ul poate sa prelucreze datele, erorile, sau sa coordoneze terminarea, in fiecare dintre aceste actiuni se poate scrie cod (asincron) care sa fie executat.

Exemple: la un route: ActivatedRoute => route.params este un Observable, este un stream de parametri care ofera un nou parametru de fiecare data cand se schimba URL-ul
- la observabile poti doar sa faci subscribe(...)
- rolul lor cel mai important este sa emita date

Construirea unui observabil de la 0

import { interval } from 'rxjs';

// intr-o clasa:
ngOnInit() {
    interval(1000).subscribe(count => { console.log(count); } ); // primim un eveniment in fiecare secunda
}

!!! Trebuie facut unsubscribe daca observabilul nu mai este de interes (salvare Subscription intr-o variabila, si apelarea unsubscribe() in onDestroy()). Altfel, la reinitializarea componentei, se va adauga de fiecare data un nou observabil pe langa cele deja existente.
!!! Exceptie de la aceasta regula fac toti observabilii furnizati de Angular (cum ar fi route.params si altii) pentru care nu trebuie sa se apeleze unsubscribe().

Construire cu adevarat de la 0:

import { Observable } from 'rxjs';

ngOnInit() {
    const customIntervalObservable = Observable.create(observer => { 
        let count = 0;
        setInterval(() => { observer.next(count); count++; }, 1000) ;
    });

    // folosire:
    customIntervalObservable.subscribe(data => { console.log(data); });
}

// 1000 este timeout-ul
// observer.next() emite o noua valoare; se poate folosi alternativ observer.error() sau observer.complete()

Tratarea erorilor -> observer.error(new Error(' etc '));
- observabilul moare dupa ce emite o eroare (nu mai e nevoie de unsubscribe), totusi nu e acelasi lucru cu terminarea
- prindere: customIntervalObservable.subscribe(data => { console.log(data); , error => { console.error("Caught it"); } );

Terminarea unui observabil -> observer.complete();
- prindere: customIntervalObservable.subscribe(data => { console.log(data); , error => { console.error("Caught it"); }, () -> { console.log('do some cleanup'); } );
- nu este nevoie de unsubscribe()

Operatori - ajuta la construirea unor pasi prin care vrei sa obtii datele
- exemple: map, filter, expand, etc.

import { map } from 'rxjs/operators';

// in ngOnInit()
customIntervalObservable.pipe( map((data: number) => { return 'Round ' + (data + 1); }) ).subscribe(....);

customIntervalObservable.pipe( filter(...), map(...) ).subscribe(....);

Subiecti - un fel de Observable, dar sunt mai activi pentru ca se poate chema metoda next() pe ei din exterior (pe cand in observabile, next() se poate apela doar din interior)
- fiind tot niste observabile, se poate apela in continuare subscribe() si, de asemenea, unsubscribe()
- se recomanda folosirea subiectilor in detrimentul EventEmitter-ilor (mai putin cand obiectul este si un Output())
- subiectii sunt pentru comunicarea intre componente prin servicii, prin mecanisme la care se face subscribe() manual.

// in user.service.ts
import { Subject } from 'rxjs';

activatedEmitter = new Subject<boolean>(); // putea fi EventEmitter<boolean>

// Html:
<button (click)="onActivated()" />

// Ts:
onActivate() {
    this.userService.activatedEmitter.next(true); // era emit(true) pt EventEmitter
}

---
Sursa: Udemy, section 13+14

17 martie 2020

Angular - ziua 4 - Routing

Routing - functionalitate oferita de Angular pentru a da impresia mai multor pagini pe care  poate naviga un utilizator. In realitate este o singura pagina pe care se plimba mai multe componente.

Pasii pentru inregistrarea rutelor:

1. Rutele trebuie declarate in app.module.ts, exemplu:

const appRoutes = Routes [
    { path: '', component: HomeComponent },
    { path: 'users', component: UsersComponent }
]

2. In app.module.ts -> @NgModule -> imports, se adauga: RouterModule.forRoot(appRoutes)

3. In fisierul html se adauga: <router-outlet></router-outlet> in locul componentelor care fusesera adaugate folosind selectori (adica in exemplu, in locul lui <app-home> si <app-users>).

La acest moment, accesarea paginii de baza (http://localhost:4200/ si http://localhost:4200/users va duce utilizatorul catre cele 2 componente, dar nu exista navigatie in interior).

4. Adaugarea navigatiei:
- actualizare cu <a href="/"> sau <a href="/users" in html => dezavantaj ca se va face refresh la pagina de fiecare data cand se acceseaza acele link-uri
- inlocuire <a href> cu <a routerLink="/users"> pentru a scapa de refresh. Alternativa este <a [routerLink]="['/users']">

Alternativ (primii 2 pasi):
1. Outsourcing - se creeaza un nou modul "app-routing.module.ts", in care se declara const appRoutes ca la pct.1 (dar in afara  definitiei clasei). Clasa va fi adnotata cu:
@NgModule({
  imports: [RouterModule.forRoot(appRoutes)],
  exports: [RouterModule]
})
2. In app.module.ts -> @NgModule -> imports, se adauga AppRoutingModule

Tips<li routerLinkActive="active"> marcheaza ruta activa (via Bootstrap). Se adauga pe <li> care detine link-ul pe care s-a navigat.

Rutare programatica
Se injecteaza Router => se adauga Router in constructor, dupa care se poate folosi intr-o metoda din Typescript cu sintaxa: this.router.navigate(['/users']);

Cai relative: in constructor se injecteaza in plus route: ActivatedRoute
this.router.navigate(['/users'], {relativeTo: this.route});

Cai dinamice:
const appRoutes : Routes [
    { path: '', component: HomeComponent },
    { path: 'users', component: UsersComponent }
    { path: 'users/:id', component: UsersComponent }
]
Acum se poate accesa un anume utilizator cu id=3 la http://localhost:4200/users/3:
- id este un camp dintr-o variabila user definita in UsersComponent.
- acestei componente i se injecteaza in constructor route: ActivatedRoute
- id-ul se poate obtine apoi prin this.routes.snapshot.params['id'];
Se pot adauga si alte elemente dinamice:  { path: 'users/:id/:name', component: UsersComponent }

!!! Pentru actualizarea obiectului din Typescript cand se apeleaza un link asemanator cu rutare, pentru ca schimbarile sa se propage in template, se adauga: this.route.params.subscribe( (params: Params) -> { this.user.id = params['id']; });
!!! Pentru ca subscriptia sa nu traiasca la nesfarsit in ciuda distrugerii componentei din care face parte, se poate salva ca variabila, iar in ngOnDestroy() se va apela: this.subscription.unsubscribe();

Cai care contin parametri de tip query:
<a [routerLink]="['/users']" [queryParams]="{allowEdit: '1'}" fragment="loading" href="#"> // va duce catre http://localhost:4200/users?allowEdit=1#loading

Echivalent in Typescript: this.router.navigate(['/users'], {queryParams: {allowEdit: '1'}}, fragment: 'loading');

Accesul la parametrii de tip query (non-reactiv la schimbari):
- se injecteaza route: ActivatedRoute in constructor
- se acceseaza prin: this.route.snapshot.queryParams si this.route.snapshot.fragment
Reactiv: se apeleaza subscribe() pe cele 2 variabile, in ngOnInit().

Tips: const id = +this.route.snapshot.params['id']; // va cauza ca id-ul sa fie interpretat ca un numar si nu un string (default)

Nested routes:
const appRoutes : Routes [
    { path: '', component: HomeComponent },
    { path: 'users', component: UsersComponent, children: [
        { path: ':id', component: UsersComponent }
    ]}
]

Pastrarea parametrilor query - this.router.navigate(['/users'], {relativeTo: this.route, queryParamsHandling: 'preserve'});  // optiuni: merge, preserve

Redirectari pentru pagini invalide - se adauga in app.module.ts:

const appRoutes : Routes [
    { path: '', component: HomeComponent },
    { path: 'users', component: UsersComponent },
    { path: 'not-found', component: PageNotFoundComponent },
    { path: '**', redirectTo: '/not-found' }
]

'**' este wildcard pt orice altceva. Ordinea este importanta (redirectarea va fi intotdeauna ultima ruta adaugata)
Tips: { path: '', redirectTo: '/somewhere-else', pathMatch: 'full' } pentru ca '' sa nu mai faca match cu absolut orice

Route Guards - functionalitati care se executa inainte sa se incarce o ruta, sau imediat dupa parasirea ei.

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

Apoi,  pentru a proteja rutele:
const appRoutes : Routes [
    { path: '', component: HomeComponent },
    { path: 'users', canActivate: [AuthGuard], component: UsersComponent } // va fi accesibila doar daca canActivate() din AuthGuard intoarce true
}

Protejarea rutelor-copii: AuthGuard va implementa si interfata CanActivateChild -> se suprascrie metoda canActivateChild cu aceiasi parametri, in care doar se cheama canActivate(...)

const appRoutes : Routes [
    { path: '', component: HomeComponent },
    { path: 'users', canActivateChild: [AuthGuard], component: UsersComponent, children: [
        { path: ':id', component: UsersComponent }
    ]}
]

Transmiterea de date statice unei rute:

1. in declararea rutei se adauga:
const appRoutes : Routes [
    { path: '', component: HomeComponent },
    { path: 'users', component: UsersComponent },
    { path: 'not-found', component: PageNotFoundComponent, data: {message: 'Page not found!'} },
    { path: '**', redirectTo: '/not-found' }
]

2. in TypeScript se injecteaza un ActivatedRoute, iar in ngOnInit() se adauga: this.errorMessage = this.route.snapshot.data['message'];
Eventual se poate inlocui cu urmatoarea linie, daca vrem sa fim la curent cu schimbarile:
this.route.data.subscribe( (data: Data) => { this.errorMessage = data['message']; } );

Transmiterea de date dinamice unei rute:

1. in declararea rutei se adauga:

const appRoutes : Routes [
    { path: '', component: HomeComponent },
    { path: 'users', component: UsersComponent, resolve: {detectie: DetectionResolver} }
]
// aici detectie este un keyword ales

2. se declara DetectionResolver ca implementand Resolve<MyDataType>, in care trebuie implementata metoda resolve:

resolve (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<MyDataType> | Promise<MyDataType> | MyDataType {
    // aici se poate folosi un serviciu injectat deja in constructor pentru a putea aduce date din exterior
    // se va returna obiectul MyDataType continand informatiile dorite (sau un Observable, sau un Promise)
}

3. in TypeScript-ul componentei, in care s-a injectat un route: ActivatedRoute, se poate acum obtine obiectul MyDataType cu informatiile dorite, folosind in cazul de mai sus: this.route.snapshot.data['detectie'];

---
Sursa: cap. 11-12 Udemy

16 martie 2020

Angular - ziua 3

Directiva:
- de atribute: afecteaza un singur element, cel pe care este pus
- structurale: afecteaza mai multe elemente din DOM, contine * (exemplu *ngIf, *ngFor) - o singura directiva structurala are voie sa existe pe un element

Exemplu in TypeScript:

@Directive({
    selector: '[appBasicHighlight]' // va fi recunoscut si fara paranteze patrate, ca un atribut
})
export class BasicHighlightDirective implements OnInit{
    constructor(private elementRef: ElementRef) {}

    ngOnInit(private elementRef: ElementRef) {
        this.elementRef.nativeElement.style.backgroundColor = 'green'; // setare stil
    }
}

- se adauga in app.module.ts -> declarations, numele BasicHighlightDirective
- utilizarea in Html: <p appBasicHighlight></p>


Renderer - o abordare mai buna pentru accesul la DOM, detalii despre api-ul Renderer2 aici

@Directive({
    selector: '[appBetterHighlight]'
})
export class BetterHighlightDirective implements OnInit{
    constructor(private elementRef: ElementRef, private renderer: Renderer2) {}

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

<p appBetterHighlight></p>


HostListener

@HostListener('mouseenter')  mouseover (eventData: Event){
     this.renderer.setStyle(...);
}
@HostListener('mouseleave')  mouseleave (eventData: Event){
     this.renderer.setStyle(...);
}

Alias-urile de la @HostListener sunt standard, specifica trigger-ul.


HostBinding - pentru scurtarea accesului la proprietati

@HostBinding('style.backgroundColor') backgroundColor: string; // alias-ul specifica proprietatea elementului (locatia din DOM)

@HostListener('mouseenter')  mouseover (eventData: Event){
     this.backgroundColor = 'blue'; // => se acceseaza mai usor stilul
}


Directiva ngSwitch

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


Serviciile - o clasa in Typescript, fara directive 

In componenta unde vrei sa folosesti un serviciu, se adauga tipul in @Component -> providers. Apoi serviciul trebuie injectat in constructor (nu se instantiaza): constructor(private service: MyService) {}
Instanta salvata din constructor va putea fi folosita oriunde in restul codului din acel ts.

Injectie la nivel de:
- AppModule: serviciul e vizibil in intreaga aplicatie. Se adauga in providers.
- AppComponent: serviciul e vizibil in toate componentele (dar nu in alte servicii)
- alta componenta: serviciul e vizibil in acea componenta si in toti copiii ei

@Injectable() - se adauga intr-un serviciu in care se poate injecta alt serviciu (cand te astepti sa se injecteze ceva).