Skip to content

2.1 Prima Componenta

Irina edited this page Nov 10, 2018 · 7 revisions

1. Prima componenta

In aceasta sectiune vom crea o prima componenta in aplicatia noastra.

1.1 Generarea componentei

Pentru a genera automat o componenta (adica sa nu creem noi de mana toate fisierele si sa scriem codul de la 0) vom folosi Angular CLI.

Ruleaza urmatoarea comanda in terminal:

ng generate component Hero

Rularea acestei comenzi are 2 efecte:

  1. a creat pentru noi 3 fisiere in folder-ul hero

  • hero.component.ts - logica componentei (cum se comporta)
  • hero.component.html - ce elemente vizuale contine componenta (structura vizuala)
  • hero.component.css - cum arata elementele vizuale din componenta (stilurile componentei)
  1. A inregistrat componenta in modulul aplicatiei (fisierul src/app/app.module.ts), dupa ce a importat-o pentru a putea fi folosita.

Valideaza ca si la tine codul arata la fel si apoi treci la urmatorul punct.

1.2 Folosirea componentei in aplicatie

Pentru a putea folosi componenta tocmai creata, trebuie sa o includem in template-ul unei alte componente. In cazul de fata trebuie sa adaugam un tag <jsh-hero> in template-ul componentei AppComponent. Prefixul jsh este adaugat automat numelui componentei pentru a evita coliziunea de nume cu elemente standard de html.

Numele tagului vine din proprietatea selector aflata in decoratorul componentei:

Inlocuiti codul existent in fisierului src/app/app.component.html cu urmatoarea linie de cod:

<jsh-hero></jsh-hero>

Daca salvati si dati refresh la browser (daca nu a facut asta deja automat), veti vedea noua componenta:

De unde vine acest text? Daca deschideti fiserul src/app/hero/hero.component.html veti gasi exact sursa textului afisat pe ecran.

<p>
hero works!
</p>

Incercati sa modificati textul cu numele vostru (sau cu orice alt text), salvati si urmariti rezultatul in browser.

1.3 Binding de proprietati prin interpolare

De cele mai multe ori in aplicatiile noastre vom dori ca textul dintr-o componenta sa fie dinamic (sa se schimbe in functie de indeplinirea unor conditii). Pentru asta ne vom folosi de mecanismul de data-binding. Pentru asta este nevoie de o variabila (care va contine textul dinamic) pe care sa o folosim in template.

  • In componenta HeroComponent (fisierul src/app/hero/hero.component.ts) adaugati o proprietate numita name pe care o initializati cu numele vostru:
export class HeroComponent implements OnInit {
    public name = 'Andrei'; // adaugati linia aceasta de cod

    ... 
}
  • Pentru a afisa valoarea variabilei, in template-ul componentei HeroComponent (fisierul src/app/hero/hero.component.html) adaugati numele variabilei intre perechi de acolade ({{name}}) si inlocuiti codul existent.
<p>
  Hello {{name}}!
</p>

Perechile de acolade ii semnalizeaza lui Angular sa inlocuiasca numele variabilei cu valoarea ei din cod:

In browser ar trebui sa vedeti schimbarea:

De asemenea daca modificati valoarea din cod (de exemplu, din Andrei in George) ar trebui sa observati modificarea. Marele castig in acest caz este ca nu mai trebuie sa editati template-ul (sau design-ul componentei) daca textul ce il contine se modifica.

1.4 Basic event binding

In alte situatii ne dorim ca in momentul in care apare un eveniment generat de utilizator (click, input, scroll etc.) sa rulam o anumita bucata de cod. Aceasta se realizeaza prin legarea evenimentului in template de o metoda a clasei componentei.

De exemplu, vrem ca in momentul in care apasam pe un buton, sa apara un alert cu numele nostru (de ex. Hello Andrei).

  • In componenta HeroComponent (fisierul src/app/hero/hero.component.ts) adaugati o metoda numita greet care va alerta numele aflat in proprietatea name:
