1 of 109

...The good, the bad and the ugly

2 of 109

Hello!

I’m Sherry List

Senior frontend developer, Nordea

Women Techmaker Lead

You can find me at @SherrryLst

3 of 109

Hi!

I’m Ana Cidre

Developer Avocado, Ultimate Angular

Women Techmaker Lead

You can find me at @AnaCidre_

4 of 109

How we met / Why we give talks together

5 of 109

6 of 109

7 of 109

We do not want that!

8 of 109

What is a BAD architecture?

NOT Scalable

9 of 109

What is a BAD architecture?

NOT Scalable

NOT Maintainable

10 of 109

What is a BAD architecture?

NOT Scalable

NOT Maintainable

NOT Reliable

11 of 109

What is a BAD architecture?

NOT Scalable

NOT Maintainable

NOT Reliable

NOT Reusable

12 of 109

13 of 109

Sherry, Dinosaurs can eat us!

14 of 109

15 of 109

Is it:

Scalable

Maintainable

Reliable

Reusable

16 of 109

What will we learn today?

Breaking down components

17 of 109

What will we learn today?

Breaking down components

Smart and Dumb

components

18 of 109

What will we learn today?

Breaking down components

Smart and Dumb

components

Content projection

19 of 109

What will we learn today?

Breaking down components

Smart and Dumb

components

Content projection

Component communication

20 of 109

Let’s go see!

21 of 109

Car shows up and starts moving

22 of 109

Car shows up and starts moving

23 of 109

dinosaur-dashboard.component.ts

const httpOptions = {

headers: new HttpHeaders({ 'Content-Type': 'application/json'})

};

@Component({ … })

export class DinosaurDashboardComponent implements OnInit {

dinosaurs: Dinosaur[] = [];

dataUrl = './dino.json';

constructor( private http: HttpClient ) { }

ngOnInit() {

this.getDinosaurs().subscribe(dinosaurs => this.dinosaurs = dinosaurs.data)

}

getDinosaurs(): Observable<any> { return this.http.get(this.dataUrl, httpOptions); }

}

24 of 109

dinosaur-dashboard.component.ts

const httpOptions = {

headers: new HttpHeaders({ 'Content-Type': 'application/json'})

};

export class DinosaurDashboardComponent implements OnInit {

getDinosaurs(): Observable<any> {

return this.http.get(this.dataUrl, httpOptions);

}

}

25 of 109

We do not approve!

26 of 109

Why?

We should not add too much logic in one component

27 of 109

Why?

We should not add too much logic in one component

Separation of concerns

28 of 109

Why?

We should not add too much logic in one component

Separation of concerns

Single responsibility

29 of 109

How do we save the component?

30 of 109

31 of 109

dinosaur.service.ts

const httpOptions = {

headers: new HttpHeaders({ 'Content-Type': 'application/json'})

};

const dataUrl = './dino.json';

@Injectable({ providedIn: 'root' })

export class DinosaurService {

constructor(private http: HttpClient) {}

getDinosaurs(): Observable<Dinosaur[]> {

return this.http.get(dataUrl, httpOptions);

}

}

32 of 109

dinosaur-dashboard.component.ts

import { DinosaurService } from '../../services/dinosaur.service';

..

export class DinosaurDashboardComponent implements OnInit {

dinosaurs$: Observable<Dinosaur[]>;

constructor(

private service: DinosaurService,

private route: ActivatedRoute

) {}

ngOnInit() {

this.dinosaurs$ = this.service.getDinosaurs();

}

}

33 of 109

Why it’s a bad component

Then t-rex steps on it and breaks it.

34 of 109

35 of 109

Car shows up and starts moving

36 of 109

dinosaur-detail.component.html

<div class="dinosaur-detail">

<div class="dinosaur__section1">

<div class="dinosaur__img">

<img src="{{dinosaur.img}}">

</div>

<div class="dinosaur-info">

<h3>{{dinosaur.nickname}} the {{dinosaur.name}}</h3>

<p class="dinosaur__pronunciation">{{dinosaur.pronunciation}}</p>

<p>Meaning: {{dinosaur.meaningOfName}} </p>

<p><strong>About {{dinosaur.nickname}}:</strong> {{dinosaur.info}}</p>

