Prezentácia je k dispozícii tu.


Celý kód si napíšeme spolu, nasledujúce ukážky kódu sú tu v prípade, ak by niekto nestíhal, poprípade si to chcel pozrieť doma. :-)

Prvým krokom je spraviť si appku, ktorá nám vypíše Hello World!, aby sme v rýchlosti pochopili syntax Angularu :-)

Tento kód si napíšeme a vysvetlíme spolu.

Otvoríme príkazový riadok, nainštalujeme angular command line interface, vytvoríme nový projekt a otvoríme ho vo Visual studio code.

npm install -g @angular/cli 
ng new my-first-angular
cd my-first-angular
code .

Otvoríme si terminál vo Visual studio code a server spustíme príkazom:

ng serve

Na základe zadania si vytvoríme komponenty, z ktorých sa bude skladať náš projekt.

ng generate component issuesform
ng generate component issuestable
ng generate component label

Vytvoríme si priečinok "model" a následne modely našich entít, ktoré budeme potrebovať:
create new folder model
create new file user.model.ts
create new file label.model.ts
create new file issue.model.ts


Namodelujeme si ich. Používateľ bude identifikovaný menom a emailom. user.model.ts:

export class UserModel {
	Id: number;
	Name: string;
	Email: string;
						
	constructor(Id: number, Name: string, Email: string) {
		this.Id = Id;
		this.Name = Name;
		this.Email = Email;
	}
}
							

Každý label bude identifikovaný názvom, farbou písma a farbou pozadia. label.model.ts:

export class LabelModel {
	Id: number;
	Text: string;
	TextColor: string;
	BackgroundColor: string;
									
	constructor(Id: number, Text: string, TextColor: string, BackgroundColor: string) {
		this.Id = Id;
		this.Text = Text;
		this.TextColor = TextColor;
		this.BackgroundColor = BackgroundColor;
	}
}
									
							

Každá issue(problém) bude identifikovaná dátumom a časom vytvorenia, textom, autorom, komu je daná požiadavka pridelená a tiež danými tagmi. issue.model.ts:

import { UserModel } from "./user.model";
import { LabelModel } from "./label.model";
import * as moment from "moment";
								
export class IssueModel {
	Id: number;
	Time: Date;
	Text: string;
	Author: UserModel;
	AssignedTo: UserModel;
	Labels: LabelModel[] = [];
								
	constructor(Id: number, Time: Date, Text: string, Author: UserModel, AssignedTo: UserModel, Labels: LabelModel[]) {
		this.Id = Id);
		this.Time = Time;
		this.Text = Text;
		this.Author = Author;
		this.AssignedTo = AssignedTo;
		this.Labels = Labels;
	}
								
	getTimeMoment() {
		return moment(this.Time);
	}
								
}
						

Budeme tiež potrebovať service na prácu s našou internou "databázou" (nebudeme si ukazovať sql ani mongodb, podstatou projektu je ukázať si ako funguje angular).

ng generate service "model/issue"

Keďže pracujeme s interným úložiskom, je potrebné si nainicializovať potrebné hodnoty. Určite budeme potrebovať funkcie, ktoré nám vrátia všetky položky daného poľa. issue.service.ts:

import { Injectable } from '@angular/core';
import { IssueModel } from './issue.model';
import { UserModel } from './user.model';
import { LabelModel } from './label.model';
									
@Injectable({
	  providedIn: 'root'
})
									
export class IssueService {
							
	private _issueDatabase: IssueModel[] = null;
	private _issueDatabaseId: number = 2;
	private _userDatabase: UserModel[] = null;
	private _labelDatabase: LabelModel[] = null;
									
	constructor() {
		if (this._issueDatabase == null) {
							
		}
		this.initialize();
	}
							
	initialize() {
		this._userDatabase = [
			new UserModel(1, "Jozko", "jozko.mrkvicka@upjs.sk"), 
			new UserModel(2, "Evka", "eva.markova@upjs.sk"),
		];
		this._labelDatabase = [
			new LabelModel(1, "angular", "white", "red"),
			new LabelModel(2, "reactjs", "white", "blue"),
			new LabelModel(3, "python", "white", "green")
		];
		this._issueDatabase = [
			new IssueModel(1, new Date(), "Issue1", this.getUserById(1), this.getUserById(1), [
											this.getLabelById(1)]),
			new IssueModel(2, new Date(), "Issue2", this.getUserById(2), this.getUserById(1), [
											this.getLabelById(1),
											this.getLabelById(2)])
		];										
	}
									
	getLabelById(id: number): LabelModel {
		for (let label of this._labelDatabase) {
			if (label.Id == id) {
				return label;
			}
		}
		return null;
	}
									
									  
	getUserById(id: number): UserModel {
		for (let user of this._userDatabase) {
			if (user.Id == id) {
				return user;
			}
		}
		return null;
	}
									  