sayHello() {
    alert('Hello ' + this.name);
}
  • In template-ul componentei HeroComponent (fisierul src/app/hero/hero.component.html) adaugati un buton care sa aibe ca text Say Hello.
<button>Say Hello</button>

Daca rulati acum aplicatia si apasati pe butonul nou creat, veti vedea ca nu se intampla inca nimic. Pentru a lega actiunea de click la functia din clasa mai este necesar un pas. Trebuie sa ii spunem lui Angular ce eveniment atasat elementului respectiv este asociat carei actiuni din clasa. Modificati butonul recent adaugat cu urmatorul cod:

<button (click)="sayHello()">Say Hello</button>
  • (click) - parantezele rotunde semnaleaza lui Angular ca este vorba despre un binding de eveniment, iar in interiorul parantezelor este numele evenimentului ce declanseaza actiunea (in cazul de fata, actiunea este click pe buton)
  • ="sayHello()" - in partea dreapta a egalului, intre ghilimele se afla numele functiei ce urmeaza sa fie chemata in momentul in care este declansat evenimentul. Numele trebuie neaparat sa fie o functie declarata in clasa componentei, altfel se va ivi o eroare.

Daca rulati din nou aplicatia veti vedea ca in momentul in care veti da click, va fi afisat numele stocat in variabila name intr-o alerta:

Exercitiu bonus: Afisati in template un numar, si in momentul in care dati click pe un buton, cresteti numarul cu 1.

1.5 Two way binding

Pana in momentul de fata am descoperit ca exista 2 modalitati de comunicare intre template-ul unei componente si clasa sa. Prin afisarea in template a proprietatilor din clasa (clasa -> template : property binding) si prin semnalarea evenimentelor din template catre clasa (template -> clasa : event binding). Diagrama de mai jos ilustreaza aceste 2 moduri de comunicare clasa/template:

Dar mai exista si o a treia modalitate ce implica o comunicare bidirectionala intre clasa si template.

  • Adaugati in template-ul componentei un element input:
<input>
  • Deocamdata inputul este gol. Pentru a-l initializa cu valoarea variabilei name adaugam un property binding. Binding-ul il vom realiza cu ajutorul proprietatii ngModel. Fara a intra prea mult in detalii, ngModel este o directiva care ii spune elementului <input> de la ce proprietate a clasei sa isi ia valoarea. Adaugati binding-ul la element:
<input [ngModel]="name">

Daca ati intampinat o eroare, aceasta se datoreaza faptului ca nu am inclus in aplicatie modulul ce se ocupa cu prelucrarea formularelor. ngModel este o directiva ce este inclusa in modulul FormsModule si nu poate fi folosita fara acesta.

  • Pentru a adauga modulul, trebuie mai intai importat in AppModule (in fisierul src/app/app.module.ts). Adaugati urmatoarea linie de cod pe primul rand al fisierului:
import { FormsModule } from '@angular/forms';

apoi adaugati modulul in array-ul imports din decoratorul modulului:

@NgModule({
    declarations: [ ... ],
    imports: [
        BrowserModule,
        FormsModule
    ],
    ...
})

(insert poza cu codul final al modulului si highlight pe importuri)

Daca rulati acum codul, aplicatia ar trebui sa mearga din nou. Insa daca schimbam textul din input, nimic nu se intampla.

  • Pentru a ii semnaliza lui Angular ca vrem ca proprietatea la care facem bind sa se schimbe odata cu evenimentul de input, trebuie sa creem un nou tip de binding: two-way binding. Pentru aceasta folosim urmatoarea notatie: [(ngModel)]="name"

  • (ngModel) - schimba textul din directia template clasa (cand se introduce un caracter de la tastatura)

  • [ngModel] - schimba proprietatea de la clasa la template (seteaza valoarea initiala sau daca o schimba programatic, din codul clasei)

  • ="name" - in partea dreapta avem intre ghilimele numele proprietatii din clasa la care vrem sa facem binding. Nu uitati ca aceasta trebuie sa fie un nume valid de proprietate, altfel veti avea o eroare.

