Prerekvizity
1. Node.js a npm
Z tohto linku si stiahnite LTS verziu Node.js (tá v sebe už obsahuje npm). Stiahnuté verzie si viete overiť príkazmi
node -v
a npm -v
z príkazového riadku.
2. Angular CLI
Angular CLI si viete stiahnuť z príkazového riadku zadaním príkazu npm install -g @angular/cli@6.0.7
3. Editor (v našom prípade VS Code)
Ak ho ešte nemáte, viete si ho stiahnuť z tohto linku.
Ak ste úspešne zvládli tieto tri kroky, ste pripravení na vytvorenie nového projektu v Angulari. Tak poďme na to!
Vytvorenie nového projektu
V editore VS Code si otvoríme terminál. Z neho si vieme ľahko vytvoriť nový Angular projekt zadaním príkazu ng new nazovProjektu --skip-tests --routing --style less
Jeho spustenie pomocou npm automaticky stiahne všetky závislosti. Po dokončení budete mať nový Angular projekt, ktorý viete spustiť príkazom ng serve --open
Pozn.: Ak ste po spustení dostali Error hlášku typu: ERROR in node_modules/rxjs/internal/types.d.ts(81,44): error TS1005: ';' expected. #4540, je potrebné spustiť ešte tento príkaz: npm install rxjs@6.0.0
Ak všetko prebehlo úspešne, Váš prehliadač by Vám na adrese http://localhost:4200/ mal otvoriť Welcome Angular stránku.
Projekt Weather
1. časť: Prvé zmeny
Súbor app.component.html:
Zmažte existujúci obsah a nahraďte ho:
<div>{{city}}</div>
<div>{{weather}}</div>
<div>{{temp}}</div>
<router-outlet></router-outlet>
Súbor app.component.ts:
Doplňte do AppComponent nasledovné:
export class AppComponent {
city = 'Kosice';
weather = 'Sunny';
temp = 23.0;
}
2. časť: Ozajstná kontrola počasia s API
Na vygenerovanie service spustite z príkazového riadku príkaz:
ng generate service weather
V súbore src/app/weather.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class WeatherService {
constructor(private httpClient: HttpClient) { }
apiKey = '3dfb7e1ae2a36f9b8f454ae2e7313d78';
unit = 'metric';
getCurrentWeather(city: string): Observable<any> {
const apiCall = `https://api.openweathermap.org/data/2.5/weather?q=${city}&units=${this.unit}&APPID=${this.apiKey}`;
console.log('apiCall', apiCall);
return this.httpClient.get<any>(apiCall).pipe(
map(resp => {
console.log('Response', resp);
const weather = resp.weather[0];
const temp = resp.main.temp;
const x = { weather, temp };
console.log(x);
return x;
}));
}
}
V súbore src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { WeatherService } from './weather.service';
...
export class AppComponent implements OnInit {
city = 'Kosice';
weather = '?';
temp = 0;
constructor(public weatherService: WeatherService) {
}
ngOnInit() {
this.weatherService.getCurrentWeather(this.city).subscribe(x => {
this.weather = x.weather.description;
this.temp = x.temp;
});
}
}
Úloha 1:
Import akého Angular modulu je potrebné doplniť v súbore src/app/app.module.ts, aby sme vedeli urobiť http volania?
3. časť: Error handler
Do súboru src/app/app.component.html pridáme
<div>*ngIf="failedToLoad">Počasie pre toto mesto nie je dostupné. Skúste zadať iné.</div>
V súbore src/app/app.component.ts zmeníme:
import { Component, OnInit } from '@angular/core';
import { WeatherService } from './weather.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.less']
})
export class AppComponent implements OnInit {
city = 'Kosice';
weather = '?';
temp = 0;
failedToLoad: boolean;
constructor(public weatherService: WeatherService) {
}
ngOnInit() {
this.weatherService.getCurrentWeather(this.city).subscribe(x => {
this.weather = x.weather.description;
this.temp = x.temp;
},
error => {
console.log('error occured', error);
this.failedToLoad = true;
});
}
}
4. časť: Práca s novým komponentom city
Úloha 2:
Z príkazového riadku vytvorte nový komponent city. Preorganizujte projekt tak, aby sa logika presunula do nového komponentu city.
Prekvapenie 1
5. časť: Routovanie
Do súboru src/app/app-routing.module.ts dodáme:
import { CityComponent } from './city/city.component';
const routes: Routes = [
{ path: 'city/:city', component: CityComponent }
];
p> Zo súboru
src/app/app.component.html vymažeme riadok:
<app-city></app-city>
Do súboru src/app/city/city.component.ts doplníme:
import { Component, OnInit } from '@angular/core';
import { WeatherService } from '../weather.service';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-city',
templateUrl: './city.component.html',
styleUrls: ['./city.component.less']
})
export class CityComponent implements OnInit {
city = '?';
weather = '?';
temp = 0;
failedToLoad: boolean;
constructor(public weatherService: WeatherService, private route: ActivatedRoute) {
}
ngOnInit() {
this.city = this.route.snapshot.params['city'];
this.weatherService.getCurrentWeather(this.city).subscribe(x => {
this.weather = x.weather.description;
this.temp = x.temp;
},
error => {
console.log('error occured', error);
this.failedToLoad = true;
});
}
}
Teraz môžete spustiť http://localhost:4200/city/kosice
Do súboru src/app/app.component.html doplníme:
<p>Some test cities:</p>
<div>
<a routerLink="/city/vancouver">Vancouver</a>
</div>
<div>
</a routerLink="/city/limerick">Limerick</a>
</div>
<div>
<a routerLink="/city/fakecity">Fake city</a>
</div>
V súbore src/app/city/city.component.ts zmeníme:
import { Component, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { WeatherService } from '../weather.service';
import { ActivatedRoute, Router } from '@angular/router';
@Component({
selector: 'app-city',
templateUrl: './city.component.html',
styleUrls: ['./city.component.less']
})
export class CityComponent implements OnInit {
city = '?';
weather = '?';
temp = 0;
failedToLoad: boolean;
constructor(public weatherService: WeatherService, private route: ActivatedRoute) {
}
ngOnInit() {
this.route.paramMap.subscribe(route => {
this.city = route.get('city');
this.reset();
this.weatherService.getCurrentWeather(this.city).subscribe(x => {
this.weather = x.weather.description;
this.temp = x.temp;
},
error => {
console.log('error occured', error);
this.failedToLoad = true;
});
});
}
reset() {
this.failedToLoad = false;
this.weather = '?';
this.temp = 0;
}
}
6. Vynechali sme :)
Generujeme service config z príkazového riadku:
ng generate service config
Do súboru src/app/app.module.ts pridáme:
import { weatherServiceProvider } from './weather.service';
...
providers: [weatherServiceProvider],
bootstrap: [AppComponent]
})
export class AppModule { }
Do súboru src/app/config.service.ts pridáme:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ConfigService {
public inMemoryApi = true;
}
Súbor src/app/weather.service.ts po úpravách:
import { Injectable } from '@angular/core';
import { HttpClient, HttpResponse } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { map, delay } from 'rxjs/operators';
import { ConfigService } from './config.service';
@Injectable()
export class WeatherService {
constructor(private httpClient: HttpClient) { }
apiKey = '665d48140dca13e411595586a256c4ce';
unit = 'metric';
getCurrentWeather(city: string): Observable<any> {
const apiCall = `https://api.openweathermap.org/data/2.5/weather?q=${city}&units=${this.unit}&APPID=${this.apiKey}`;
console.log('apiCall', apiCall);
return this.httpClient.get<any> (apiCall).pipe(
map(resp => {
console.log('Response', resp);
const weather = resp.weather[0];
const temp = resp.main.temp;
const x = { weather, temp };
console.log(x);
return x;
}));
}
}
@Injectable()
export class DevelopmentWeatherService {
getCurrentWeather(city: string): Observable<any> {
const weather = { description: 'Rain Rain Rain' };
const temp = 12.2;
const x = { weather, temp };
// of(x).pipe(delay(2000)) allows you to mimic delays
// that can happen when you call the real api.
return of(x).pipe(delay(2000));
// throwError can mimic errors from the API call.
// return throwError('mimic an api failure');
}
}
export function weatherServiceFactory(httpClient: HttpClient, configService: ConfigService) {
let service: any;
if (configService.inMemoryApi) {
service = new DevelopmentWeatherService();
} else {
service = new WeatherService(httpClient);
}
return service;
}
export let weatherServiceProvider = {
provide: WeatherService,
useFactory: weatherServiceFactory,
deps: [HttpClient, ConfigService]
};
V súbore src/app/config.service.ts prepíšeme:
public inMemoryApi = true; na public inMemoryApi = false;
7. Úprava vzhľadu
Pridanie containera v súbore src/app/app.component.html úloha???
</div class='container'>
<router-outlet></router-outlet>
<p>Some test cities:<p>
<div>
<a routerLink="/city/vancouver">Vancouver </a>
</div>
<div>
<a routerLink="/city/limerick">Limerick</a>
</div>
<div>
<a routerLink="/city/fakecity">Fake</a>
</div>
</div>
Obsah súboru src/app/app.component.less
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
Obsah súboru src/app/city/city.component.html
<div class="city">
<h1>{{city | uppercase }}</h1>
<div>{{weather}}</div>
<div>{{temp}}</div>
<div *ngIf="failedToLoad">Weather is not available. Try looking out the window.</div>
</div>
Obsah súboru src/app/city/city.component.less
.city {
display: flex;
flex-direction: column;
align-items: center;
min-width: 250px;
padding: 0px 20px 20px 20px;
margin: 10px;
border: 1px solid;
border-radius: 5px;
box-shadow: 2px 2px #888888;
h1 {
line-height: 1.2
}
}
V súbore src/styles.less prepíšeme:
body {
line-height: 1.6;
font-size: 18px;
color: #444;
font-family: 'Open Sans', sans-serif;
}
8. časť: Pridanie mesta
Z príkazového riadku si opäť vygenerujeme component addcity príkazom
ng generate component addCity
V súbore src/app/add-city/add-city.component.html
<p>
add-city works!
</p>
V súbore src/app/add-city/add-city.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-add-city',
templateUrl: './add-city.component.html',
styleUrls: ['./add-city.component.less']
})
export class AddCityComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
V súbore src/app/app-routing.module.ts
import { AddCityComponent } from './add-city/add-city.component';
const routes: Routes = [
{ path: 'city/:city', component: CityComponent },
{ path: 'add-city', component: AddCityComponent },
];
V súbore src/app/app.module.ts
import { AddCityComponent } from './add-city/add-city.component';
@NgModule({
declarations: [
AppComponent,
CityComponent,
AddCityComponent
],
V súbore src/app/city/city.component.html
...
<a routerLink="/add-city">Add new city</a>
Vyskúšame to spustiť.
>
V súbore src/app/add-city/add-city.component.html Add input box with binding to add-city component
<input type="text" placeholder="Vancouver" [(ngModel)]="newCity">
<div>{{newCity}}</div>
V súbore src/app/add-city/add-city.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-add-city',
templateUrl: './add-city.component.html',
styleUrls: ['./add-city.component.less']
})
export class AddCityComponent implements OnInit {
newCity: string;
constructor() { }
ngOnInit() {
}
}
V súbore src/app/app.module.ts
import { FormsModule } from '@angular/forms';
...
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule
HttpClientModule,
FormsModule
],
Vyskúšame to spustiť http://localhost:4200/add-city
V súbore src/app/add-city/add-city.component.html
<button (click)="addCity()">Add</button>
<div *ngIf="failed">Could not add the city.</div>
V súbore src/app/add-city/add-city.component.ts
import { Component, OnInit } from '@angular/core';
import { WeatherService } from '../weather.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-add-city',
templateUrl: './add-city.component.html',
styleUrls: ['./add-city.component.less']
})
export class AddCityComponent implements OnInit {
newCity: string;
failed: boolean;
constructor(public weatherService: WeatherService, private router: Router) { }
ngOnInit() {
}
addCity() {
this.failed = false;
const city = this.newCity;
this.weatherService.getCurrentWeather(city).subscribe(x => {
console.log('Successfully found city');
this.router.navigate(['/city/' + city]);
},
error => {
console.log('Could not add city');
this.failed = true;
});
}
}
9. časť: Pridanie väčšieho počtu miest
Z príkazového riadku vygenerujeme nový service cityStorage
ng generate service cityStorage
V súbore: src/app/city-storage.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class CityStorageService {
public cities: string[] = [];
storageName = 'cities';
constructor() {
const existingCities = JSON.parse(localStorage.getItem(this.storageName));
if (existingCities) {
this.cities = existingCities;
}
console.log('Stored cities', this.cities);
}
addCity(city: string) {
this.cities.push(city);
localStorage.setItem(this.storageName, JSON.stringify(this.cities));
}
}
V súbore: src/app/add-city/add-city.component.ts
import { Component, OnInit } from '@angular/core';
import { WeatherService } from '../weather.service';
import { Router } from '@angular/router';
import { CityStorageService } from '../city-storage.service';
@Component({
selector: 'app-add-city',
templateUrl: './add-city.component.html',
styleUrls: ['./add-city.component.less']
})
export class AddCityComponent implements OnInit {
newCity: string;
failed: boolean;
searching: boolean;
constructor(public weatherService: WeatherService, private router: Router, public cityStorage: CityStorageService) { }
ngOnInit() {
}
addCity() {
this.searching = true;
this.failed = false;
const city = this.newCity;
this.weatherService.getCurrentWeather(city).subscribe(x => {
console.log('Successfully found city');
this.searching = false;
this.cityStorage.addCity(city);
this.router.navigate(['/city/' + city]);
},
error => {
console.log('Could not add city');
this.failed = true;
this.searching = false;
});
}
}
Do súboru: src/app/add-city/add-city.component.html pridáme
<div>Stored cities: {{cityStorage.cities}}</div>
Vygenerujeme si nový komponent startup
ng generate component startup
V súbore: src/app/startup.component.ts
import { Component, OnInit } from '@angular/core';
import { WeatherService } from './weather.service';
import { CityStorageService } from './city-storage.service';
import { Router, ActivatedRoute } from '@angular/router';
@Component({
template: ``, // Nothing will show on this component.
})
export class StartupComponent {
constructor(private cityStorage: CityStorageService, private router: Router) {
if (this.cityStorage.cities.length === 0) {
this.router.navigate(['/add-city/']);
} else {
const defaultCity = this.cityStorage.cities[0];
console.log('going to default');
this.router.navigate(['/city/' + defaultCity]);
}
}
}
V súbore: src/app/app.module.ts
import { StartupComponent } from './startup.component';
@NgModule({
declarations: [
AppComponent,
CityComponent,
AddCityComponent,
StartupComponent
],
V súbore: src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { WeatherService } from './weather.service';
import { CityStorageService } from './city-storage.service';
import { Router, ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.less']
})
export class AppComponent {
constructor() {
}
}
V súbore: src/app/app-routing.module.ts
...
import { AppComponent } from './app.component';
import { StartupComponent } from './startup.component';
const routes: Routes = [
{ path: '', component: StartupComponent },
{ path: 'city/:city', component: CityComponent },
{ path: 'add-city', component: AddCityComponent },
];
Zo súboru src/app/app.component.html odstránime testovacie mestá.
Úloha 4:
Vašou úlohou je dokončiť aplikáciu tak, aby sme sa vedeli prepínať medzi uloženými mestami. Stačí jedným smerom.
Ďakujem za pozornosť!