	getIssueById(id:number): IssueModel {
		for (let issue of this._issueDatabase) {
			if (issue.Id == id) {
				return issue;
			}
		}
		return null;
	}
									
	getAllUsers(): UserModel[] {
		return this._userDatabase;
	}
									
	getAllLabels(): LabelModel[] {
		return this._labelDatabase;
	}
									
	getAllIssues(): IssueModel[] {
		return this._issueDatabase;
	}
									  
	addOrChangeIssue(model: IssueModel): boolean {
		model.Id = ++this._issueDatabaseId;
		model.Time = new Date();
		this._issueDatabase.push(model);
		return true;
	}
}	
							

Na vstupe očakávame label. label.component.ts:

import { Component, OnInit, Input } from '@angular/core';
import { LabelModel } from '../model/label.model';
								
@Component({
  selector: 'app-label',
  templateUrl: './label.component.html',
  styleUrls: ['./label.component.css']
})
								
export class LabelComponent implements OnInit {							
								 
	@Input() label: LabelModel;					
								  
	constructor() { }
								
	ngOnInit() {	  
	}
							
								
}							
						

Dopredu si nadefinujeme ako bude vyzerať "výpis" konkrétneho labelu. label.component.html:

<span [ngStyle]="{background: label.BackgroundColor, color: label.TextColor}"> {{label.Text}} </span>

Do premennej issues si uložíme všetky issues. issuestable.component.ts:

import { Component, OnInit } from '@angular/core';
import { IssueModel } from '../model/issue.model';
import { IssueService } from '../model/issue.service';
import { Router } from '@angular/router';
								
@Component({
  selector: 'app-issuestable',
  templateUrl: './issuestable.component.html',
  styleUrls: ['./issuestable.component.css']
})

export class IssuestableComponent implements OnInit {
	protected issues: IssueModel[] = [];
	constructor(private issueService: IssueService, private router: Router) { }
	
	ngOnInit() {
		this.issues = this.issueService.getAllIssues();
		console.log(this.issues);
	}						
}
						

V issuestable.component.html si naformátujeme výpis všetkých issues.

<table>
	<thead>
		<tr>
			<th>ID</th>
			<th>Čas</th>
			<th>Názov</th>
			<th>Autor</th>
			<th>Pridelené</th>
			<th>Tagy</th>
			<th>Akcie</th>
		</tr>
	</thead>

	<tbody>
		<tr *ngFor="let issue of issues">
			<td>{{issue.Id}}</td>
			<td>{{issue.getTimeMoment().format("D.MMM.YYYY h:mm:ss")}}</td>
			<td>{{issue.Text}}</td>
			<td>{{issue.Author.Name}}</td>
			<td>{{issue.AssignedTo.Name}}</td>
			<td>
				<app-label *ngFor="let label of issue.Labels" [label]="label"></app-label>
			</td>
		</tr>
	</tbody>
</table>
									
<a [routerLink]="['/issues/create']" routerLinkActive="router-link-active"><button class="btn btn-secondary">Nová</button></a>

Na vytvorenie úplne novej issue použijeme nový komponent. V podstate si ideme zadefinovať, akú bude mať funkcionalitu a ako bude vyzerať issue formulár. issuesform.component.ts:

								
import { Component, OnInit } from '@angular/core';
import { IssueModel } from '../model/issue.model';
import { IssueService } from '../model/issue.service';
import { LabelModel } from '../model/label.model';
import { UserModel } from '../model/user.model';
import { ActivatedRoute, Router } from '@angular/router';								

@Component({
  selector: 'app-issuesform',
  templateUrl: './issuesform.component.html',
  styleUrls: ['./issuesform.component.css']
})

export class IssuesformComponent implements OnInit {								
	protected model: IssueModel = new IssueModel();
	protected labels: LabelModel[];
	protected users: UserModel[];
	protected modelLabelsAssigns: {};
								
	constructor(private route: ActivatedRoute, private router: Router, private issueService: IssueService) {
	}
								
	ngOnInit() {
		let id = this.route.snapshot.paramMap.get('id');
		this.labels = this.issueService.getAllLabels();
		this.users = this.issueService.getAllUsers();
		this.modelLabelsAssigns = {};
		this.labels.forEach(label => {
			this.modelLabelsAssigns[label.Id] = false;
		});
								
	}
								
	onSubmit() {
		this.model.Labels = [];
		this.labels.forEach(label => {
			if (this.modelLabelsAssigns[label.Id]) {
				this.model.Labels.push(label);
			}
		});
									
		if (this.issueService.addOrChangeIssue(this.model)) {
			this.router.navigate(["/issues"]);
		}
	}								
}
						