Astfel, inlocuiti elmentul <input> cu urmatorul cod:

<input [(ngModel)]="name">

Iar acum daca incercati sa modificati numele din input veti vedea ca aceasta modificare este reflectata atat in template cat si daca apasati pe buton (numele va fi actualizat cu valoarea schimbata):

1.6 Adaugarea detaliilor unui erou in componenta

Acum ca ne-am familiarizat cu felul in care functioneaza o componenta, sa incepem sa adaugam detaliile corespunzatoare unui erou in componenta HeroComponent. Pentru aceasta trebuie mai intai sa ne gandim ce fel de informatii am vrea sa afisam pentru un erou:

  1. Nume - numele de scena al eroului (de ex. Batman)
  2. Alter ego - adevarata identitate (secreta) a eroului (de ex. Bruce Wayne)
  3. Scurta descriere - o scurta descriere a eroului si a superputerilor acestuia (de ex. Bla bla bla despre Batman)
  4. Imaginea eroului - calea catre o poza cu eroul
  5. Buton pentru chemarea eroului - cand apasam acest buton, un mesaj va fi trimis sa cheme eroul, si va afisa o alerta care sa confirme acest lucru (de ex. 'Batman a fost chemat')

Pentru inceput, sa concepem html-ul (structura vizuala) pentru componenta. Trebuie sa afisam fiecare informatie din cele de sus folosind elemente html.

  • Inlocuiti continutul template-ului (fisierul src/app/hero/hero.component.html) cu urmatorul markup:
<div class="hero">
  <!-- IMAGE -->
  <div class="photo">
    <img src="assets/batman.jpg">
  </div>
  <div class="details">
    <!-- NAME -->
    <span class="detail-title">Name</span>
    <p class="detail-text">Batman</p>
    <!-- ALTER EGO -->
    <span class="detail-title">Alter ego</span>
    <p class="detail-text">Bruce Wayne</p>
    <!-- DESCRIPTION -->
    <span class="detail-title">Description</span>
    <p class="detail-text">Bla bla despre Batman</p>
    <!-- BUTTON -->
    <button class="btn btn-xl btn-purple">CALL HERO</button>
  </div>
</div>

Acesta se gaseste la bit.ly/codepaste1

Rezultatul ar trebui sa arate ceva asemantor cu:

Observati faptul ca fiecare element are aplicat pe el o clasa, pentru a putea stiliza mai usor. Si apropo de stiluri, desi avem toate informatiile necesare pe ecran, componenta nu arata foarte atragator din punct de vedere vizual. Pentru a imbunatati situatia, putem adauga urmatorul cod CSS in fisierul de stilizare al componentei (src/app/hero/hero.component.css):

.hero {
  min-width: 50vw;
  padding: 16px;
  margin: 16px 16px 64px 16px;
  border-radius: 2px;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
}

.photo {
  float: left;
  margin: 0 24px 8px 0;
}

.detail-title {
  font-weight: bold;
  font-family: 'Oswald', sans-serif;
}

img {
  width: 270px;
  margin-right: 10px;
}

p {
  margin-top: 8px;
}

Codul se gaseste la bit.ly/codepaste2

La final, rezultatul, imbunatatit, ar trebui sa arate asemanator cu:

Am reusit sa definim ce date ar trebui sa se afiseze si cum ar trebui sa fie prezentate (stilul vizual). Insa, felul in care am implementat componenta nu este unul deosebit de flexibil intrucat daca am dori sa modificam informatiile eroului, ar trebui sa modificam direct textul static din template. Iar scopul nostru final nu este sa definim cate o componenta pentru fiecare erou, ci mai degraba sa creem o componenta pe care sa o refolosim. Si asa cum am aratat mai devreme, de cele mai multe ori datele se vor gasi in obiecte, in clasa componentei.

1.7 Modelul de date pentru erou

