...The good, the bad and the ugly
Hello!
I’m Sherry List
Senior frontend developer, Nordea
Women Techmaker Lead
You can find me at @SherrryLst
Hi!
I’m Ana Cidre
Developer Avocado, Ultimate Angular
Women Techmaker Lead
You can find me at @AnaCidre_
How we met / Why we give talks together
We do not want that!
What is a BAD architecture?
NOT Scalable
What is a BAD architecture?
NOT Scalable
NOT Maintainable
What is a BAD architecture?
NOT Scalable
NOT Maintainable
NOT Reliable
What is a BAD architecture?
NOT Scalable
NOT Maintainable
NOT Reliable
NOT Reusable
Sherry, Dinosaurs can eat us!
Is it:
Scalable
Maintainable
Reliable
Reusable
What will we learn today?
Breaking down components
What will we learn today?
Breaking down components
Smart and Dumb
components
What will we learn today?
Breaking down components
Smart and Dumb
components
Content projection
What will we learn today?
Breaking down components
Smart and Dumb
components
Content projection
Component communication
Let’s go see!
Car shows up and starts moving
Car shows up and starts moving
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); }
}
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);
}
}
We do not approve!
Why?
We should not add too much logic in one component
Why?
We should not add too much logic in one component
Separation of concerns
Why?
We should not add too much logic in one component
Separation of concerns
Single responsibility
How do we save the component?
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);
}
}
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();
}
}
Why it’s a bad component
Then t-rex steps on it and breaks it.
Car shows up and starts moving
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>
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')
}
We do not approve!
Why?
We should not add too much logic in one component
Separation of concerns
Single responsibility
How do we save the component?
Component based Architecture
Our way of structuring it
dinosaur-products.component.ts
export class DinosaurProductsComponent {
@Input() products: Product[];
@Input() habits: Habits;
@Output() buy = new EventEmitter<Product[]>();
value: Product[] = [];
habitInFocus: string;
...
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);
}
}
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>`})
Dumb and Smart components
Let’s go see!
Car shows up and starts moving
We do not approve!
Why?
Keep it DRY
Why?
Keep it DRY
Makes adding changes more complex
Why?
Keep it DRY
Makes adding changes more complex
Testing x2
Why?
Keep it DRY
Makes adding changes more complex
Testing x2
Not Maintainable
How do we save the component?
ngContent
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>
...
dinosaur-form.component.ts
...
<general-info
[parent]="form">
</general-info>
<ng-content select="button"></ng-content>
</form>
</div>
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>
Communication
One way data flow
</>
</>
</>
</>
</>
</>
One way data flow
</>
</>
</>
</>
</>
</>
One way data flow
</>
</>
</>
</>
</>
</>
One way data flow
</>
</>
</>
</>
</>
</>
More than one way data flow
</>
</>
</>
</>
</>
</>
More than one way data flow
</>
</>
</>
</>
</>
</>
What if we have a more complex scenario?
Nested Components
Input and Emit
BUT
Emit and Input
Emit and Input
</>
</>
</>
</>
</>
</>
</>
B
A
How can we solve it?
ngContent
</>
</>
</>
</>
</>
</>
</>
</>
</>
</>
</>
</>
</>
</>
</>
</>
</>
</>
</>
</>
</>
</>
</>
</>
BehaviorSubject
</>
</>
A
</>
B
</>
Service
</>
</>
A
</>
B
</>
Service
C
State Management
</>
</>
</>
</>
</>
</>
Store
Service
</>
</>
</>
</>
</>
</>
Store
Service
</>
</>
</>
</>
</>
</>
Store
Service
</>
</>
</>
</>
</>
</>
Store
Service
IF NECESSARY
State Management
Michael Ryan
14:05
You might not need NgRx
@MikeRyanDev
State Management
ngContent
BehaviorSubject
What’s Next?
Web Components
- The 80/20 rule
- Apps are different
The Ugly Truth
Advice
THANK YOU
Todd Motto
Mike Ryan
Mike Hladky
Manfred Steyer
Chris Norring
Kwinten Pisman
Brecht Billiet
Also thanks to:
Sherry List
@SherrryLst
Ana Cidre
@AnaCidre_
Thank YOU
Also thanks to:
For the Dino API
Source:
https://github.com/auth0-blog/sample-nodeserver-dinos
Kim Maida