# Softwareentwicklung Maturaskript 2020/21
1. [Java | Quarkus](#1-Java-|-Quarkus)
1. [Konfiguration](#1.1-Konfiguration)
2. [Java Syntax](#1.2-Java-Syntax)
3. [Entities](#1.3-Entities)
4. [JPA](#1.4-JPA)
5. [Repository](#1.5-Repository)
6. [Services](#1.6-Services)
7. [Websockets](#1.7-Websockets)
8. [SSE](#1.8-SSE)
9. [Testen](#1.9-Testen)
10. [JSON Web Tokens (JWT)](#1.10-JSON-Web-Tokens)
11. [Sonstiges](#1.11-Sonstiges)
2. [Angular](#2-Angular)
1. [Testumgebung starten](#2.1-Testumgebung-starten)
2. [CLI](#2.2-CLI)
3. [Entity](#2.3-Entity)
4. [HTTP-Service](#2.4-HTTP-Service)
5. [Data-Service](#2.5-Data-Service)
6. [Binding](#2.6-Binding)
7. [Pipes](#2.7-Pipes)
8. [Angular Material Schematics](#2.8-Angular-Material-Schematics)
9. [Typescript Syntax](#2.9-Typescript-Syntax)
10. [Routing](#2.10-Routing)
11. [App-Module](#2.11-App-Module)
12. [Websockets](#2.12-Websockets)
13. [SSE](#2.13-SSE)
14. [Authentication (JWT)](#2.14-Authentication-(JWT))
15. [HTML Cheatsheet](#2.15-HTML-Cheatsheet)
1 Java | Quarkus
==============
1.1 Konfiguration
-------------
### Extensions
Beim Anlegen eines neuen Projekts folgende Extensions auswählen:
- RESTEasy JSON-B
- JDBC Driver - Derby
- Hibernate ORM with Panache (falls ohne Panache gearbeitet wird, nur
Hibernate ORM)
- Websockets
- SmallRye JWT (falls JWT erforderlich ist)
### Datenbank
**Port Derby:** 1527
**Host:** localhost
**Database:** databaseName
`startNetworkServer` ausführen
### Application Properties
Folgende Konfigurationen in application.properties eintragen:
application.properties
```dockerfile=
quarkus.datasource.db-kind=derby
quarkus.datasource.jdbc.url=jdbc:derby://localhost:1527/reservationdb;create=true
quarkus.hibernate-orm.database.generation=drop-and-create (none)
quarkus.http.cors=true
```
1.2 Java Syntax
------
### Array
```java=
// Array mit bestimmter Länge
int[] arr = new int[5];
// Array mit unbestimmter Länge
String[] cars = {"Volvo", "BMW", "Ford", "Mazda"};
// Schleife über Array
for (String i : cars) {}
// Mehrdimensional
int[][] myNumbers = { {1, 2, 3, 4}, {5, 6, 7} };
int x = myNumbers[1][2];
String[][] personen = {
{ "Klaus", "Meyer", "Schlossallee. 4", "12345 Janzweitweg" },
{ "Franz", "Schmitz", "Elisenstr. 18", "98765 Posenuckel" },
```
### Listen
```java=
LinkedList<String> cars = new LinkedList<String>();
cars.add("Volvo");
// Remove the head using remove()
cars.remove();
list.remove(4);
list.remove("Geeks");
LinkedList<String> linkedList = new LinkedList<>();
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i));
}
Iterator<String> iterator = linkedList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
List<String> list = new ArrayList<>();
list.add("str 1");
list.add("str 2");
list.add(0,"str 3"); // Add 3 on position 0
list.remove(1); // remove item on position 1
list.remove("str 2"); // remove first occurrence of str 2
```
### Sets
```java=
Set<String> cars = new HashSet<String>();
cars.add("Mazda");
cars.contains("Mazda");
cars.remove("Volvo");
```
### Maps
Dient als Key-Value Objekt
```java=
HashMap<String, String> capitalCities = new HashMap<String, String>();
capitalCities.put("England", "London");
capitalCities.get("England");
capitalCities.remove("England");
```
1.3 Entities
--------
Bei einer Entity Klasse sollten allgemein 4 Punkte beachtet werden:
- Annotation @Entity
- Annotation @Id (nicht erforderlich mit Panache Entity)
- Standardkonstruktor
- extends PanacheEntity
Bei der Verwendung von static, final, transient oder @Transient wird ein
Feld nicht in die DB gespeichert
### @Column Annotation
Da gewisse Bezeichnungen (wie bspw. User) vom System reserviert sind und somit nicht verwendet werden dürfen kann man Felder einen anderen Namen in der DB geben. Außerdem kann man in der @Column-Annotation beispielsweise die Länge festlegen, sowie unique, precision, scale und nullable state konfigurieren.
@Table: Benennung der Datenbanktabelle
name: Name der Tabelle
Beispiel:
```java=
@Column(name = "benutzer", nullable = false, unique = true, length = 255)
private String user;
@Table(name="Reservation")
```
### Weitere wichtige Annotationen
**Datumformat:** @JsonbDateFormat(“yyyy-MM-dd HH:mm”)\
**@JsonbTransient** Wenn ein Objekt nicht serialisiert werden soll.
**@Basic:** erlaubt das setzen von Attributen wie optional: setzt entsprechend der Wahl NOT NULL; fetch: eager oder lazy
```java
@Column(name="STARTTIME")
@JsonbDateFormat("yyyy-MM-dd HH:mm")
public LocalDateTime start;
@ManyToOne
@JsonbTransient
public Customer customer;
```
### Ansatz mit Panache
If you don’t want to bother defining getters/setters for your entities, you can make them extend PanacheEntityBase and Quarkus will generate them for you. You can even extend PanacheEntity and take advantage of the default ID it provides. Your attributes must be public.
Example for PanacheEntity:
```java=
package at.htl.entities;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import javax.json.bind.annotation.JsonbDateFormat;
import javax.json.bind.annotation.JsonbTransient;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
public class Reservation extends PanacheEntity {
@ManyToOne
@JsonbTransient
public Customer customer;
@ManyToOne
public Court court;
@Column(name="STARTTIME")
@JsonbDateFormat("yyyy-MM-dd HH:mm")
public LocalDateTime start;
@Column(name="ENDTIME")
@JsonbDateFormat("yyyy-MM-dd HH:mm")
public LocalDateTime end;
public Reservation() {
}
public Reservation(Customer customer, LocalDateTime start, LocalDateTime end) {
this.customer = customer;
this.start = start;
this.end = end;
}
}
```
As mentioned before you can specify your own ID strategy by extending PanacheEntityBase instead of PanacheEntity. Then you just declare whatever ID you want as a public field:
```java=
@Entity
public class Person extends PanacheEntityBase {
@Id
@SequenceGenerator(
name = "personSequence",
sequenceName = "person_id_seq",
allocationSize = 1,
initialValue = 4)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "personSequence")
public Integer id;
//...
}
```
If you’re using repositories, then you will want to extend
PanacheRepositoryBase instead of PanacheRepository and specify your ID type as an extra type parameter:
```java=
@ApplicationScoped
public class PersonRepository implements PanacheRepositoryBase<Person,Integer> {
//...
}
```
1.4 JPA
---
### Beziehungen
#### 1:1 Beziehung unidirektional
<img src="https://i.ibb.co/TBcc3CD/unidirektional11.png" width="400">
#### 1:1 Beziehung bidirektional
<img src="https://i.ibb.co/41hVtfH/bidirektional11.png" width="400">
#### 1:n Beziehung unidirektional
<img src="https://i.ibb.co/2Z0sY5g/unidirektional1n.png" width="400">
#### 1:n Beziehung bidirektional
<img src="https://i.ibb.co/p0CSwkD/bidirektional1n.png" width="400">
#### m:n Beziehung unidirektional
<img src="https://i.ibb.co/2Kjwzrt/unidirektionalnm.png" width="400">
#### m:n Beziehung bidirektional
<img src="https://i.ibb.co/xmjTc2P/bidirektionalnm.png" width="400">
JPA bietet die Möglichkeit, im Mapping anzugeben, ob eine Beziehung
gleich mitgeladen werden soll, wenn die eigentliche Entität geladen
wird. Dies ist möglich, indem man im Mapping den FetchType.EAGER angibt.
### JPA Named Queries
Sollen mehrere Named Queries definiert werden, müssen diese unter einer
@NamedQueries-Annotation gekapselt werden.
<img src="https://i.ibb.co/WKnN2Tk/namedqueries.png" width="500">
Einsatz von Named Queries

Weitere Beispiele:
```java=
public List<Ressource> findAllRessource(){
return em.createNamedQuery("Ressource.findAll", Ressource.class).getResultList();
}
@Transactional
public void deleteAllRessource(){
Query q1 = em.createQuery("DELETE FROM Ressource");
q1.executeUpdate();
}
```
Im nächsten Eintrag wird der auch der Einsatz von TypedQueries beschrieben.
### JPA Cascade Types
* **CascadeType.PERSIST**: cascade type presist means that save() or persist() operations cascade to related entities.
* **CascadeType.REMOVE**: cascade type remove removes all related entities association with this setting when the owning entity is deleted.
* **CascadeType.ALL**: cascade type all is shorthand for all of the above cascade operations.
Example:
`@OneToMany(cascade=CascadeType.REMOVE)`
### JPA Fetch Types
**EAGER** loading of collections means that they are fetched fully at the time their parent is fetched. So if you have Course and it has List<Student>, all the students are fetched from the database at the time the Course is fetched.
**LAZY** loading Associated data loads only when we explicitly call getter or size method.
Example:
`@OneToMany(fetch = FetchType.LAZY)`
1.5 Repository
----------
### Ansatz mit Panache
When using Repositories, you get the exact same convenient methods as
with the active record pattern, injected in your Repository, by making
them implements PanacheRepository:
```java=
@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
// put your custom logic here as instance methods
public Person findByName(String name){
return find("name", name).firstResult();
}
public List<Person> findAlive(){
return list("status", Status.Alive);
}
public void deleteStefs(){
delete("name", "Stef");
}
}
```
Panache.getEntityManager(): Zugriff auf Enity Manager Instanz:
```java=
package at.htl.repositories;
import javax.enterprise.context.ApplicationScoped;
import javax.persistence.TypedQuery;
import javax.transaction.Transactional;
import at.htl.entities.*;
import io.quarkus.hibernate.orm.panache.Panache;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import java.time.LocalDateTime;
import java.util.List;
@ApplicationScoped
public class CourtRepository implements PanacheRepository<Court> {
@Transactional
public void update(Court court){
Panache.getEntityManager().merge(court);
}
public List<Court> getAvailableCourts(long courtTypeId,
LocalDateTime start,
LocalDateTime end) {
String q = "select c from Court c where c.courtType.id=?1 " +
" and c NOT IN ( " +
" select r.court from Reservation r " +
" where r.court.courtType.id = ?1" +
" and (?2 < r.end " +
" and ?3 > r.start)" +
" )";
TypedQuery<Court> query = getEntityManager().createQuery(q, Court.class);
query.setParameter(1, courtTypeId);
query.setParameter(2, start);
query.setParameter(3, end);
return query.getResultList();
}
}
```
Wie in diesem Beispiel angeführt, können so TypedQueries ausgeführt
werden.
Weitere Beispiele:
```java=
public List<Court> findAvailableCourts(long courtTypeId, LocalDateTime start, LocalDateTime end) {
Query query = Panache.getEntityManager().createQuery("SELECT c " +
"FROM Court c " +
"LEFT JOIN Reservation r on r.court.id = c.id " +
"JOIN c.courtType ct " +
"where ct.id = :cTID AND ((r.start < :start AND r.end < :start OR r.end > :end AND r.start > r.end) or (r.id is null))");
query.setParameter("start", start);
query.setParameter("end", end);
query.setParameter("cTID", courtTypeId);
return query.getResultList();
}
public Object getLastSettlement() {
Query query = Panache.getEntityManager().createQuery("select s from Settlement s where date in (select max(date) from Settlement )");
return query.getSingleResult();
}
public void deleteSettlementData(){
Query query1 = Panache.getEntityManager().createQuery("delete from Ride");
Query query2 = Panache.getEntityManager().createQuery("delete from Repair");
Query query3 = Panache.getEntityManager().createQuery("delete from Fuel");
query1.executeUpdate();
query2.executeUpdate();
query3.executeUpdate();
}
```
1.6 Services
--------
```java=
package at.htl.services;
import at.htl.entities.Court;
import at.htl.entities.TimeslotDTO;
import at.htl.repositories.CourtRepository;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.transaction.Transactional;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedList;
import java.util.List;
@ApplicationScoped
@Path("/api/court")
public class CourtService {
@Inject
CourtRepository courtRepository;
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}")
public Court find(@PathParam("id") long id){
return courtRepository.findById(id);
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Court> findAll(){
return courtRepository.listAll();
}
@GET
@Path("/freeTimeslots/{ctypeid}/{date}/{duration}")
@Produces(MediaType.APPLICATION_JSON)
public List<TimeslotDTO> getFreeTimeslots(
@PathParam("ctypeid") long courtTypeId,
@PathParam("date") String sDate,
@PathParam("duration") int duration) {
List<TimeslotDTO> slots = new LinkedList<>();
LocalDate date = LocalDate.parse(sDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
LocalDateTime start = date.atTime(15, 0);
LocalDateTime end = date.atTime(22, 1);
LocalDateTime actTime = start;
while (start.plusMinutes(duration).isBefore(end)) {
List<Court> courts = courtRepository.getAvailableCourts(courtTypeId, start, start.plusMinutes(duration));
if (courts != null && courts.size() > 0) {
slots.add(new TimeslotDTO(start, start.plusMinutes(duration), courts));
}
start = start.plusMinutes(30);
}
return slots;
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Transactional
public Response createCourt(Court court, @Context UriInfo uriInfo){
courtRepository.persist(court);
URI uri = uriInfo.getAbsolutePathBuilder().path(Long.toString(court.id)).build();
return Response.created(uri).entity(court).build();
}
@DELETE
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
@Transactional
public Response deleteCourt(@PathParam("id") long id){
courtRepository.deleteById(id);
return Response.status(Response.Status.NO_CONTENT).build();
}
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Transactional
public Response updateCourt(Court c){
courtRepository.update(c);
return Response.status(Response.Status.NO_CONTENT).build();
}
}
```
Anstatt mehrerer PathParams kann auch eine simple DTO Klasse verwendet
werden:
```java=
public class TimeslotDTO {
private LocalDateTime starttime;
private LocalDateTime endtime;
private long[] courts;
public TimeslotDTO() {
courts = new long[0];
}
public TimeslotDTO(LocalDateTime starttime, LocalDateTime endtime, List<Court> courts) {
this.starttime = starttime;
this.endtime = endtime;
this.courts = courts.stream().mapToLong(value -> value.id).toArray();
}
//Getter und Setter
}
```
Alternative: Get-Request mit QueryParam.\
Bsp: http://localhost:8080/rest/getUser?id=1
```java=
@Path("getUser")
@GET
@Produces (MediaType.APPLICATION_JSON)
public Person findOne(@QueryParam("id") long id) {
return courtRepository.findById(id);
}
```
1.7 Websockets
----------
If you already have your Quarkus project configured, you can add the websockets extension to your project by running the following command in your project base directory:
`./mvnw quarkus:add-extension -Dextensions="websockets"`
This will add the following to your pom.xml:
```xml=
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-websockets</artifactId>
</dependency>
```
### Handling web sockets
Our application contains a single class that handles the web sockets. Create the org.acme.websockets.ChatSocket class in the src/main/java directory.
ChatSocket.java:
```java=
package at.htl.services;
import javax.enterprise.context.ApplicationScoped;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ServerEndpoint("/chat/{username}")
@ApplicationScoped
public class ChatSocket {
Map<String, Session> sessions = new ConcurrentHashMap<>();
private void broadcast(String message) {
sessions.values().forEach(s -> {
s.getAsyncRemote().sendObject(message , result -> {
if (result.getException() != null) {
System.out.println("Unable to send");
}
});
});
}
@OnOpen
public void onOpen(Session session,
@PathParam("username") String username) {
sessions.put(username, session);
System.out.println("User " + username + " joined");
broadcast("User " + username + " joined");
}
@OnClose
public void onClose(Session session,
@PathParam("username") String username) {
sessions.remove(username);
broadcast("User " + username + " left");
}
@OnMessage
public void onMessage(String message,
@PathParam("username") String username) {
broadcast(">> " + username + ": " + message);
}
@OnError
public void onError(Session session,
@PathParam("username") String username,
Throwable throwable) {
sessions.remove(username);
broadcast("User " + username
+ " left on error: " + throwable);
}
}
```
1.8 SSE
---
```java=
package at.htl.services;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.sse.OutboundSseEvent;
import javax.ws.rs.sse.Sse;
import javax.ws.rs.sse.SseBroadcaster;
import javax.ws.rs.sse.SseEventSink;
@Path("/sse")
public class SseResource {
private Sse sse;
private volatile SseBroadcaster sseBroadcaster;
private Integer id = 1;
private OutboundSseEvent.Builder eventBuilder;
@Context
public void setSse(Sse sse) {
this.sse = sse;
this.eventBuilder = sse.newEventBuilder();
if (sseBroadcaster == null) {
this.sseBroadcaster = sse.newBroadcaster();
}
}
@GET
@Path("/subscribe")
@Produces(MediaType.SERVER_SENT_EVENTS)
public void subscribe(@Context SseEventSink sseEventSink) {
this.sseBroadcaster.register(sseEventSink);
this.broadcast(" members joined");
}
private void broadcast(String msg) {
try {
OutboundSseEvent event = eventBuilder
// .name("EventName")
.id("" + ++id)
.mediaType(MediaType.APPLICATION_JSON_TYPE)
.data(id + " " + msg).build();
sseBroadcaster.broadcast(event);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
1.9 Testen
------
Press Alt + Einfg to generate a test class.
Example:
```java=
package at.htl;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
public class ExampleResourceTest {
@Test
public void testHelloEndpoint() {
given()
.when().get("/hello")
.then()
.statusCode(200)
.body(is("hello"));
}
@Test
public void testGreetingEndpoint() {
String uuid = UUID.randomUUID().toString();
given()
.pathParam("name", uuid)
.when().get("/hello/greeting/{name}")
.then()
.statusCode(200)
.body(is("hello " + uuid));
}
@Test
void create() {
JsonObject tt = Json.createObjectBuilder()
.add("userid", "if1001")
.add("firstname", "Tom")
.add("lastname", "Tester")
.build();
given().contentType(MediaType.APPLICATION_JSON)
.body(tt.toString())
.when()
.post("/students")
.then()
.statusCode(201);
}
}
```
1.10 JSON Web Tokens
---------
If you already have your Quarkus project configured, you can add the smallrye-jwt extension to your project by running the following command in your project base directory:
```dockerfile=
./mvnw quarkus:add-extension -Dextensions="smallrye-jwt, smallrye-jwt-build"
```
application.properties:
```dockerfile=
mp.jwt.verify.publickey.location=META-INF/resources/publicKey.pem
mp.jwt.verify.issuer=http://htl.at/securitydemo
smallrye.jwt.sign.key-location=privateKey.pem
smallrye.jwt.new-token.lifespan=3600
smallrye.jwt.new-token.issuer=http://htl.at/securitydemo
quarkus.smallrye-jwt.enabled=true
```
AuthService:
```java=
package at.htl.services;
import io.smallrye.jwt.build.Jwt;
import io.vertx.core.json.JsonObject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.jwt.Claims;
import javax.inject.Inject;
import javax.json.Json;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
@Path("api/auth")
public class AuthService {
@Inject
UserRepository userRepository;
@Inject
@ConfigProperty(name = "smallrye.jwt.new-token.lifespan")
long lifespan;
@Inject
@ConfigProperty(name = "smallrye.jwt.new-token.issuer")
String issuer;
@POST
@PermitAll
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response getToken(User user){
try {
//Usually check in DB, if user is registered
User u = userRepository.find("email", user.email).firstResult();
if (u == null || !user.passwort.equals(u.passwort)){
System.out.println(!user.passwort.equals(u.passwort));
return Response.status(Response.Status.UNAUTHORIZED).build();
}
long exp = Instant.now().getEpochSecond() + lifespan;
Map<String, Object> claims = new HashMap<>();
claims.put(Claims.upn.name(), v.email);
claims.put(Claims.iss.name(), issuer);
claims.put(Claims.exp.name(), exp);
String token = Jwt.claims(claims).groups("user").sign();
String entity = Json.createObjectBuilder().add("token", token).add("expires_at", exp).add("id", v.id).build().toString();
return Response.ok().entity(entity).build();
}
catch (Exception ex){
System.out.println(ex);
return Response.status(Response.Status.UNAUTHORIZED).build();
}
}
}
```
Vor Funktionen oder Klassen kann mittels
`@RolesAllowed("user")`
geprüft werden ob der Benutzer auch wirklich für die angefragte Resource autorisiert ist.
Adding publicKey.pem to resources/META-INF/resources ensures that it is available in the native image without having to provide a GraalVM resource file.
RSA Public Key PEM Content:
```
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEq
Fyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwR
TYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5e
UF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9
AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYn
sIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9x
nQIDAQAB
-----END PUBLIC KEY-----
```
Note for this code to work we need the content of the RSA private key that corresponds to the public key we have in the TokenSecuredResource application. Take the following PEM content and place it /resources/privateKey.pem:
```
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCWK8UjyoHgPTLa
PLQJ8SoXLLjpHSjtLxMqmzHnFscqhTVVaDpCRCb6e3Ii/WniQTWw8RA7vf4djz4H
OzvlfBFNgvUGZHXDwnmGaNVaNzpHYFMEYBhE8VGGiveSkzqeLZI+Y02G6sQAfDtN
qqzM/l5QX8X34oQFaTBW1r49nftvCpITiwJvWyhkWtXP9RP8sXi1im5Vi3dhupOh
nelk5n0BfajUYIbfHA6ORzjHRbt7NtBl0L2J+0/FUdHyKs6KMlFGNw8O0Dq88qnM
uXoLJiewhg9332W3DFMeOveel+//cvDnRsCRtPgd4sXFPHh+UShkso7+DRsChXa6
oGGQD3GdAgMBAAECggEAAjfTSZwMHwvIXIDZB+yP+pemg4ryt84iMlbofclQV8hv
6TsI4UGwcbKxFOM5VSYxbNOisb80qasb929gixsyBjsQ8284bhPJR7r0q8h1C+jY
URA6S4pk8d/LmFakXwG9Tz6YPo3pJziuh48lzkFTk0xW2Dp4SLwtAptZY/+ZXyJ6
96QXDrZKSSM99Jh9s7a0ST66WoxSS0UC51ak+Keb0KJ1jz4bIJ2C3r4rYlSu4hHB
Y73GfkWORtQuyUDa9yDOem0/z0nr6pp+pBSXPLHADsqvZiIhxD/O0Xk5I6/zVHB3
zuoQqLERk0WvA8FXz2o8AYwcQRY2g30eX9kU4uDQAQKBgQDmf7KGImUGitsEPepF
KH5yLWYWqghHx6wfV+fdbBxoqn9WlwcQ7JbynIiVx8MX8/1lLCCe8v41ypu/eLtP
iY1ev2IKdrUStvYRSsFigRkuPHUo1ajsGHQd+ucTDf58mn7kRLW1JGMeGxo/t32B
m96Af6AiPWPEJuVfgGV0iwg+HQKBgQCmyPzL9M2rhYZn1AozRUguvlpmJHU2DpqS
34Q+7x2Ghf7MgBUhqE0t3FAOxEC7IYBwHmeYOvFR8ZkVRKNF4gbnF9RtLdz0DMEG
5qsMnvJUSQbNB1yVjUCnDAtElqiFRlQ/k0LgYkjKDY7LfciZl9uJRl0OSYeX/qG2
tRW09tOpgQKBgBSGkpM3RN/MRayfBtmZvYjVWh3yjkI2GbHA1jj1g6IebLB9SnfL
WbXJErCj1U+wvoPf5hfBc7m+jRgD3Eo86YXibQyZfY5pFIh9q7Ll5CQl5hj4zc4Y
b16sFR+xQ1Q9Pcd+BuBWmSz5JOE/qcF869dthgkGhnfVLt/OQzqZluZRAoGAXQ09
nT0TkmKIvlza5Af/YbTqEpq8mlBDhTYXPlWCD4+qvMWpBII1rSSBtftgcgca9XLB
MXmRMbqtQeRtg4u7dishZVh1MeP7vbHsNLppUQT9Ol6lFPsd2xUpJDc6BkFat62d
Xjr3iWNPC9E9nhPPdCNBv7reX7q81obpeXFMXgECgYEAmk2Qlus3OV0tfoNRqNpe
Mb0teduf2+h3xaI1XDIzPVtZF35ELY/RkAHlmWRT4PCdR0zXDidE67L6XdJyecSt
FdOUH8z5qUraVVebRFvJqf/oGsXc4+ex1ZKUTbY0wqY1y9E39yvB3MaTmZFuuqk8
f3cg+fr8aou7pr9SHhJlZCU=
-----END PRIVATE KEY-----
```
1.11 Sonstiges
---------
### Testdaten einfügen
import.sql in Resources Ordner anlegen.\
Beispiele:
```sql=
insert into courttype(id, description) values (1, 'Tennis');
insert into courttype(id, description) values (2, 'Squash');
insert into courttype(id, description) values (3, 'Badminton');
insert into court(id, location, courttype_id) values (1, 'Halle A', 1);
insert into court(id, location, courttype_id) values (2, 'Halle B', 1);
insert into court(id, location, courttype_id) values (3, 'Halle C', 1);
```
2 Angular
=======
2.1 Testumgebung starten
---
- Angular_Testumgebung.zip auf Desktop kopieren -> Dateien mit 7ZIP entpacken
- In dem Ordner liegt das startAngular.bat File
- startAngular.bat ausführen
- Dadurch wird das globale NPM-Repository sowie die PATH-Variable neu gesetzt und ein Terminal Fenster geöffnet.
- Alle weiteren Terminal-Windows ebenfalls mit diesem Script öffnen.
- In dem geöffneten Fenster mit cd client auf das Projekt-Template wechseln mit ng serve --open kann man jetzt das Template starten
- Nur das app Verzeichnis zum Schluss auf das U-Laufwerk kopieren vom Client. Den kompletten Server nach einem “Clear Project” auch auf das U-Laufwerk legen.
2.2 CLI
---
- `npm install -g @angular/cli`
- `ng new [project name]`
- `ng serve --open`
- `ng generate component [name]`
- `ng generate service [name]`
- `ng generate class [name]`
- `ng add @angular/material`
2.3 Entity
------
Erstellung eines Typescript Interfaces. Erstelle einen Ordner namens “model” und erstelle darin deine Entities (.ts File) WICHTIG: Attribute am Client müssen wie am Server für Mapping benannt werden.
```typescript=
export interface Reservation {
firstname: String;
lastname?: String; // optionale Property
street: String;
city: String;
houseno: number;
zip: String;
}
```
Alternativ kann normale Typescript Klasse verwendet werden:
```typescript=
export class Person {
constructor(
public id = 0,
public vorname = '',
public nachname = '',
public email = ''
) {
}
}
```
2.4 HTTP-Service
------------
Um die Daten vom Server anzufragen oder an den Server zu senden wird der HTTP-Service genutzt. Ein Service kann mit `ng generate service http` erstellt werden.
```typescript=
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Customer } from '../model/customer';
@Injectable({
providedIn: 'root'
})
export class HttpService {
http: HttpClient;
api: string = "http://localhost:8080";
constructor(http: HttpClient) {
this.http = http;
}
}
```
### GET
<span>GET von mehreren Daten</span>
```typescript=
getCustomerList(): Observable<Customer[]> {
return this.http.get<Customer[]>(this.api + '/api/customer');
}
```
<span>GET von bestimmten Daten </span>
```typescript=
getCustomer(id: number): Observable<Customer> {
return this.http.get<Customer>(this.api + '/api/customer/' + id)
}
```
### POST
```typescript=
addCustomer(customer: Customer): Observable<Customer> {
return this.http.post<Customer>(this.api + '/api/customer', customer);
}
```
### PUT
```typescript=
updateCustomer(customer: Customer): Observable<Customer> {
return this.http.put<Customer>(this.api + '/api/customer', customer);
}
```
### DELETE
```typescript=
deleteCustomer(id: number): Observable<Customer>{
return this.http.delete<Customer>(this.api + '/api/customer/' + id);
}
```
2.5 Data-Service
------------
An observable data service is an injectable service that can be used to provide data to multiple parts of the application.
`ng generate service data`
```typescript=
import { Injectable } from '@angular/core';
import { Ressource } from './models/ressource';
import { Person } from './models/person';
import { Todo } from './models/todo';
import { HttpService} from './http.service';
import { NbToastrService } from '@nebular/theme';
@Injectable({
providedIn: 'root'
})
export class DataService {
// for ts interfaces
persons: Array<Person> = [];
todos: Array<Todo> = [];
ressourcen: Array<Ressource> = [];
// for ts classes
c_todos: Todo = new Todo();
c_persons: Person = new Person();
c_resources: Ressource = new Ressource();
constructor(public httpService: HttpService) { }
findPerson(){
this.httpService.getAllPersons().subscribe(data => {
this.persons = data;
console.log(this.persons);
});
}
}
```
2.6 Binding
-------
### Property Binding
Durch Property-Binding können Properties von HTML-Elementen oder Komponenten gesetzt werden.
```htmlembedded=
<div [style.visibility]="story ? 'visible' : 'hidden'" [class.special]="!isSpecial">
<img [src]="imagePath">
<a [href]="link">{{story}}</a>
</div>
<tr><td [attr.colspan]="1 + 1">One-Two</td></tr>
```
<span>**Input/Output:**</span> Da eine App oft aus hierarchisch geschachtelten Komponenten besteht, ist es auch möglich, Parameter von einer übergeordneten an eine untergeordnete Komponente mit Hilfe von Properties zu übergeben.
```typescript=
// parent.html
<person-detail [person]="person" (deleted)="deletePerson($event)"></person-detail>
// child.ts
@Input() person: Person;
@Output() deleted = new EventEmitter<Person>();
onDelete() {
this.deleted.emit(this.person);
}
// parent.ts
deletePerson(person: event) {
console.log('lastnameToDelete: '+person.lastname);
}
```
... dient dazu Daten zwischen Parent- und Child-Element auszutauschen
### Bidirectional Binding
`<input type="text" [(ngModel)]="person.lastname"/>`
**Wichtig:** NgModel benötigt das FormsModule:
`import { FormsModule } from ’@angular/forms’;`
### Attribut Binding
`<tr><td [attr.colspan]="1 + 1">One-Two</td></tr>`
### Class Binding
```htmlembedded=
<div class="special"
[class.special]="!isSpecial">This one is not so special</div>
```
```htmlembedded=
<div [ngStyle]="{'background-color':person.country === 'UK' ? 'green' : 'red' }"></<div>
```
```htmlembedded=
[ngClass]="{'text-success':person.country === 'UK'}"
<some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
```
### Event Binding
```htmlembedded=
(selectionChange)="refreshAvailableTimeslots()"
(click)="createReservation()"
(dateChange)="refreshAvailableTimeslots()"
```
### Style Binding
`<button [style.backgroundColor]="canSave ?'cyan' : 'grey'" >Save</button>`
### Daten anzeigen
**ngFor (For-Schleife):**
`*ngFor="let item of itemArray; let i = index"`
Wobei *item* jedes einzelne Element von *itemArray* wiederspiegelt. Der zweite Teil dient dazu einen Index (i) innerhalb des Elements zu verwenden.
***ngIf (If-Bedingung):**
`*ngIf="item != undefined && item.length > 0"`
Wenn die Bedingung *true* ist, dann wird das Element angezeigt.
***ngIf and Else**
```htmlembedded=
<div *ngIf="isLoggedIn; else loggedOut">
Welcome back, friend.
</div>
<ng-template #loggedOut>
Please friend, login.
</ng-template>
```
```htmlembedded=
<ng-template [ngIf]="isLoggedIn" [ngIfElse]="loggedOut">
<div>
Welcome back, friend.
</div>
</ng-template>
<ng-template #loggedOut>
<div>
Please friend, login.
</div>
</ng-template>
```
2.7 Pipes
-----
### Date
```javascript=
<p>Today is {{ day | date }}</p>
<p>Today is {{ day | date:"MM/dd/yy" }}</p>
<p>Today is {{ day | | date: 'dd.MM.yyyy' }}</p>
<p>Today is {{ day | date:format }}</p>
<p>Today is {{ day | date | uppercase }}</p>
<p>Today is {{ day | date | lowercase }}</p>
<p>pi (3.5-5): {{pi | number:'3.5-5'}}</p> <!-- {minIntegerDigits}.{minFractionDigits}-{maxFractionDigits} -->
<pre>{{ object | json }}</pre>
//Example:
// .html
{{slot.starttime | date: ’d.M.yyyy HH:mm:ss’}}
// .ts
formatDate(this.date, 'd.M.yyyy HH:mm:ss', this.locale)
formatDate(this.date, 'yyyy-MM-dd HH:mm', this.locale)
// constructor
.. @Inject(LOCALE_ID) private locale: string, ...
// imports
import { formatDate } from '@angular/common';
import { LOCALE_ID } from '@angular/core';
// app.module.ts (mit Angular Material)
import {MAT_DATE_LOCALE} from '@angular/material/core';
...
providers: [
{ provide: MAT_DATE_LOCALE, useValue: 'de-DE' },
],
```
... dient dazu ein Date-Objekt zu formatieren. Obiges Beispiel
formatiert Datum folgendermaßen: *14.12.2020 13:35:00*
### Currency
`{{fuel.price |currency:'EUR':true}} `
2.8 Angular Material Schematics
---------------------------
`ng add @angular/material`
In case you just want to install the @angular/cdk
`ng add @angular/cdk`
Address form schematic
`ng generate @angular/material:address-form <component-name>`
Navigation schematic
`ng generate @angular/material:navigation <component-name>`
Table schematic
`ng generate @angular/material:table <component-name>`
Dashboard schematic
`ng generate @angular/material:dashboard <component-name>`
Tree schematic
`ng generate @angular/material:tree <component-name>`
Drag and Drop schematic
`ng generate @angular/cdk:drag-drop <component-name>`
2.9 Typescript Syntax
----------
### Arrays
```typescript=
let fruits: string[] = ['Apple', 'Orange', 'Banana'];
let fruits: Array<string> = ['Apple', 'Orange', 'Banana'];
```
```typescript=
let userTestStatus: { id: number, name: string }[] = [
{ "id": 0, "name": "Available" },
{ "id": 1, "name": "Ready" },
{ "id": 2, "name": "Started" }
];
userTestStatus[0].name;
```
```typescript=
var obj: MyGroupType = {
"0": { "id": 0, "name": "Available" },
"1": { "id": 1, "name": "Ready" },
"2": { "id": 2, "name": "Started" }
};
```
```typescript=
private things: Thing[][];
constructor() {
this.things = [];
for(var i: number = 0; i < 10; i++) {
this.things[i] = [];
for(var j: number = 0; j< 10; j++) {
this.things[i][j] = new Thing();
}
}
}
```
```typescript=
interface IStringList {
[index:string]:string
}
let strArr : IStringList;
strArr["TS"] = "TypeScript";
strArr["JS"] = "JavaScript";
```
```typescript=
var arr_names:number[] = new Array(4)
for(var i = 0;i<arr_names.length;i++) {
arr_names[i] = i * 2
console.log(arr_names[i])
}
```
### Promise
```typescript=
getSpecificData(index: number){
let driversRequest = new Promise<number>((resolve) => {
this.http.getDrivenKilometers(index).subscribe((resp) => {
resolve(resp);
})
})
return driversRequest;
}
setSpecificData(){
for (let index = 0; index < this.drivers.length; index++) {
this.getSpecificData(this.drivers[index].id).then((data)=>{
if(data == undefined) {
this.drivers[index].km = 0;
}
else{
this.drivers[index].km = data;
}
});
});
}
```
### Sort Example
```typescript=
sortByRidesByDate(){
this.ride.sort((a, b) => {
if (a.date > b.date) return -1;
if (a.date < b.date) return 1;
return 0;
});
}
```
### Array-Funktionen
```typescript=
[ 1, 2 ].concat([5], [7, 9]) // [ 1, 2, 5, 7, 9 ]
[ 1, 2, 3, 4, 5 ].copyWithin(0,2) // [ 3, 4, 5, 4, 5 ]
[1, 30, 40].every(val => val > 0) // true
[1, 2, 3, 4].fill('x', 1, 3) // [ 1, "x", "x", 4 ]
[1, 10, 5, 6].filter(val => val > 5) // [ 10, 6 ]
[1, 10, 5, 6].find(val => val > 5) // 10
[1, 4, 5, 6].findIndex(val => val > 5) // 3
[1, [2, [3, [4]]]].flat(2) // [ 1, 2, 3, [4] ]
[[2], [4], [6], [8]].flatMap(val => val/2) // [ 1, 2, 3, 4 ]
[ 1, 2, 3 ].forEach(val => console.log(val)) // 1 // 2 // 3
[ 1, 2, 3 ].includes(3) // true
[ 1, 2, 3 ].indexOf(3) // 2
[ "x", "y", "z" ].join(" - ") // "x - y - z"
[ 1, 2, 3, 1, 0].lastIndexOf(1) // 3
[ 2, 3, 4 ].map(val => val * 2) // [ 4, 6, 8 ]
const arr = [ 1, 2, 3 ]
arr.pop() // returns: 3 // arr is [ 1, 2 ]
const arr = [ 1, 2, 3 ]
arr.push(1) // returns: 4 // arr is [ 1, 2, 3, 4 ]
[ 'a', 'b', 'c' ].reduce((acc, curr) => acc + curr, 'd') // "dabc"
[ 'a', 'b', 'c' ].reduceRight((acc, curr) => acc + curr, 'd') // "dcba"
[ 1, 2, 3 ].reverse() // [ 3, 2, 1 ]
const arr = [ 1, 2, 3 ]
arr.shift() // returns: 1 // arr is [ 2, 3 ]
[ 1, 2, 3, 4 ].slice(1, 3) // [ 2, 3 ]
[ 1, 2, 3, 4 ].some(val => val > 3) // true
[ 1, 2, 3, 4 ].sort((a, b) => b - a) // [ 4, 3, 2, 1 ]
const arr = [ 1, 2, 3, 4 ]
arr.splice(1, 2, 'a') // returns [ 2, 3 ] // arr is [ 1, "a", 4 ]
[1.1, 'a', new Date()].toLocaleString('EN') // "1.1,a,5/18/2020, 7:58:57 AM"
Object.keys(fruits); // ['0', '1', '2', '5']
const arr = [ 1, 2, 3 ]
arr.unshift(0, 99) // returns 5 // arr is [ 0, 99, 1, 2, 3 ]
```
### Date
```javascript=
let today = new Date();
let d = today.getDate();
new Date('Mar 25 10:30:00 2020');
new Date('1995-12-17T03:24:00');
new Date('1995-12-17');
const formattedDate = formatDate(new Date(), 'dd.MM.yyyy', 'en-US');
```
2.10 Routing
-------
In Angular wird zwischen Componenten gewechselt mittels Routing. Hierzu gibt es das app-routing.module.ts. Die Routes werden als Array hier eingetragen.
```typescript=
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { CustomerComponent } from './customer/customer.component';
import { NewReservationComponent } from './new-reservation/new-reservation.component';
import { ReservationListComponent } from './reservation-list/reservation-list.component';
const routes: Routes = [
{path: 'customer', component: CustomerComponent},
{path: 'resList', component: ReservationListComponent},
{path: 'resNew', component: NewReservationComponent},
//wildcard routes
{ path: '', component: CustomerComponent, pathMatch: 'full'},
{ path: '**', redirectTo: 'customer' },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
```
Auf den jeweiligen HTTP-Seiten auf der man Routing benutzen möchte muss man nur folgende Code-Zeile verwenden.
`<a routerLink="/resList" mat-list-item href="#">Reservierungsliste</a>`
Die Startseite (app.component.html) soll mit
`<router-outlet></router-outlet>`
gekenntzeichnet werden. Dort werden dann die unterschiedlichen Routes hineingeladen, die gerade angesprochen wurden.
Routing in Typescript:
`import { Router } from '@angular/router';`
`constructor(public router: Router) {}`
`this.router.navigate(['/login']);`
2.11 App-Module
----------
Imports überprüfen! Services bei Providers eintragen.
```typescript=
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientJsonpModule, HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
BrowserAnimationsModule,
ReactiveFormsModule,
HttpClientJsonpModule,
HttpClientModule
],
providers: [HttpService, DataService],
bootstrap: [AppComponent]
})
export class AppModule { }
```
2.12 Websockets
----------
`ng g service websocket`
```typescript=
import { Injectable } from '@angular/core';
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
@Injectable({
providedIn: 'root'
})
export class WebsocketService {
connection: WebSocketSubject<any>;
username = new Date().getMilliseconds()+Math.random()*100;
message: string;
url="ws://localhost:8080/chat"
constructor() { }
connect(): void {
this.connection = webSocket({
url: this.url + '/' + this.username,
deserializer: msg => msg.data
});
this.connection.subscribe(
value => console.log(value),
error => console.log('Error: ' + error),
() => console.log('Disconnected...')
);
}
send(): void {
this.connection.next(this.message);
}
disconnect(): void {
if (this.connection) {
this.connection.complete();
this.connection = null;
}
}
}
```
Treffen neue Nachrichten ein, können beispielweise alle Daten neu geladen werden:
```typescript=
this.connection = webSocket({
url: this.url + '/' + this.username,
deserializer: msg => msg.data
});
this.connection.subscribe((value) =>{
this.findDrivers();
this.findFuels();
this.findRepairs();
this.findRide();
}
);
```
2.13 SSE
---
```typescript=
import { NgZone } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
@Component({
selector: 'app-sse',
templateUrl: './sse.component.html',
styleUrls: ['./sse.component.css']
})
export class SseComponent implements OnInit {
info = 'no info';
sse: Subscription;
constructor(private zone: NgZone) {
}
ngOnInit(): void {
this.sse = this.getSseObservable().subscribe(
value => this.info = value,
error => console.log(error)
);
}
public getSseObservable(): Observable<any> {
return new Observable<any>(subscriber => {
const eventSource = new EventSource('http://localhost:8080/sse/subscribe');
eventSource.onmessage = event => {
this.zone.run(() => {
subscriber.next(event.data);
});
};
eventSource.onerror = event => {
this.zone.run(() => {
subscriber.error(event);
});
};
});
}
}
```
2.14 Authentication (JWT)
----------
Nachdem das Token vom erfolgreichen Login-Aufruf erhalten wurde wird es meist im LocalStorage abgelegt. Dieses Token muss dann bei allen Requests im Header mitgesendet werden, wofür sich ein HttpInterceptor anbietet:
Ein neuer Interceptor kann mit
`ng generate interceptor <name>`
erstellt werden.
```typescript=
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
} from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const idToken = localStorage.getItem('id_token');
if (idToken) {
const cloned = req.clone({
headers: req.headers.set('Authorization', 'Bearer ' + idToken),
});
return next.handle(cloned);
} else {
return next.handle(req);
}
}
}
```
Zum Aktivieren wird der in der Datei `app.modules.ts` im Abschnitt `providers` angegeben.
```
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
],
```
Eine entsprechende Login Komponente könnte dann folgendermaßen aussehen:
```typescript=
public login(username: string, password: string): void {
this.http.login(username, password).subscribe(
(data) => {
localStorage.setItem('id_token', data.token);
localStorage.setItem('user_id', data.id.toString());
// this.data.customerid = data.id;
this.router.navigate(['/']);
},
(err: Error) => {
alert('Login failed');
}
);
}
```
### Auth Guards
Zusätzlich kann ein AuthGuard implementiert werden. Darin könnten wir beispielsweise überprüfen, ob der Expires Zeitstempel schon überschritten wurde. Wenn ja leiten wir auf die Login-Maske um:
```typescript=
{
path: 'customer', component: CustomerComponent,
canActivate: [AuthGuardService]
},
```
AuthGuard-Implementierung:
```typescript=
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
@Injectable({
providedIn: 'root',
})
export class AuthGuardService implements CanActivate {
constructor(private router: Router) {}
canActivate(): boolean {
if (localStorage.getItem('id_token') == null) {
this.router.navigate(['/login']);
return false;
}
return true;
}
}
```
2.15 HTML Cheatsheet
----------
Table:
```htmlembedded=
<table><caption>Phone numbers</caption>
<thead>
<tr>
<th>Name</th>
<th colspan="2">Phone</th>
</tr>
</thead>
<tbody>
<tr>
<td>John</td>
<td>577854</td>
<td>577855</td>
</tr>
</tbody>
<tfoot>
<tr>
<td> </td>
<td>Personal</td>
<td>Office</td>
</tr>
</tfoot>
</table>
```
Button:
```htmlembedded=
<input type="button" (click)="onSave()" value="Klick mich">`
```
Input:
```htmlembedded=
<input [value]="foo" (change)="changeFn($event)">
<input [ngModel]="bar" (ngModelChange)="modelChangeFn($event)">
```
Radio-Button:
```htmlembedded=
<input id="male"
type="radio"
value="male"
name="gender"
formControlName="gender"
(change)="changeGender($event)">
<label for="male">Male</label>
<input type="radio"
name="{{groupName}}"
[value]="choice"
[(ngModel)]="defaultChoice"
(ngModelChange)="choose($event)" />
```
Selectbox:
```htmlembedded=
<select [(ngModel)]="data" (ngModelChange)="dataChanged($event)" name="data">
<option *ngFor="let currentData of allData" [ngValue]="currentData">
{{data.name}}
</option>
</select>
<select (change)="changed($event)">
<option *ngFor="let currentData of allData" [value]="currentData.id">
{{data.name}}
</option>
</select>
```
Textarea:
```htmlembedded=
<textarea id="subject" rows="4" cols="40">
...
</textarea>
```
Checkbox:
```htmlembedded=
<input (change)="fieldsChange($event)" [value]="currentValue" type="checkbox"/>
//.ts file
fieldsChange(values:any):void {
console.log(values.currentTarget.checked);
}
<input
type="checkbox"
[checked]="saveUsername"
(change)="saveUsername = !saveUsername"/>
<input type="checkbox" id="rememberMe" name="rememberMe" [(ngModel)]="rememberMe">
```
List:
```htmlembedded=
<ul>
<li>First</li>
<li>Second</li>
<li>Third</li>
</ul>
<dl>
<dt>HTML</dt>
<dd>Hypertext Markup Language</dd>
<dt>CSS</dt>
<dd>Cascading Style Sheets </dd>
</dl>
```
Horizontal Line:
`<hr /> `
Date:
```htmlembedded=
<label for="start">Start date:</label>
<input type="date" id="start" name="trip-start"
value="2018-07-22"
min="2018-01-01" max="2018-12-31">
```