Primul pas pentru a imbunatati componenta este sa mutam datele (numele, alter-ego, descrierea si sursa pozei) in clasa componentei si sa folosim binding-ul pentru a le afisa. Desi prima intuitie ar fi sa creem cate o proprietate pentru fiecare, exista o modalitate mai buna

  • Trebui sa creem o clasa care sa defineasca modelul de date pentru erou. Pentru a crea un fiser care sa contina o clasa in proiect, folositi urmatoarea comanda in terminal:
ng generate class Hero

Aceasta comanda va crea un fiser numit hero.ts in folderul src/app/. Daca inspectam continutul fiserului, vom vedea ca avem deja definitia clasei Hero

export class Hero {
}

In continuare trebuie sa adaugam proprietatile ce definesc un erou, asa cum le-am expus mai sus. Proprietatile definite pe clase se pot defini ca parametrii publici ai constructorului si astfel acestea vor fi create automat. Completati definitia clasei astfel:

export class Hero {
  constructor(
    public name: string,
    public alterEgo: string,
    public description: string,
    public photo: string,
  ) {}
}

In acest moment putem defini obiecte de tip Hero folosind operatorul new in acest fel:

const hero = new Hero(
    'Batman',
    'Bruce Wayine',
    'Bla bla bla despre Batmal',
    'assets/batman.jpg'
)

De asemenea mai putem crea obiecte de tip Hero si folosind declaratia directa de obiect:

const hero: Hero = {
    name: 'Batman',
    alterEgo: 'Bruce Wayine',
    description: 'Bla bla bla despre Batmal',
    photo: 'assets/batman.jpg'
}

Rezultatul celor doua este acelasi, in ambele cazuri creandu-se un obiect ce respecta semnatura tipului Hero.

1.8 Adaugarea modelului de date la componenta

In acest moment avem un model pentru tipul Hero, mai ramane doar sa adaugam un obiect de acest tip in componenta noastra pentru a-l afisa.

  • Inainte de a incepe, sa facem putina curatenie in componenta (in fisierul src/app/hero/hero.component.ts):
    • stergeti proprietatea name
    • stergeti functia sayHello()

Componenta voastra ar trebui sa arate cam asa:

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

@Component({
  selector: 'jsh-hero',
  templateUrl: './hero.component.html',
  styleUrls: ['./hero.component.css']
})
export class HeroComponent implements OnInit {
  constructor() { }

  ngOnInit() { }
}
  • Pentru a putea folosi proaspat creata clasa Hero in componenta noastra trebuie mai intai sa importam fiserul. Adaugati urmatoarea declaratie la inceputul fisierului:
import { Hero } from '../hero';
  • Adaugati o proprietate publica numita hero de tipul Hero in clasa HeroComponent
export class HeroComponent implements OnInit {
  public hero:Hero;
  ...
}
  • In interiorul functiei ngOnInit() initializati proprietatea cu un obiect de tip Hero:
ngOnInit() {
  this.hero = new Hero(
    'Batman',
    'Bruce Wayne',
    'Bla bla bla despre Batman',
    'assets/batman.jpg'
  );
}

1.9 Binding pe proprietatile elementelor html

Avem in acest moment datele pentru erou pe proprietatea hero in componenta. A mai ramas un singur pas pentru a le afisa pe ecran: binding la date in template.

  • Pentru a inlocui numele static din template si a aduce valoarea proprietatii name de pe hero trebuie sa inlocuim textul din template cu o interpolare ({{...}}) pentru proprietate. Astfel in template (in fisierul src/app/hero/hero.component.html) :
<p class="detail-text">Batman</p>

devine

<p class="detail-text">{{hero.name}}</p>

Daca dati refresh la pagina, veti vedea ca nimic nu s-a schimbat la pagina voastra, insa implicatiile inlocuirii textului static cu interpolarea sunt foarte importante. Pentru a ilustra acest fapt, la instantierea obiectului hero, inlocuiti numele eroului din Batman in Wonder Woman

ngOnInit() {
    this.hero = new Hero(
      'Wonder Woman',
    ...
    );
}

