Loiane Groner
Java + JavaScript/HTML5 developer • Web/Angular GDE • Microsoft MVP • author @PacktPub More decks available at: https://www.slideshare.net/loianeg
Java, JavaScript + HTML5, Sencha, Cordova/Ionic, Angular, RxJS + all things reactive
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"loiane.angular-extension-pack",
"loiane.ts-extension-pack"
]
}
extensions.json
TSLint, snippets, testes, debug, organização TS, formatação arquivo
{
"editor.wordWrap": "off",
"editor.minimap.enabled": false,
"editor.codeActionsOnSave": { "source.fixAll.tslint": true },
"files.autoSave": "afterDelay",
"files.autoSaveDelay": 3000,
"prettier.tabWidth": 2,
"prettier.singleQuote": true,
"prettier.useTabs": false,
"html.format.wrapAttributes": "auto",
"html.format.wrapLineLength": 0,
"html.format.unformatted": "a, abbr, acronym, b, bdo, big, br, ...",
"workbench.editor.enablePreview": false,
"auto-rename-tag.activationOnLanguage": ["html", "xml"]
}
settings.json
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome with ng serve",
"url": "http://localhost:4200",
"webRoot": "${workspaceRoot}"
},
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome with ng test",
"url": "http://localhost:9876/debug.html",
"webRoot": "${workspaceRoot}"
},
{
"type": "chrome",
"request": "attach",
"name": "Attach to Chrome",
"port": 9222,
"webRoot": "${workspaceRoot}"
}
]
}
launch.json
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "npm",
"script": "build",
"presentation": {
"reveal": "always"
},
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": ["$tsc"]
},
{
"label": "test",
"type": "npm",
"script": "test",
"presentation": {
"reveal": "always"
},
"group": {
"kind": "test",
"isDefault": true
}
}
]
}
tasks.json
// Place your key bindings in this file to overwrite the defaults
[
// Tests
{
"key": "ctrl+shift+t",
"command": "workbench.action.tasks.test"
},
// Lint
{
"key": "ctrl+shift+l",
"command": "workbench.action.tasks.runTask",
"args": "lint"
},
// Serve app
{
"key": "ctrl+shift+s",
"command": "workbench.action.tasks.runTask",
"args": "serve"
}
]
keybindings-win.json
Código back-end
Protótipo
Angular
Integração back-end
Build produção
"scripts": {
"ng": "ng",
"start": "ng serve --aot --env=dev",
"server": "ng serve --proxy-config proxy.conf.js",
"build": "ng build --prod --aot --build-optimizer -op ../webapps/app"
},
package.json
Desenvolvimento Local
Desenvolvimento com Back-end
Produção
export const environment = {
production: false,
baseUrl: '/'
};
environment.dev.ts
export const environment = {
production: false,
baseUrl: '/api/'
};
environment.ts
export const environment = {
production: false,
baseUrl: '../'
};
environment.prod.ts
import { environment } from '@env/environment';
export class API {
static readonly BASE_URL = environment.baseUrl;
// LISTA DE CLIENTES
static readonly CLIENTS = `${API.BASE_URL}clients`;
}
// para usar essa API:
// this.http.get(API.CLIENTES)
API.ts
{
"author" : [ {
"id": 1,
"name" : "Loiane"
},
{
"id": 2,
"name" : "abcdef"
}
],
"client" : []
}
meuServidor.json
npm install -g json-server
json-server --watch meuServidor.json
const routes: Routes = [
{
path: '',
redirectTo: 'modulo-1',
pathMatch: 'full'
},
{
path: 'modulo-1',
component: Modulo1Component
},
{
path: 'modulo-2',
loadChildren: './modulo-2/modulo-2.module#Modulo2Module',
data: { preload: true } // pré-carregar em background
},
{
path: 'modulo-3',
loadChildren: './feature-3/modulo-3.module#Modulo3Module'
}
];
@NgModule({
imports: [RouterModule.forRoot(routes, {
preloadingStrategy: AppCustomPreloader
})], // implementamos nossa lógica
exports: [RouterModule],
providers: [AppCustomPreloader]
})
export class AppRoutingModule { }
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
export class AppCustomPreloader implements PreloadingStrategy {
preload(route: Route, load: Function): Observable<any> {
return route.data && route.data.preload ? load() : of(null);
}
}
tamanho é documento!
<mat-list>
<app-task-item *ngFor="let task of tasks$ | async"
[task]="task"
(remove)="onRemove(task)"
(edit)="onUpdate(task, $event)">
</app-task-item>
</mat-list>
tasks-list.component.html
@Component({
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TasksListComponent {
@Input('tasks') tasks$: Observable<Task[]>;
@Output() remove: EventEmitter<any> = new EventEmitter(false);
@Output() edit: EventEmitter<any> = new EventEmitter(false);
onRemove(task) {
this.remove.emit(task);
}
onUpdate(task, changes) {
this.edit.emit({task: task, updates: changes});
}
}
tasks-list.component.ts
<mat-card>
<app-task-form (createTask)="onCreateTask($event)"></app-task-form>
<mat-spinner *ngIf="isLoading$ | async; else taskList"></mat-spinner>
<ng-template #taskList>
<app-tasks-list
[tasks]="tasks$"
(remove)="onRemoveTask($event)"
(edit)="onUpdateTask($event)">
</app-tasks-list>
</ng-template>
<div class="error-msg" *ngIf="error$ | async as error">
<p>{{ error }}</p>
</div>
</mat-card>
tasks.component.html
export class TasksComponent implements OnInit {
tasks$: Observable<Task[]>;
isLoading$: Observable<boolean>;
error$: Observable<string>;
constructor(private taskStoreService: TaskStoreService) {}
ngOnInit() {
this.taskStoreService.dispatchLoadAction();
this.tasks$ = this.taskStoreService.getTasks();
this.isLoading$ = this.taskStoreService.getIsLoading();
this.error$ = this.taskStoreService.getError();
}
onCreateTask(title) {
this.taskStoreService.dispatchCreateAction(new Task(title));
}
onRemoveTask(task) {
this.taskStoreService.dispatchRemoveAction(task);
}
onUpdateTask(event) {
this.taskStoreService.dispatchUpdateAction(event.updates);
}
}
tasks.component.ts
<form (ngSubmit)="onSubmit()" novalidate>
<mat-input-container class="example-full-width">
<input matInput placeholder="What needs to be done?" type="text"
name="title"
[(ngModel)]="title"
(keyup.escape)="clear()"
autocomplete="off"
autofocus>
</mat-input-container>
</form>
component1.html
<mat-list-item class="list-item">
<mat-checkbox color="primary" type="checkbox" [name]="task.id" [(ngModel)]="task.completed" (change)="onEdit()">
<span [class.task-completed]="task.completed">{{task.title}}</span>
</mat-checkbox>
<span class="fill-remaining-space"></span>
<span>
<button mat-mini-fab (click)="onRemove()">
<mat-icon>delete_forever</mat-icon>
</button>
<button mat-mini-fab (click)="openEditDialog()" [disabled]="task.completed" color="primary">
<mat-icon>mode_edit</mat-icon>
</button>
</span>
</mat-list-item>
component2.html
<mat-list>
<app-task-item *ngFor="let task of tasks$ | async"
[task]="task"
(remove)="onRemove(task)"
(edit)="onUpdate(task, $event)">
</app-task-item>
</mat-list>
component3.html
<mat-card>
<form (ngSubmit)="onSubmit()" novalidate>
<mat-input-container class="example-full-width">
<input matInput placeholder="What needs to be done?" type="text"
name="title"
[(ngModel)]="title"
(keyup.escape)="clear()"
autocomplete="off"
autofocus>
</mat-input-container>
</form>
<mat-spinner *ngIf="isLoading$ | async; else taskList" style="margin:0 auto;"></mat-spinner>
<ng-template #taskList>
<mat-list>
<mat-list-item class="list-item" *ngFor="let task of tasks$ | async">
<mat-checkbox color="primary" type="checkbox" [name]="task.id" [(ngModel)]="task.completed" (change)="onEdit()">
<span [class.task-completed]="task.completed">{{task.title}}</span>
</mat-checkbox>
<span class="fill-remaining-space"></span>
<span>
<button mat-mini-fab (click)="onRemove()">
<mat-icon>delete_forever</mat-icon>
</button>
<button mat-mini-fab (click)="openEditDialog()" [disabled]="task.completed" color="primary">
<mat-icon>mode_edit</mat-icon>
</button>
</span>
</mat-list-item>
</mat-list>
</ng-template>
<div class="error-msg" *ngIf="error$ | async as error">
<p>{{ error }}</p>
</div>
</mat-card>
component1.html
component3.html
@Injectable()
export class ApiService {
constructor(public http: HttpClient) { }
getRequest<T>(url: string, params?: any) {
return this.http.get<RetornoDoServer<T>>(url, {
params: this.getQueryParams(params)
})
.take(1); // já podemos 'matar' o Observable no Ajax
}
postRequest(url: string, body: any) {
// POST
}
private getQueryParams(params: any) {
// lógica para setar os query params com Angular v5+
}
downloadFile(url: string, params?: any) {
// lógica para download de arquivo
}
uploadFile(file: AppFile | AppFile[], body?: any, params?: any) {
// lógica para download de arquivo
}
}
api.service.ts
@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}`);
}
}
tasks.service.ts
export interface RetornoDoServer<T> {
dados: T[];
total: number;
successo?: boolean;
msgErro?: string;
}
Retorno
export abstract class BaseFormComponent implements IFormCanDeactivate {
protected formSubmitAttempt: boolean;
protected validateDirty = true;
form: FormGroup;
constructor() {
this.formSubmitAttempt = false;
}
isFieldInvalid(field: string) {
// return lógica validação dos campos
}
onSubmit() {
this.formSubmitAttempt = true;
// lógica genérica
}
onReset() { }
canDeactivate(): Observable<boolean> {
return this.modalService.showConfirm(
'DoYouWantToLeavePage',
'DoYouWantToLeavePageMsg'
);
}
onCancel() {
this.location.back();
}
}
@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}`);
}
}
@Injectable()
export class TasksService extends CRUDService<Task> {
constructor(public http: HttpClient) {
super(http, API.TASKS_URL)
}
}
export class TasksLogicService {
canCreate() {
// lógica para verificar se usuário pode criar
}
filterTasks(querySearch: string) {
// faz filtro e retorna resultado para o componente
}
verifyTaskIsValid(task: Task) {
// lógica com 50 linhas de código
}
}
import { AuthService } from '../../../core/security/auth/auth.service';
import { AuthService } from '@projeto/core';
import { AuthService } from '../../../core/security/auth/auth.service';
import { AuthService } from '@projeto/core';
{
"compilerOptions": {
...
"baseUrl": "src",
"paths": {
"@projeto/*": ["app/*"],
"@env/*": ["environments/*"]
}
}
}
tsconfig.json
"scripts": {
"ng": "ng",
"build": "ng build --prod --aot --build-optimizer -op ../webapps/app"
},
package.json
export * from './my-module/my-module.module';
"dependencies": {
...
"toolkit-cwt": "file:../toolkit/dist/toolkit-cwt-0.0.0.tgz",
...
}
package.json
<div style="text-align:center">
<h1>
Welcome to {{ title }}!
</h1>
</div>
<app-my-component></app-my-component>
app.component.html
By Loiane Groner
Você está trabalhando em projetos Angular e ainda tem algumas perguntas sobre a estrutura do seu projeto? Como organizar os módulos e componentes de uma aplicação para facilitar o fluxo de dados e a manutenção do projeto? Como fazer com que todo o time siga o mesmos padrões e melhore a produtividade? Existe alguma maneira mais fácil de se trabalhar com prototipação mesmo integrando o front-end com o servidor? Como fazer para diminuir o impacto das migrações de uma versão para outra? Nessa talk compartilho algumas das experiências e decisões sobre melhorias de compartilhamento de código e componentes que aprendi desenvolvendo projetos angular que estão em produção.
Java + JavaScript/HTML5 developer • Web/Angular GDE • Microsoft MVP • author @PacktPub More decks available at: https://www.slideshare.net/loianeg