Angular	for	Java	Enterprise Developers @loiane loiane.com
Agenda • High-level	overview	of	Angular	+	TypeScript • TypeScript/Java	common	pa=erns • Do’s	and	Dont’s	-	Angular	(Java	dev	ediCon) • Angular	CLI • Java	REST	with	Spring	Boot • Bundling	Angular	for	producCon • Guide	to	deploy	Angular	+	Java/Spring	to	the	cloud	(maven	+	docker)
#itsJustAngular v2+
Angular •Based	on	Components •Uses	TypeScript •Dependency	InjecCon •Angular	CLI •Very	popular	with	Java/.Net	developers
TypeScript •JavaScript	that	escales •StaCc	typing	-	compiling	9me •Transpiles	to	JavaScript	(ES201X) •More	produc9ve	than	JavaScript	(personal	opinion)
TypeScript 2018 Dev Environment TypeScript 3.1 EcmaScript 2015+ ES5
TypeScript my-script.ts transpiler my-script.js ES5
Getting Started npm install -g typescript mv main.js main.ts tsc main.ts
TypeScript: Strongly Typed? https://twitter.com/ahejlsberg/status/792762247239995392?s=09
TypeScript Type Sytem
Don’t Repeat Yourself
Java 10: var AWer	compiling	the	variables	are	strongly	typed var number10 = 10; var user = new User(); var myList = List.of("1","2","3","4","5"); !// after compiled int number10 = 10; User user = new User(); List<String> myList = List.of("1","2","3","4","5");
Java 10: var !// No!!! var userName; userName = user.getName(); !// Yes! var userName = user.getName();
TypeScript: Avoid Verbosity !// Yes let myString = 'Code One'; !// No!!! let myString2: string = 'Code One';
Optional: adding return types function orderByName(a: User[]): User[] { let result = a.slice(0); result.sort((x, y) !=> x.name.localeCompare(y.name)); return result; } But	someCmes	it	might	help	finding	bugs	in	the	code
Interfaces: two concepts (1/2) interface Person { name: string; age: number; } Interface	is	a	type,	a	definiCon
Interfaces: two concepts (2/2) interface Person { name: string; age: number; } interface Contact extends Person { phone: string; } Or	it	can	be	used	in	the	OO	concept
Duck Typing
Defining Types on the go + Unions type Shape = | { type: 'circle'; radius: number } | { type: 'square'; w: number } | { type: 'rectangle'; w: number, h: number };
Defining Types on the go + unions function calculateArea(shape: Shape) { switch (shape.type) { case 'circle': return Math.PI * shape.radius !** 2; case 'rectangle': return shape.w * shape.h; case 'square': return shape.w !** 2; } throw new Error('Invalid Shape'); } const myCircle: Shape = { type: 'circle', radius: 2 }; calculateArea(myCircle);
Enums enum UserActionTypes { LOAD_REQUEST = '[User] Load Request', LOAD_SUCCESS = '[User] Load Success' } type UsuarioActions = | { type: UserActionTypes.LOAD_REQUEST } | { type: UserActionTypes.LOAD_SUCCESS; payload: User[]; };
Getters and Setters: Don’t export class Contact { private _name: string; get name() { return this._name; } set name(name: string) { this._name = name; } }
Getters and Setters: Do export class AuthService { private loggedIn = new BehaviorSubject(false); get isLoggedIn() { return this.loggedIn.asObservable(); } } isLoggedIn = AuthService.isLoggedIn;
EcmaScript
Arrow Functions function orderByName(a: User[]): User[] { let result = a.slice(0); return result.sort((x, y) !=> x.name.localeCompare(y.name)); } Similar	to	Java	lambdas
> npm install -g @angular/cli > ng new my-project > cd my-project > ng serve || npm run start
Angular CLI
COMPONENTS DIRECTIVES ROUTINGSERVICES TEMPLATE DATA BINDINGDEPENDENCY INJECTION MODULES Main Blocks
COMPONENT {…} TEMPLATE <..> DIRECTIVES {..} SERVICES SERVICE B SERVICE A MODULE X MODULE A MODULE B Property Binding Event Binding
Modules
Admin Users Permissions Shared/Common Validations Pipes Products ProductsPanel ProductsList ProductDetail ProductForm Clients ClientsPanel ClientList ClientDetail ClientForm Order OrdersPanel OrdersList OrderDetail OrderForm OrderProductForm Reports MainReport ClientsReport ClientSearch ProductsReport ProductsSearch OrdersReport OrdersSearch ExportPDFButton Root AppComponent NavBar Menu
import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { ModalModule } from 'ngx-bootstrap/modal'; import { ContactDetailsComponent } from './components/contact-details/contact-details.component'; import { ContactFormComponent } from './components/contact-form/contact-form.component'; import { ContactListComponent } from './components/contact-list/contact-list.component'; import { ContactsContainerComponent } from './contacts-container/contacts-container.component'; import { ContactsRoutingModule } from './contacts-routing.module'; @NgModule({ imports: [ CommonModule, ReactiveFormsModule, ContactsRoutingModule, ModalModule.forRoot() ], declarations: [ ContactsContainerComponent, ContactListComponent, ContactFormComponent, ContactDetailsComponent ], entryComponents: [ContactDetailsComponent] }) export class ContactsModule {} Import classes
import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { ModalModule } from 'ngx-bootstrap/modal'; import { ContactDetailsComponent } from './components/contact-details/contact-details.component'; import { ContactFormComponent } from './components/contact-form/contact-form.component'; import { ContactListComponent } from './components/contact-list/contact-list.component'; import { ContactsContainerComponent } from './contacts-container/contacts-container.component'; import { ContactsRoutingModule } from './contacts-routing.module'; @NgModule({ imports: [ CommonModule, ReactiveFormsModule, ContactsRoutingModule, ModalModule.forRoot() ], declarations: [ ContactsContainerComponent, ContactListComponent, ContactFormComponent, ContactDetailsComponent ] }) export class ContactsModule {} Import other modules Import components used in this module
Project Organization Core Shared Feature A Feature B Feature C
Modules, Modules Everywhere!
Lazy Loading
Lazy Loading Strategies const routes: Routes = [ { path: 'module-2', loadChildren: './module-2/module-2.module#Module2Module', data: { preload: true } !// pre-load - background }, { path: 'module-3', loadChildren: './feature-3/module-3.module#Module3Module' } ]; @NgModule({ imports: [ RouterModule.forRoot(routes, { preloadingStrategy: AppCustomPreloader }) ], !// our custom logic }) export class AppRoutingModule {}
Lazy Loading Strategies import { PreloadingStrategy, Route } from '@angular/router'; import { Observable, of } from 'rxjs'; export class AppCustomPreloader implements PreloadingStrategy { preload(route: Route, load: Function): Observable<any> { return route.data !&& route.data.preload ? load() : of(null); } }
Routing: use Hash @NgModule({ imports: [RouterModule.forRoot(routes, { useHash: true })], exports: [RouterModule] }) export class AppRoutingModule {}
Routing: use Hash •h=p://locahost:4200/contacts •h=p://locahost:4200/#contacts
Common Patterns •Interfaces •Extending	Classes •Generics
Create Wrappers @Injectable() export class ApiService { constructor(public http: HttpClient) { } getRequest<T>(url: string, params!?: any) { return this.http.get<ServerResponse<T!>>(url, { params: this.getQueryParams(params) }) .take(1); !// as simple Ajax call, we only need 1 value } postRequest(url: string, body: any) { !// POST } private getQueryParams(params: any) { !// logic to create the query params for http call } downloadFile(url: string, params!?: any) { !// logic for file download } }
Keep consistency @Injectable() export class TasksService { constructor(public http: ApiService) { } getAllTasksWithPaging(start = 0, limit = 100) { return this.http .getRequest<Task[]>(API.READ_TASKS, {start: start, limit: limit}); } getById(id: number) { return this.http.getRequest<Task>(`${API.READ_TASKS}/${id}`); } }
Keep consistency export interface ServerResponse<T> { data: T[]; total: number; success!?: boolean; errorMsg!?: string; }
Inheritance export abstract class BaseFormComponent implements IFormCanDeactivate { protected formSubmitAttempt: boolean; protected validateDirty = true; form: FormGroup; constructor() { this.formSubmitAttempt = false; } isFieldInvalid(field: string) { !// some logic here } onSubmit() { this.formSubmitAttempt = true; !// generic logic for all forms } canDeactivate(): Observable<boolean> { return this.modalService.showConfirm( 'DoYouWantToLeavePage', 'DoYouWantToLeavePageMsg' ); } onCancel() { this.location.back(); } }
Generic “Dao” Service @Injectable() export class CRUDService<T> { constructor(public http: HttpClient, private API_URL) {} load() { return this.http.get<T[]>(this.API_URL); } create(record: T) { return this.http.post<Task>(this.API_URL, record); } update(record: T) { return this.http.put<Task>(`${this.API_URL}/${record.id}`, record); } remove(id: string) { return this.http.delete<T>(`${this.API_URL}/${id}`); } }
Generic “Dao” Service @Injectable() export class TasksService extends CRUDService<Task> { constructor(public http: HttpClient) { super(http, API.TASKS_URL) } }
Generic “Dao” ServiceDebugging
Multiple Angular Environments Custom	Environment Produc9on Development "scripts": { "ng": "ng", "dev": "ng serve !--configuration=dev", "start": "ng serve !--proxy-config proxy.conf.js", "build": "ng build !--prod !--aot !--build-optimizer -op !../webapps/app" },
Multiple Angular Environments !// environment.dev.ts export const environment = { production: false, baseUrl: '/' }; !// environment.ts export const environment = { production: false, baseUrl: '/api/' }; !// environment.prod.ts export const environment = { production: true, baseUrl: '!../' };
angular.json "configurations": { "production": { "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" } ], !!... }, "dev": { "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.dev.ts" } ] } !!... }
Back-end endpoints import { environment } from '@env/environment'; export class API { static readonly BASE_URL = environment.baseUrl; static readonly CLIENTS = `${API.BASE_URL}clients`; } !// this.http.get(API.CLIENTS)
Integration with Back-end: No CORs proxy.conf.json { "/api": { "target": "http:!//localhost:8080", "secure": false }, "logLevel": "debug" } const PROXY_CONFIG = [ { context: ['/api'], target: 'http:!//localhost:8080', secure: false, logLevel: 'debug', pathRewrite: { '^/api': '' } } ]; module.exports = PROXY_CONFIG; proxy.conf.js
Integration with Back-end: No CORs "scripts": { "ng": "ng", "start": "ng serve !--proxy-config proxy.conf.json", },
IE Support
Deploying to the cloud!
Different Approaches • MulCple	Maven	Projects	(client,	server	and	main	pom.xml)	(single	jar/war) • Single	Maven	(server	pom.xml	with	frontend	plugin)	(single	jar/war) • MulC-stage	Docker	file	(single	jar/war) • MulCple	docker	containers	(client	and	server	docker	files)	(mulCple	containers)
Code repo Angular src: Angular CLI Spring src: start.spring.io
Single jar/war package Angular prod bundle
Maven Front-end Plugin !!!<!-- Angular build !!--> <plugin> <groupId>com.github.eirslett!</groupId> <artifactId>frontend-maven-plugin!</artifactId> <version>1.6!</version> <configuration> <nodeVersion>v10.0.0!</nodeVersion> <npmVersion>6.4.1!</npmVersion> <workingDirectory>./!../angular-rest-crud!</workingDirectory> <installDirectory>${user.home}/.node!</installDirectory> !</configuration> <executions> !!!<!-- Steps for Angular prod build !!--> !</executions> !</plugin>
Maven Front-end Plugin <execution> <id>install node and npm!</id> <goals> <goal>install-node-and-npm!</goal> !</goals> <configuration> <nodeVersion>v10.0.0!</nodeVersion> <npmVersion>6.4.1!</npmVersion> !</configuration> !</execution> <execution> <id>npm install!</id> <goals> <goal>npm!</goal> !</goals> <configuration> <arguments>install !</arguments> !</configuration> !</execution>
Maven Front-end Plugin <execution> <id>npm build!</id> <goals> <goal>npm!</goal> !</goals> <configuration> <arguments>run build-op!</arguments> !</configuration> !</execution>
package.json "scripts": { "build-op": "ng build !--prod —output-path !../spring-rest-crud/target/classes/static”, },
Docker: Java / Spring FROM maven:3.5.4-jdk-10-slim WORKDIR /usr/src/java-code COPY . /usr/src/java-code/ RUN mvn package WORKDIR /usr/src/java-app RUN cp /usr/src/java-code/target!/*.jar ./app.jar EXPOSE 8080 CMD ["java", "-jar", "app.jar"]
Docker: Angular ##### Angular build FROM node:latest as angular-client WORKDIR /app COPY package.json package.json RUN npm install COPY . . RUN npm run build !--prod ##### Nginx FROM nginx:alpine VOLUME /var/cache/nginx COPY !--from=angular-client /app/dist/angular-rest-crud /usr/share/nginx/html COPY ./config/nginx.conf /etc/nginx/conf.d/default.conf
Docker: Angular + NGINX + proxy # proxy location /api { proxy_pass http:!//129.150.111.200:8083; proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504; proxy_buffering off; proxy_set_header Accept-Encoding ""; }
Oracle Application Container Cloud Service https://docs.oracle.com/en/cloud/paas/app-container-cloud/index.html
Oracle Application Container Cloud Service
Oracle Container Cloud Service https://docs.oracle.com/en/cloud/iaas/container-cloud/index.html
Thank you! @loiane github.com/loiane loiane.com loiane.training youtube.com/loianegroner
h=ps://github.com/loiane/ codeone-2018-angular-java-tutorial
Experts	in	Modern	Development • Cloud • Microservices	and	Containers • Java,	JavaScript/Node.js,	PHP,	Python • DevOps developer.oracle.com/ambassador @groundbreakers • Continuous	Delivery • Open	Source	Technologies • SQL/NoSQL	Databases • Machine	Learning,	AI,	Chatbots

Angular for Java Enterprise Developers: Oracle Code One 2018