Observati in browser ca acum numele s-a modificat insa este foarte important de remarcat ca aceasta modificare s-a produs fara a mai modifica template-ul - lucru ce face componenta mult mai utila pentru ca o putem folosi pentru orice structura de date de tip Hero.

  • Inlocuiti si celelalte texte statice cu interpolarile proprietatilor:

    • {{hero.alterEgo}}
    • {{hero.description}}
  • Ultimul detaliu static ramas este poza. Pentru a rezolva, intr-o prima faza ne putem folosi tot de mecanismul interpolarii si inlocui textul static cu o binding la proprietatea photo de pe proprietatea hero. Astfel, in template inlocuim:

<img src="assets/batman.jpg">

cu

<img src={{hero.photo}}>

Desi aceasta varianta functioneaza ok, exista un mecanism mai elegant pe care Angular ni-l pune la dispozitie pentru astfel de cazuri, si anume binding la proprietatile elementelor html. Daca la binding-ul de evenimente foloseam perechea de paranteze rotunde ( () ), in acest caz se foloseste o pereche de paranteze patrate ( [] ). Astfel, putem rescrie codul de afisare al imaginii in felul urmator:

<img [src]="hero.photo">
  • [src] - paranteza patrata in jurul unei proprietati a elementului html (trebuie sa fie o proprietate valida altfel vom avea o eroare) semnalizeaza lui Angular ca ceea ce va veni in partea dreapta a expresiei nu trebuie interpretat ca un simplu string, ci ca un nume de proprietate de pe clasa componentei a carei valori va fi preluata.
  • ="hero.photo" - in partea dreapta, avem intre ghilimele numele proprietatii din clasa la care vrem sa facem binding. Nu uitati ca aceasta trebuie sa fie un nume valid de proprietate, altfel veti avea o eroare.

Daca reveniti in browser, din nou nu veti vedea nicio diferenta fata de varianta statica, insa diferenta este una esentiala pentru felul in care veti putea folosi mai departe componenta.

Un ultim aspect (estetic) pe care il vom aplica este sa afisam numele eroului doar cu litere mari, chiar daca in obiect textul contine si litere mici. Pentru aceasta, Angular vine din nou cu un mecanism ajutator numit Pipe. Pipe-urile sunt reprezentate in cod prin caracterul | iar rolul lor este sa proceseze un input prin aplicarea unei functii. Din punct de vedere al sintaxei, ar arata in felul urmator:

<p class="detail-text">{{hero.name | uppercase}}</p>
  • uppercase - este doar o functie de procesare, ce primeste ca argument elementul din stanga caracterului |

Exista mai multe pipe-uri predefinite in angular (pentru transformare de numere, valuta sau date) dar avem posibilitatea sa creem si noi propriile noastre pipe-uri.

La final template-ul componentei va arata aproximativ in acest fel:

<div class="hero">
  <!-- IMAGE -->
  <div class="photo">
    <img [src]="hero.photo">
  </div>
  <div class="details">
    <!-- NAME -->
    <span class="detail-title">Name</span>
    <p class="detail-text">{{hero.name | uppercase}}</p>
    <!-- ALTER EGO -->
    <span class="detail-title">Alter ego</span>
    <p class="detail-text">{{hero.alterEgo}}</p>
    <!-- DESCRIPTION -->
    <span class="detail-title">Description</span>
    <p class="detail-text">{{hero.description}}</p>
    <!-- BUTTON -->
    <button class="btn btn-xl btn-purple">CALL HERO</button>
  </div>
</div>

1.10 Exercitiu

  • Implementati functionalitatea butonului Call Hero astfel incat in momentul in care este apasat apare o alerta cu textul: Batman (Bruce Wayne) has been called!.

Tips:

  • folositi binding-ul de evenimente pe buton - (click)
  • implementati o metoda callHero() in clasa componentei si legati-o de eveniment
  • in functie folositi-va de proprietatile hero pentru a afisa mesajul corespunzator
  • folositi alert() pentru afisare