<h4>★ Fun Facts</h4>

<p>

<span class="strong">Length:</span> {{dinosaur.size}} meters <br>

<span class="strong">Favourite Foor: </span> {{dinosaur.favouriteFood}}<br>

<span class="strong">Period: </span> {{dinosaur.period}}

</p>

<button

type="button"

class="btn btn__ok"

(click)="adoptDinosaur(basket)">

Adopt {{dinosaur.nickname}}

</button>

</div>

</div>

</div>

<div

*ngIf="dinosaur.characteristic.length"

class="dinosaur-characteristics">

<div

*ngFor="let char of dinosaur.characteristic"

class="dinosaur-characteristics__item"

[ngClass]="{

'is-true': char.value === true,

'is-false': char.value === false}">

{{char.label}}

</div>

</div>

<div class="dinosaur-form">

<label>

<h3>Select Services</h3>

</label>

<div class="dinosaur-form__list">

<dinosaur-products

[products]="products"

[habits]="dinosaur.habits"

(buy)=updateBasket($event)>

</dinosaur-products>

</div>

<span class="total">Total :</span> {{total}}€

<div class="dinosaur-form__actions">

<button

type="button"

class="btn btn__ok"

(click)="buyProducts(basket)"

[disabled]="total===0">

Sponsor {{dinosaur.nickname}}

</button>

</div>

</div>

37 of 109

dinosaur-detail.component.ts

export class DinosaurDetailComponent implements OnInit {

dinosaur: Dinosaur;

products: Product[];

exists = false;

basket: any[];

total = 0;

constructor(

private route: ActivatedRoute,

private dinosaurService: DinosaurService,

private productService: ProductService,

private location: Location

) { }

ngOnInit(): void {

this.getDinosaur();

this.getProducts();

}

}

getDinosaur(): void {

const id = +this.route.snapshot.paramMap.get('id');

this.dinosaurService.getDinosaur(id)

.subscribe(dinosaur => this.dinosaur = dinosaur);

}

getProducts(): void {

this.productService.getProducts()

.subscribe(products => this.products = products);

}

goBack(): void {

this.location.back();

}

onSelect(event: number[]) {

let products;

if (this.products && this.products.length) {

products = event.map(id =>

this.products.find(product => product.id === id)

);

} else {

products = this.products;

}

}

onAdopt(event: Dinosaur) {

}

onBuy(event: Dinosaur) {

}

onLeave(event: Dinosaur) {

}

updateBasket(products: Product[]) {

this.basket = products;

this.total = +this.calculateTotal(products);

}

calculateTotal(products: Product[]) {

return products.reduce((t, c) => c.price + t, 0);

}

buyProducts(form) {

console.log(form);

console.log('products bought!')

}

adoptDinosaur(form) {

console.log('dinosaur adopted!')

}

leaveDinosaur(form) {

console.log('dinosaur left')

}

38 of 109

We do not approve!

39 of 109

Why?

We should not add too much logic in one component

Separation of concerns

Single responsibility

40 of 109

How do we save the component?

41 of 109

Component based Architecture

42 of 109

43 of 109

Our way of structuring it

44 of 109

45 of 109

dinosaur-products.component.ts

export class DinosaurProductsComponent {

@Input() products: Product[];

@Input() habits: Habits;

@Output() buy = new EventEmitter<Product[]>();

value: Product[] = [];

habitInFocus: string;

...

46 of 109

dinosaur-products.component.ts

selectProduct(product: Product) {

if (this.existsInProducts(product)) {

this.value = this.value.filter(item => item.id !== product.id);

} else {

this.value = [...this.value, product];

}

this.buy.emit(this.value);

}

existsInProducts(product: Product) {

return this.value.some(val => val.id === product.id);

}

}

47 of 109

dinosaur-products.component.ts

@Component({

template: `

<input type="radio" name="selection" (click)="filter('likes')"> Likes

<input type="radio" name="selection" (click)="filter('dislikes')"> Dislikes

<div class="dinosaur-products">

<div *ngFor="let product of products"

class="dinosaur-products-item"

(click)="selectProduct(product)"

[class.active]="existsInProducts(product)"

[ngClass]="{

'like': isActivated(product,'likes'),

'dislike': isActivated(product,'dislikes')}">

{{ product.name }}

</div>

</div>`})