Tiež musíme prepísať html súbor. issuesform.component.html:

<div class="container">
	<h1>Issue formulár</h1>
	<form (ngSubmit)="onSubmit()" #issuesForm="ngForm">
		<div class="form-group">
			<label for="Text">Text</label>
			<input type="text" class="form-control" id="Text" name="Text" required [(ngModel)]="model.Text">
		</div>
										
		<div class="form-group">
			<label for="Author">Autor</label>
			<select class="form-control" id="Author" name="Author" [(ngModel)]="model.Author">
				<option *ngFor="let user of users" [ngValue]="user">{{user.Name}}</option>
			</select>
		</div>
										
		<div class="form-group">
			<label for="AssignedTo">Pridelené</label>
			<select class="form-control" id="AssignedTo" name="AssignedTo" [(ngModel)]="model.AssignedTo">
				<option *ngFor="let user of users" [ngValue]="user">{{user.Name}}</option>
			</select>
		</div>
										
		<div class="form-group">
			<label for="Labels">Tagy</label>
			<label *ngFor="let label of labels">
				<input type="checkbox" id="labels_{{label.Id}}" name="labels_{{label.Id}}" [(ngModel)]="modelLabelsAssigns[label.Id]" />
					{{label.Text}}
			</label>
		</div>									  
		<button type="submit" class="btn btn-success">Potvrdiť</button>									  
	</form>
</div>

V app-routing.module.ts si potrebujeme nadefinovať cesty.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Routes, RouterModule } from '@angular/router';
import { IssuestableComponent } from './issuestable/issuestable.component';
import { IssuesformComponent } from './issuesform/issuesform.component';
								
const routes: Routes = [
  { path: 'issues', component: IssuestableComponent },
  { path: 'issues/create', component: IssuesformComponent },
  { path: '**', redirectTo: '/issues' },
];
								
@NgModule({
  imports: [CommonModule, RouterModule.forRoot(routes)],
  exports: [RouterModule]
})

export class AppRoutingModule { }
						

app.component.html bude vyzerať nasledovne:

<router-outlet></router-outlet>

Do app.module.ts doplníme do imports FormModules.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { IssuesformComponent } from './issuesform/issuesform.component';
import { IssuestableComponent } from './issuestable/issuestable.component';
import { LabelComponent } from './label/label.component';
								
@NgModule({
  declarations: [
	AppComponent,
	IssuesformComponent,
	IssuestableComponent,
	LabelComponent
  ],
  imports: [
	BrowserModule,
	FormsModule,
	AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})

export class AppModule { }
						

Ale čo ak chceme editovať už pridanú issue? Do issuestable.component.ts pridáme:

editIssue(issue?: IssueModel) {							
	this.router.navigate(["/issues/edit", issue.Id]);						  
}

V issuesform.component.ts musíme v metóde ngOnInit() ošetriť, ak daná issue už existovala.

if (id) {
    this.model = this.issueService.getIssueById(Number(id));
    this.model.Labels.forEach(selected => {
        this.modelLabelsAssigns[selected.Id] = true;
    });
}

Pridáme tlačidlo do issuestable.component.html

<td>
	<button (click)="editIssue(issue)">upraviť</button>
</td>

Tiež je treba aktualizovať issue.service.ts:

addOrChangeIssue(model: IssueModel): boolean {
	if (model.Id != null) {
		let search = null;
		this._issueDatabase.forEach(item => {
			if (item.Id == model.Id) {
				search = item;
			}
		});
		let indexOf = this._issueDatabase.indexOf(search);
		this._issueDatabase.splice(indexOf, 1);
	} else {
		model.Id = ++this._issueDatabaseId;
		model.Time = new Date();
	}
	this._issueDatabase.push(model);
	return true;
}

Samozrejme nesmieme zabudnúť na nadefinovanie cesty.

 { path: 'issues/edit/:id', component: IssuesformComponent }

Ak chceme pekný dizajn, môžeme použiť knižnicu bootstrap.

npm install bootstrap

V súbore angular.json doplníme "styles" takto:

"styles": [ 
"node_modules/bootstrap/dist/css/bootstrap.min.css", 
"src/styles.css" 
],

Do styles.css pridáme nasledujúci import:

@import '~bootstrap/dist/css/bootstrap.min.css';

Úlohy

  1. Pridajte funkcionalitu vymazania issue (teda doplňte "delete" operáciu).
    Hint: Na vymazanie z našej lokálnej "databázy" funguje príkaz nazov_db.splice(index_mazaneho, 1)
  2. Pridajte atribút priorita (pomocou modelu PriorityModel) pre issues s možnosťou výberu nízka, stredná a vysoká.