48 of 109

49 of 109

Dumb and Smart components

50 of 109

51 of 109

52 of 109

Let’s go see!

53 of 109

Car shows up and starts moving

54 of 109

55 of 109

56 of 109

We do not approve!

57 of 109

Why?

Keep it DRY

58 of 109

Why?

Keep it DRY

Makes adding changes more complex

59 of 109

Why?

Keep it DRY

Makes adding changes more complex

Testing x2

60 of 109

Why?

Keep it DRY

Makes adding changes more complex

Testing x2

Not Maintainable

61 of 109

How do we save the component?

62 of 109

ngContent

63 of 109

dinosaur-form.component.ts

<div class="dino-form">

<form [formGroup]="form" (ngSubmit)="onSubmit(form.value)">

<ng-content select="h3"></ng-content>

<name-input

[isSubmitted]="submitted"

[parent]="form">

</name-input>

...

64 of 109

dinosaur-form.component.ts

...

<general-info

[parent]="form">

</general-info>

<ng-content select="button"></ng-content>

</form>

</div>

65 of 109

app.component.ts

<dino-form>

<h3>Adopt a Dinosaur</h3>

<button type="submit">

Adopt!

</button>

</dino-form>

<dino-form>

<h3>Leave a Dinosaur :(</h3>

<button type="submit">

Leave your Dino with us

</button>

</dino-form>

66 of 109

67 of 109

Communication

68 of 109

One way data flow

</>

</>

</>

</>

</>

</>

69 of 109

One way data flow

</>

</>

</>

</>

</>

</>

70 of 109

One way data flow

</>

</>

</>

</>

</>

</>

71 of 109

One way data flow

</>

</>

</>

</>

</>

</>

72 of 109

More than one way data flow

</>

</>

</>

</>

</>

</>

73 of 109

More than one way data flow

</>

</>

</>

</>

</>

</>

74 of 109

75 of 109

What if we have a more complex scenario?

76 of 109

Nested Components

77 of 109

Input and Emit

78 of 109

79 of 109

BUT

80 of 109

Emit and Input

  • Makes the app slow.

81 of 109

Emit and Input

  • Makes the app slow.
  • Bad for debugging.

82 of 109

</>

</>

</>

</>

</>

</>

</>

B

A

83 of 109

How can we solve it?

84 of 109

ngContent

85 of 109

</>

</>

</>

</>

</>

</>

</>

</>

86 of 109

</>

</>

</>

</>

</>

</>

</>

</>

87 of 109

</>

</>

</>

</>

</>

</>

</>

</>

88 of 109

BehaviorSubject

89 of 109

90 of 109

</>

</>

A

</>

B

</>

Service

91 of 109

</>

</>

A

</>

B

</>

Service

C

92 of 109

State Management

93 of 109

</>

</>

</>

</>

</>

</>

Store

Service

94 of 109

</>

</>

</>

</>

</>

</>

Store

Service

95 of 109

</>

</>

</>

</>

</>

</>

Store

Service

96 of 109

</>

</>

</>

</>

</>

</>

Store

Service

97 of 109

IF NECESSARY

State Management

98 of 109

Michael Ryan

14:05

You might not need NgRx

@MikeRyanDev

99 of 109

State Management

ngContent

BehaviorSubject

100 of 109

101 of 109

What’s Next?

102 of 109

Web Components

103 of 109

- The 80/20 rule

- Apps are different

  • Form - driven
  • Data - driven
  • Native apps

The Ugly Truth

104 of 109

  • Only break components down when necessary
  • KISS (Keep It Simple, Stupid)
  • Testable

Advice

105 of 109

THANK YOU

Todd Motto

Mike Ryan

Mike Hladky

Manfred Steyer

Chris Norring

Kwinten Pisman

Brecht Billiet

106 of 109

Also thanks to:

107 of 109

Sherry List

@SherrryLst

Ana Cidre

@AnaCidre_

Thank YOU

108 of 109

Also thanks to:

For the Dino API

Source:

https://github.com/auth0-blog/sample-nodeserver-dinos

Kim Maida

109 of 109

Resources: