SlideShare a Scribd company logo
Lessons Learned
2 years of
Dirk Luijk
@dirkluijk
#frontend
#angular
#fullstack
2 years of angular: lessons learned
About Bingel
● Full lesson support
● Instruction, exercises & testing
● Maths & language courses
● Adaptive learning
● Modern & cross-platform
2 years of angular: lessons learned
2 years of angular: lessons learned
2 years of angular: lessons learned
2 years of angular: lessons learned
2 years of angular: lessons learned
2 years of angular: lessons learned
2 years of angular: lessons learned
Architecture
How it started…
ng new bingel-app$ ng new bingel-student-app
Architecture
student app
Architecture
student app
tasks module
lessons module
results module
Architecture
student app
tasks module
lessons module
results module
shared module
Architecture
student app
tasks module
lessons module
results module
shared module
teacher app
Architecture
student app
tasks module
lessons module
results module
shared module
teacher app
instruction module
analysis module
shared module
Architecture
student app
tasks module
lessons module
results module
shared module
teacher app
"shared code"
instruction module
analysis module
shared module
Architecture
student app
tasks module
lessons module
results module
shared module
teacher app
ui library
interactions
library
api library common library
instruction module
analysis module
shared module
Architecture
student app
tasks module
lessons module
results module
shared module
teacher app
@p1/ui @p1/interactions @p1/api @p1/common
preview app admin app
instruction module
analysis module
shared module
preview module licensing module
content graph module
@p1/features
Architecture
student app
tasks module
lessons module
results module
shared module
teacher app
@p1/ui @p1/interactions @p1/api @p1/common
preview app admin app
instruction module
analysis module
shared module
preview module licensing module
content graph module
@p1/features
single repository (monorepo)
How?
Nx workspaces
● Builts on top of Angular CLI
● Supports monorepo approach
● Provides "affected builds" option:
○ build only what has been changed
● Better separation of packages
Architecture: lessons learned
1. Consider a monorepo
○ simple versioning & release process
○ easier refactoring
○ well supported by Angular CLI + Nx Workspaces
2. Think in packages and their responsibilities
Routing
Routing
[
{
path: 'overview',
loadChildren: 'overview/overview.module#OverviewModule'
},
{
path: 'lessons',
loadChildren: 'lessons/lessons.module#LessonsModule'
},
{
path: 'tasks',
loadChildren: 'tasks/tasks.module#TasksModule'
}
];
Routing guards
{
path: 'tasks',
resolve: {
blocks: BlocksResolver
},
children: [
{
path: '',
canActivate: [TasksRedirectGuard]
},
{
path: 'blocks',
component: TasksBlocksComponent
},
{
path: 'blocks/:blockId/weeks/:weekId',
component: TasksSelectionComponent
}
]
}
Routing guards
{
path: 'tasks',
resolve: {
blocks: BlocksResolver
},
children: [
{
path: '',
canActivate: [TasksRedirectGuard]
},
{
path: 'blocks',
component: TasksBlocksComponent
},
{
path: 'blocks/:blockId/weeks/:weekId',
component: TasksSelectionComponent
}
]
}
Routing in Angular is reactive.
Routing in Angular is reactive.
But not necessarily.
ActivatedRoute
class WeekComponent implements OnInit {
week: Week;
constructor(private route: ActivatedRoute, private weekService: WeekService) {}
ngOnInit(): void {
this.route.paramMap
.pipe(
map(params => params.get('weekId')),
switchMap(weekId => this.weekService.getWeek(weekId))
)
.subscribe(week => {
this.week = week;
});
}
}
ActivatedRoute
class WeekComponent implements OnInit {
week: Week;
constructor(private route: ActivatedRoute, private weekService: WeekService) {}
ngOnInit(): void {
this.route.paramMap
.pipe(
map(params => params.get('weekId')),
switchMap(weekId => this.weekService.getWeek(weekId))
)
.subscribe(week => {
this.week = week;
});
}
}
reactive!
ActivatedRoute vs. ActivatedRouteSnapShot
class WeekComponent {
week: Week;
constructor(private route: ActivatedRoute, private weekService: WeekService) {}
ngOnInit(): void {
this.weekService
.getWeek(this.route.snapshot.paramMap.get('weekId'))
.subscribe(week => this.week = week);
}
}
ActivatedRoute vs. ActivatedRouteSnapShot
class WeekComponent {
week: Week;
constructor(private route: ActivatedRoute, private weekService: WeekService) {}
ngOnInit(): void {
this.weekService
.getWeek(this.route.snapshot.paramMap.get('weekId'))
.subscribe(week => this.week = week);
}
}
not reactive (static)!
Routing: lessons learned
1. Make use of lazy-loaded feature modules
○ Breaks down bundles into smaller chunks
2. Make smart use of guards!
3. Get used to the (reactive) API of Angular Router
Components
Different types of components
Page Components
Routed, fetches data, (almost) no presentation logic1
Feature Components
Specific presentation logic (bound to domain model)2
UI Components
Generic presentation logic (not bound to domain model)3
Different types of components
TasksModule
TaskSelectionComponent
/tasks/blocks/week/:id
TaskDetailsComponent
/tasks/task/:id
LessonsModule
LessonsComponent
/lessons
Different types of components
TasksModule
TaskSelectionComponent
/tasks/blocks/week/:id
TaskDetailsComponent
/tasks/task/:id
SidebarModule
SidebarComponent
AccordionModule CardModule
AccordionComponent CardComponent
LessonsModule
LessonsComponent
/lessons
Different types of components
TasksModule
TaskSelectionComponent
/tasks/blocks/week/:id
TaskDetailsComponent
/tasks/task/:id
SidebarModule
SidebarComponent
AccordionModule CardModule
AccordionComponent CardComponent
TaskPreviewComponent
TaskMenuComponent
LessonsModule
LessonsComponent
/lessons
Different types of components
TasksModule
TaskSelectionComponent
/tasks/blocks/week/:id
TaskDetailsComponent
/tasks/task/:id
SidebarModule
SidebarComponent
AccordionModule CardModule
AccordionComponent CardComponent
TaskPreviewComponent
TaskMenuComponent
LessonsModule
LessonsComponent
/lessons
Only be generic when needed
Page components:
Try to be specific. Duplicate page variants, avoid pages that become fuzzy.
UI components:
Be generic. Don't couple to domain model.
Styling
Style encapsulation! 😍
Concept from Web Components
● A component has its own "shadow DOM"
● A component can only style its own elements
● Prevents "leaking" styling and unwanted side-effects
● No conventies like BEM, SMACSS, …, needed anymore
Example
@Component({
selector: 'p1-panel',
template: `
<header>...</header>
`,
styles: [`
header {
margin-bottom: 1em;
}
`]
})
class PanelComponent {}
Don't forget the :host element!
Don't forget the :host element!
@Component({
selector: 'p1-panel',
template: `
<div class="wrapper">
<header>...</header>
<footer>...</footer>
</div>
`,
styles: [`
.wrapper {
display: flex;
}
`]
})
class PanelComponent {}
Don't forget the :host element!
@Component({
selector: 'p1-panel',
template: `
<div class="wrapper">
<header>...</header>
<footer>...</footer>
</div>
`,
styles: [`
.wrapper {
display: flex;
}
`]
})
class PanelComponent {}
Don't forget the :host element!
@Component({
selector: 'p1-panel',
template: `
<header>...</header>
<footer>...</footer>
`,
styles: [`
:host {
display: flex;
}
`]
})
class PanelComponent {}
Don't forget the :host element!
@Component({
selector: 'p1-panel',
template: `
<header>...</header>
<footer>...</footer>
`,
styles: [`
:host {
display: flex;
}
`]
})
class PanelComponent {}
Robust default styling
Robust default styling
@Component({
selector: 'p1-panel',
template: `
<header>...</header>
<footer>...</footer>`,
styles: [`
:host {
/* ... */
}
`]
})
class PanelComponent {}
Robust default styling
@Component({
selector: 'p1-some-page',
template: `
<p1-panel *ngFor="week of weeks"></p1-panel>
`,
styles: [`
p1-panel {
/* ... */
}
`]
})
class SomePageComponent {}
@Component({
selector: 'p1-panel',
template: `
<header>...</header>
<footer>...</footer>`,
styles: [`
:host {
/* ... */
}
`]
})
class PanelComponent {}
Robust default styling
@Component({
selector: 'p1-some-page',
template: `
<p1-panel *ngFor="week of weeks"></p1-panel>
`,
styles: [`
p1-panel {
/* ... */
}
`]
})
class SomePageComponent {}
@Component({
selector: 'p1-panel',
template: `
<header>...</header>
<footer>...</footer>`,
styles: [`
:host {
/* ... */
}
`]
})
class PanelComponent {}
More
default styling!
Less
contextual styling!
Component styling vs. global styling 🤔
● Global styling
○ Try to avoid as much as possible!
● Component styling
○ Makes use of style encapsulation.
Awesome feature, :host-context()
@Component({
selector: 'p1-panel',
template: `
<header>...</header>
`,
styles: [`
header { margin-bottom: 1em; }
:host-context(p1-sidebar) header {
margin-bottom: 0;
}
`]
})
export class PanelComponent {}
Styling: lessons learned
1. Prefer component styling over global styling
2. Prevent using "cheats" like ::ng-deep. It's a smell! 💩
3. Don't forget the :host element!
4. Go for robust and flexible default styling
5. Make use of CSS inherit keyword
6. Use EM/REM instead of pixels
Testing! 🤮
Testing! 😍
Unit testing
Angular provides the following tools out-of-the-box:
➔ Karma runner with Jasmine as test framework 🤔
➔ TestBed as test API for Angular Components 🤔
TestBed API
describe('ButtonComponent', () => {
let fixture: ComponentFixture<ButtonComponent>;
let instance: ButtonComponent;
let debugElement: DebugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ButtonComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(ButtonComponent);
instance = fixture.componentInstance;
debugElement = fixture.debugElement;
}));
it('should set the class name according to the [className] input', () => {
instance.className = 'danger';
fixture.detectChanges();
const button = debugElement.query(By.css('button')).nativeElement as HTMLButtonElement;
expect(button.classList.contains('danger')).toBeTruthy();
expect(button.classList.contains('success')).toBeFalsy();
});
});
TestBed API
describe('ButtonComponent', () => {
let fixture: ComponentFixture<ButtonComponent>;
let instance: ButtonComponent;
let debugElement: DebugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ButtonComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(ButtonComponent);
instance = fixture.componentInstance;
debugElement = fixture.debugElement;
}));
it('should set the class name according to the [className] input', () => {
instance.className = 'danger';
fixture.detectChanges();
const button = debugElement.query(By.css('button')).nativeElement as HTMLButtonElement;
expect(button.classList.contains('danger')).toBeTruthy();
expect(button.classList.contains('success')).toBeFalsy();
});
});
TL;DR 🤔
Hello @netbasal/spectator! 😎
AWESOME library for component testing in Angular
➔ Simple API
➔ Better typings
➔ Custom matchers
➔ Mocking integration
➔ Simple way of querying
Spectator API
describe('ButtonComponent', () => {
const createComponent = createTestComponentFactory(ButtonComponent);
let spectator: Spectator<ButtonComponent>;
beforeEach(() => {
spectator = createComponent();
});
it('should set the class name according to the [className] input', () => {
spectator.component.className = 'danger';
spectator.detectChanges();
expect('button').toHaveClass('danger');
expect('button').not.toHaveClass('success');
});
});
Example: p1-keyboard
Go for readable test code!
it('should show the pressed key while pointing down', () => {
const keys = spectator.queryAll('.key');
const key1 = keys[0]; // q-key
const key2 = keys[1]; // w-key
spectator.dispatchMouseEvent(key1, 'pointerdown');
expect(key1.classList.contains('key-pressed')).toBe(true);
expect(key2.classList.contains('key-pressed')).toBe(false);
expect(spectator.component.activeKey).toBe('q');
spectator.dispatchMouseEvent(key1, 'pointerup');
expect(key1.classList.contains('key-pressed')).toBe(false);
expect(key2.classList.contains('key-pressed')).toBe(false);
expect(spectator.component.activeKey).toBeFalsy();
spectator.dispatchMouseEvent(key2, 'pointerdown');
expect(key1.classList.contains('key-pressed')).toBe(false);
expect(key2.classList.contains('key-pressed')).toBe(true);
expect(spectator.component.activeKey).toBe('w');
});
Go for readable test code!
it('should show the pressed key while pointing down', () => {
const keys = spectator.queryAll('.key');
const key1 = keys[0]; // q-key
const key2 = keys[1]; // w-key
spectator.dispatchMouseEvent(key1, 'pointerdown');
expect(key1.classList.contains('key-pressed')).toBe(true);
expect(key2.classList.contains('key-pressed')).toBe(false);
expect(spectator.component.activeKey).toBe('q');
spectator.dispatchMouseEvent(key1, 'pointerup');
expect(key1.classList.contains('key-pressed')).toBe(false);
expect(key2.classList.contains('key-pressed')).toBe(false);
expect(spectator.component.activeKey).toBeFalsy();
spectator.dispatchMouseEvent(key2, 'pointerdown');
expect(key1.classList.contains('key-pressed')).toBe(false);
expect(key2.classList.contains('key-pressed')).toBe(true);
expect(spectator.component.activeKey).toBe('w');
});
Go for readable test code!
it('should show the pressed key while pointing down', () => {
const keys = spectator.queryAll('.key');
const keyQ = keys[0];
const keyW = keys[1];
spectator.dispatchMouseEvent(keyQ, 'pointerdown');
expect(keyQ.classList.contains('key-pressed')).toBe(true);
expect(keyW.classList.contains('key-pressed')).toBe(false);
expect(spectator.component.activeKey).toBe('q');
spectator.dispatchMouseEvent(keyQ, 'pointerup');
expect(keyQ.classList.contains('key-pressed')).toBe(false);
expect(keyW.classList.contains('key-pressed')).toBe(false);
expect(spectator.component.activeKey).toBeFalsy();
spectator.dispatchMouseEvent(keyW, 'pointerdown');
expect(keyQ.classList.contains('key-pressed')).toBe(false);
expect(keyW.classList.contains('key-pressed')).toBe(true);
expect(spectator.component.activeKey).toBe('w');
});
Go for readable test code!
it('should show the pressed key while pointing down', () => {
const keys = spectator.queryAll('.key');
const keyQ = keys[0];
const keyW = keys[1];
spectator.dispatchMouseEvent(keyQ, 'pointerdown');
expect(keyQ.classList.contains('key-pressed')).toBe(true);
expect(keyW.classList.contains('key-pressed')).toBe(false);
expect(spectator.component.activeKey).toBe('q');
spectator.dispatchMouseEvent(keyQ, 'pointerup');
expect(keyQ.classList.contains('key-pressed')).toBe(false);
expect(keyW.classList.contains('key-pressed')).toBe(false);
expect(spectator.component.activeKey).toBeFalsy();
spectator.dispatchMouseEvent(keyW, 'pointerdown');
expect(keyQ.classList.contains('key-pressed')).toBe(false);
expect(keyW.classList.contains('key-pressed')).toBe(true);
expect(spectator.component.activeKey).toBe('w');
});
Go for readable test code!
it('should show the pressed key while pointing down', () => {
const [keyQ, keyW] = spectator.queryAll('.key');
spectator.dispatchMouseEvent(keyQ, 'pointerdown');
expect(keyQ.classList.contains('key-pressed')).toBe(true);
expect(keyW.classList.contains('key-pressed')).toBe(false);
expect(spectator.component.activeKey).toBe('q');
spectator.dispatchMouseEvent(keyQ, 'pointerup');
expect(keyQ.classList.contains('key-pressed')).toBe(false);
expect(keyW.classList.contains('key-pressed')).toBe(false);
expect(spectator.component.activeKey).toBeFalsy();
spectator.dispatchMouseEvent(keyW, 'pointerdown');
expect(keyQ.classList.contains('key-pressed')).toBe(false);
expect(keyW.classList.contains('key-pressed')).toBe(true);
expect(spectator.component.activeKey).toBe('w');
});
Go for readable test code!
it('should show the pressed key while pointing down', () => {
const [keyQ, keyW] = spectator.queryAll('.key');
spectator.dispatchMouseEvent(keyQ, 'pointerdown');
expect(keyQ.classList.contains('key-pressed')).toBe(true);
expect(keyW.classList.contains('key-pressed')).toBe(false);
expect(spectator.component.activeKey).toBe('q');
spectator.dispatchMouseEvent(keyQ, 'pointerup');
expect(keyQ.classList.contains('key-pressed')).toBe(false);
expect(keyW.classList.contains('key-pressed')).toBe(false);
expect(spectator.component.activeKey).toBeFalsy();
spectator.dispatchMouseEvent(keyW, 'pointerdown');
expect(keyQ.classList.contains('key-pressed')).toBe(false);
expect(keyW.classList.contains('key-pressed')).toBe(true);
expect(spectator.component.activeKey).toBe('w');
});
Go for readable test code!
const pointerDown = element => spectator.dispatchMouseEvent(element, 'pointerdown');
const pointerUp = element => spectator.dispatchMouseEvent(element, 'pointerdown');
it('should show the pressed key while pointing down', () => {
const [keyQ, keyW] = spectator.queryAll('.key');
pointerDown(keyQ);
expect(keyQ.classList.contains('key-pressed')).toBe(true);
expect(keyW.classList.contains('key-pressed')).toBe(false);
expect(spectator.component.activeKey).toBe('q');
pointerUp(keyQ);
expect(keyQ.classList.contains('key-pressed')).toBe(false);
expect(keyW.classList.contains('key-pressed')).toBe(false);
expect(spectator.component.activeKey).toBeFalsy();
pointerDown(keyW);
expect(keyQ.classList.contains('key-pressed')).toBe(false);
expect(keyW.classList.contains('key-pressed')).toBe(true);
expect(spectator.component.activeKey).toBe('w');
});
Go for readable test code!
const pointerDown = element => spectator.dispatchMouseEvent(element, 'pointerdown');
const pointerUp = element => spectator.dispatchMouseEvent(element, 'pointerdown');
it('should show the pressed key while pointing down', () => {
const [keyQ, keyW] = spectator.queryAll('.key');
pointerDown(keyQ);
expect(keyQ.classList.contains('key-pressed')).toBe(true);
expect(keyW.classList.contains('key-pressed')).toBe(false);
expect(spectator.component.activeKey).toBe('q');
pointerUp(keyQ);
expect(keyQ.classList.contains('key-pressed')).toBe(false);
expect(keyW.classList.contains('key-pressed')).toBe(false);
expect(spectator.component.activeKey).toBeFalsy();
pointerDown(keyW);
expect(keyQ.classList.contains('key-pressed')).toBe(false);
expect(keyW.classList.contains('key-pressed')).toBe(true);
expect(spectator.component.activeKey).toBe('w');
});
Go for readable test code!
const pointerDown = element => spectator.dispatchMouseEvent(element, 'pointerdown');
const pointerUp = element => spectator.dispatchMouseEvent(element, 'pointerdown');
it('should show the pressed key while pointing down', () => {
const [keyQ, keyW] = spectator.queryAll('.key');
pointerDown(keyQ);
expect(keyQ).toHaveClass('key-pressed');
expect(keyW).not.toHaveClass('key-pressed');
expect(spectator.component.activeKey).toBe('q');
pointerUp(keyQ);
expect(keyQ).not.toHaveClass('key-pressed');
expect(keyW).not.toHaveClass('key-pressed');
expect(spectator.component.activeKey).toBeFalsy();
pointerDown(keyW);
expect(keyQ).not.toHaveClass('key-pressed');
expect(keyW).toHaveClass('key-pressed');
expect(spectator.component.activeKey).toBe('w');
});
Class testing or component testing?
➔ Discuss it with your team
➔ What means the "unit" in "unit testing"?
➔ What does a class test prove about the quality of a component?
➔ Technical tests vs. functional tests
To mock... or not to mock?
Pure unit tests are run in isolation.
➔ Want to mock components/directives/pipes? Hello ng-mocks!
To mock... or not to mock?
➔ Pure unit tests are run in isolation.
➔ Want to mock components/directives/pipes? Hello ng-mocks!
describe('AudioPlayerComponent', () => {
const createHost = createHostComponentFactory({
component: AudioPlayerComponent,
declarations: [
MockComponent(EbMediaControlsComponent)
],
providers: [
{ provide: AUTOPLAY_DELAY, useValue: 1000 }
]
});
To mock... or not to mock?
➔ Pure unit tests are run in isolation.
➔ Want to mock components/directives/pipes? Hello ng-mocks!
But: nothing wrong with a bit of integration testing!
➔ Discuss with your team how you implement a test pyramid
Organize your testdata
● Consider complete and type-safe testdata
● Organize fixture data in one place
● Useful when using large & complex domain models
● Don't repeat yourself
Hello Jest!
● Fast, robust and powerful test framework
● Replaces Karma/Jasmine
● Simple migration path: compatible with Jasmine syntax (describe/it)
● Integrates very well with Angular (thymikee/jest-preset-angular)
● Integrates very well with Angular CLI (@angular-builders/jest)
Unit testing: lessons learned!
1. Consider Spectator instead of Angular TestBed
2. Consider Jest instead of Karma/Jasmine
3. Make unit testing FUN!
4. Go for functional component testing
- Don't think in methods, but in user events.
- Don't test from component class, but from DOM
Wrapping up...
Code quality
- Make your TypeScript compiler as strict as possible
- Make your TypeScript linting as strict as possible
- Take a look at Prettier
- Use modern JavaScript APIs and standards
- Don't fear old browsers!
- Embrace polyfills!
Complex stuff
- Take a look at @angular/cdk!
- Look at @angular/material for inspiration, to learn 'the Angular way'
- Use the power of MutationObserver, ResizeObserver, IntersectionObserver
- Don't be afraid to refactor!
Spend time on open-source development
We use open-source stuff every day. We should care about it.
1. Report your issue, and take time to investigate!
○ If you try hard, others will as well!
2. Try to fix it yourself, be a contributor!
○ Open-source projects are for everyone!
3. Improve yourself, read blogs. Know what's going on.
Reduce technical debt
1. Don't go for the best solution the first time!
○ Keep it simple so that you can obtain insights.
○ Prevent "over-engineering".
2. … but always improve things the second time!
○ Don't create refactor user stories, but take your refactor time for every user story!
○ This prevents postponing things and makes it more fun.
3. Too much technical debt? Bring it on the agenda for the upcoming sprint.
Thanks! 😁

More Related Content

What's hot (20)

PDF
Introduction to Angular 2
Naveen Pete
 
PDF
Angular 2... so can I use it now??
Laurent Duveau
 
PDF
Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)
Ontico
 
PDF
Angular Dependency Injection
Nir Kaufman
 
ODP
Introduction to Angular 2
Knoldus Inc.
 
PDF
Angular 2 : le réveil de la force
Nicolas PENNEC
 
PDF
Exploring Angular 2 - Episode 2
Ahmed Moawad
 
PDF
Angular Weekend
Troy Miles
 
PPTX
Angular2 for Beginners
Oswald Campesato
 
PDF
Using hilt in a modularized project
Fabio Collini
 
PDF
Building maintainable app
Kristijan Jurković
 
PPTX
Angular 2 - Better or worse
Vladimir Georgiev
 
PDF
Reactive Programming with JavaScript
Codemotion
 
PDF
Understanding react hooks
Samundra khatri
 
PDF
How Angular2 Can Improve Your AngularJS Apps Today!
Nir Kaufman
 
PPTX
Angular 1.x in action now
GDG Odessa
 
PDF
Workshop 23: ReactJS, React & Redux testing
Visual Engineering
 
PDF
Flutter hooks tutorial (part 1) flutter animation using hooks (use effect and...
Katy Slemon
 
PDF
React on es6+
Nikolaus Graf
 
PDF
Tech Webinar: Angular 2, Introduction to a new framework
Codemotion
 
Introduction to Angular 2
Naveen Pete
 
Angular 2... so can I use it now??
Laurent Duveau
 
Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)
Ontico
 
Angular Dependency Injection
Nir Kaufman
 
Introduction to Angular 2
Knoldus Inc.
 
Angular 2 : le réveil de la force
Nicolas PENNEC
 
Exploring Angular 2 - Episode 2
Ahmed Moawad
 
Angular Weekend
Troy Miles
 
Angular2 for Beginners
Oswald Campesato
 
Using hilt in a modularized project
Fabio Collini
 
Building maintainable app
Kristijan Jurković
 
Angular 2 - Better or worse
Vladimir Georgiev
 
Reactive Programming with JavaScript
Codemotion
 
Understanding react hooks
Samundra khatri
 
How Angular2 Can Improve Your AngularJS Apps Today!
Nir Kaufman
 
Angular 1.x in action now
GDG Odessa
 
Workshop 23: ReactJS, React & Redux testing
Visual Engineering
 
Flutter hooks tutorial (part 1) flutter animation using hooks (use effect and...
Katy Slemon
 
React on es6+
Nikolaus Graf
 
Tech Webinar: Angular 2, Introduction to a new framework
Codemotion
 

Similar to 2 years of angular: lessons learned (20)

PDF
angular fundamentals.pdf angular fundamentals.pdf
NuttavutThongjor1
 
PDF
angular fundamentals.pdf
NuttavutThongjor1
 
PDF
Dumb and smart components + redux (1)
Brecht Billiet
 
PDF
Angular meetup 2 2019-08-29
Nitin Bhojwani
 
PPTX
Advanced angular
Sumit Kumar Rakshit
 
PPTX
Angular or React
Orkhan Gasimov
 
PDF
Angular Interview Question & Answers PDF By ScholarHat
Scholarhat
 
PDF
Angular 2 - The Next Framework
Commit University
 
PDF
Angular performance slides
David Barreto
 
PDF
Angular2 with TypeScript
Rohit Bishnoi
 
PDF
MVS: An angular MVC
David Rodenas
 
PPTX
Angular2 + rxjs
Christoffer Noring
 
PDF
Capstone ms2
TanishGupta44
 
PDF
"Frameworks in 2015" Андрей Листочкин
Fwdays
 
PDF
Angular - Chapter 3 - Components
WebStackAcademy
 
PDF
Angular Optimization Web Performance Meetup
David Barreto
 
PDF
Angular 2 overview in 60 minutes
Loiane Groner
 
PDF
Angular JS2 Training Session #2
Paras Mendiratta
 
PDF
Building an Angular 2 App
Felix Gessert
 
PDF
Everything You Should Know About the New Angular CLI
Amadou Sall
 
angular fundamentals.pdf angular fundamentals.pdf
NuttavutThongjor1
 
angular fundamentals.pdf
NuttavutThongjor1
 
Dumb and smart components + redux (1)
Brecht Billiet
 
Angular meetup 2 2019-08-29
Nitin Bhojwani
 
Advanced angular
Sumit Kumar Rakshit
 
Angular or React
Orkhan Gasimov
 
Angular Interview Question & Answers PDF By ScholarHat
Scholarhat
 
Angular 2 - The Next Framework
Commit University
 
Angular performance slides
David Barreto
 
Angular2 with TypeScript
Rohit Bishnoi
 
MVS: An angular MVC
David Rodenas
 
Angular2 + rxjs
Christoffer Noring
 
Capstone ms2
TanishGupta44
 
"Frameworks in 2015" Андрей Листочкин
Fwdays
 
Angular - Chapter 3 - Components
WebStackAcademy
 
Angular Optimization Web Performance Meetup
David Barreto
 
Angular 2 overview in 60 minutes
Loiane Groner
 
Angular JS2 Training Session #2
Paras Mendiratta
 
Building an Angular 2 App
Felix Gessert
 
Everything You Should Know About the New Angular CLI
Amadou Sall
 
Ad

Recently uploaded (20)

PDF
HiHelloHR – Simplify HR Operations for Modern Workplaces
HiHelloHR
 
PPTX
Foundations of Marketo Engage - Powering Campaigns with Marketo Personalization
bbedford2
 
PDF
MiniTool Partition Wizard 12.8 Crack License Key LATEST
hashhshs786
 
PDF
vMix Pro 28.0.0.42 Download vMix Registration key Bundle
kulindacore
 
PDF
[Solution] Why Choose the VeryPDF DRM Protector Custom-Built Solution for You...
Lingwen1998
 
PDF
Top Agile Project Management Tools for Teams in 2025
Orangescrum
 
PDF
Download Canva Pro 2025 PC Crack Full Latest Version
bashirkhan333g
 
PPTX
Help for Correlations in IBM SPSS Statistics.pptx
Version 1 Analytics
 
PPTX
AEM User Group: India Chapter Kickoff Meeting
jennaf3
 
PPTX
Tally software_Introduction_Presentation
AditiBansal54083
 
PDF
Open Chain Q2 Steering Committee Meeting - 2025-06-25
Shane Coughlan
 
PDF
iTop VPN With Crack Lifetime Activation Key-CODE
utfefguu
 
PDF
Empower Your Tech Vision- Why Businesses Prefer to Hire Remote Developers fro...
logixshapers59
 
PDF
SciPy 2025 - Packaging a Scientific Python Project
Henry Schreiner
 
PPTX
Homogeneity of Variance Test Options IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
PDF
Build It, Buy It, or Already Got It? Make Smarter Martech Decisions
bbedford2
 
PDF
How to Hire AI Developers_ Step-by-Step Guide in 2025.pdf
DianApps Technologies
 
PPTX
Home Care Tools: Benefits, features and more
Third Rock Techkno
 
PPTX
Agentic Automation Journey Session 1/5: Context Grounding and Autopilot for E...
klpathrudu
 
PPTX
Finding Your License Details in IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
HiHelloHR – Simplify HR Operations for Modern Workplaces
HiHelloHR
 
Foundations of Marketo Engage - Powering Campaigns with Marketo Personalization
bbedford2
 
MiniTool Partition Wizard 12.8 Crack License Key LATEST
hashhshs786
 
vMix Pro 28.0.0.42 Download vMix Registration key Bundle
kulindacore
 
[Solution] Why Choose the VeryPDF DRM Protector Custom-Built Solution for You...
Lingwen1998
 
Top Agile Project Management Tools for Teams in 2025
Orangescrum
 
Download Canva Pro 2025 PC Crack Full Latest Version
bashirkhan333g
 
Help for Correlations in IBM SPSS Statistics.pptx
Version 1 Analytics
 
AEM User Group: India Chapter Kickoff Meeting
jennaf3
 
Tally software_Introduction_Presentation
AditiBansal54083
 
Open Chain Q2 Steering Committee Meeting - 2025-06-25
Shane Coughlan
 
iTop VPN With Crack Lifetime Activation Key-CODE
utfefguu
 
Empower Your Tech Vision- Why Businesses Prefer to Hire Remote Developers fro...
logixshapers59
 
SciPy 2025 - Packaging a Scientific Python Project
Henry Schreiner
 
Homogeneity of Variance Test Options IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
Build It, Buy It, or Already Got It? Make Smarter Martech Decisions
bbedford2
 
How to Hire AI Developers_ Step-by-Step Guide in 2025.pdf
DianApps Technologies
 
Home Care Tools: Benefits, features and more
Third Rock Techkno
 
Agentic Automation Journey Session 1/5: Context Grounding and Autopilot for E...
klpathrudu
 
Finding Your License Details in IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
Ad

2 years of angular: lessons learned

  • 4. About Bingel ● Full lesson support ● Instruction, exercises & testing ● Maths & language courses ● Adaptive learning ● Modern & cross-platform
  • 13. How it started… ng new bingel-app$ ng new bingel-student-app
  • 16. Architecture student app tasks module lessons module results module shared module
  • 17. Architecture student app tasks module lessons module results module shared module teacher app
  • 18. Architecture student app tasks module lessons module results module shared module teacher app instruction module analysis module shared module
  • 19. Architecture student app tasks module lessons module results module shared module teacher app "shared code" instruction module analysis module shared module
  • 20. Architecture student app tasks module lessons module results module shared module teacher app ui library interactions library api library common library instruction module analysis module shared module
  • 21. Architecture student app tasks module lessons module results module shared module teacher app @p1/ui @p1/interactions @p1/api @p1/common preview app admin app instruction module analysis module shared module preview module licensing module content graph module @p1/features
  • 22. Architecture student app tasks module lessons module results module shared module teacher app @p1/ui @p1/interactions @p1/api @p1/common preview app admin app instruction module analysis module shared module preview module licensing module content graph module @p1/features single repository (monorepo)
  • 23. How?
  • 24. Nx workspaces ● Builts on top of Angular CLI ● Supports monorepo approach ● Provides "affected builds" option: ○ build only what has been changed ● Better separation of packages
  • 25. Architecture: lessons learned 1. Consider a monorepo ○ simple versioning & release process ○ easier refactoring ○ well supported by Angular CLI + Nx Workspaces 2. Think in packages and their responsibilities
  • 27. Routing [ { path: 'overview', loadChildren: 'overview/overview.module#OverviewModule' }, { path: 'lessons', loadChildren: 'lessons/lessons.module#LessonsModule' }, { path: 'tasks', loadChildren: 'tasks/tasks.module#TasksModule' } ];
  • 28. Routing guards { path: 'tasks', resolve: { blocks: BlocksResolver }, children: [ { path: '', canActivate: [TasksRedirectGuard] }, { path: 'blocks', component: TasksBlocksComponent }, { path: 'blocks/:blockId/weeks/:weekId', component: TasksSelectionComponent } ] }
  • 29. Routing guards { path: 'tasks', resolve: { blocks: BlocksResolver }, children: [ { path: '', canActivate: [TasksRedirectGuard] }, { path: 'blocks', component: TasksBlocksComponent }, { path: 'blocks/:blockId/weeks/:weekId', component: TasksSelectionComponent } ] }
  • 30. Routing in Angular is reactive.
  • 31. Routing in Angular is reactive. But not necessarily.
  • 32. ActivatedRoute class WeekComponent implements OnInit { week: Week; constructor(private route: ActivatedRoute, private weekService: WeekService) {} ngOnInit(): void { this.route.paramMap .pipe( map(params => params.get('weekId')), switchMap(weekId => this.weekService.getWeek(weekId)) ) .subscribe(week => { this.week = week; }); } }
  • 33. ActivatedRoute class WeekComponent implements OnInit { week: Week; constructor(private route: ActivatedRoute, private weekService: WeekService) {} ngOnInit(): void { this.route.paramMap .pipe( map(params => params.get('weekId')), switchMap(weekId => this.weekService.getWeek(weekId)) ) .subscribe(week => { this.week = week; }); } } reactive!
  • 34. ActivatedRoute vs. ActivatedRouteSnapShot class WeekComponent { week: Week; constructor(private route: ActivatedRoute, private weekService: WeekService) {} ngOnInit(): void { this.weekService .getWeek(this.route.snapshot.paramMap.get('weekId')) .subscribe(week => this.week = week); } }
  • 35. ActivatedRoute vs. ActivatedRouteSnapShot class WeekComponent { week: Week; constructor(private route: ActivatedRoute, private weekService: WeekService) {} ngOnInit(): void { this.weekService .getWeek(this.route.snapshot.paramMap.get('weekId')) .subscribe(week => this.week = week); } } not reactive (static)!
  • 36. Routing: lessons learned 1. Make use of lazy-loaded feature modules ○ Breaks down bundles into smaller chunks 2. Make smart use of guards! 3. Get used to the (reactive) API of Angular Router
  • 38. Different types of components Page Components Routed, fetches data, (almost) no presentation logic1 Feature Components Specific presentation logic (bound to domain model)2 UI Components Generic presentation logic (not bound to domain model)3
  • 39. Different types of components TasksModule TaskSelectionComponent /tasks/blocks/week/:id TaskDetailsComponent /tasks/task/:id LessonsModule LessonsComponent /lessons
  • 40. Different types of components TasksModule TaskSelectionComponent /tasks/blocks/week/:id TaskDetailsComponent /tasks/task/:id SidebarModule SidebarComponent AccordionModule CardModule AccordionComponent CardComponent LessonsModule LessonsComponent /lessons
  • 41. Different types of components TasksModule TaskSelectionComponent /tasks/blocks/week/:id TaskDetailsComponent /tasks/task/:id SidebarModule SidebarComponent AccordionModule CardModule AccordionComponent CardComponent TaskPreviewComponent TaskMenuComponent LessonsModule LessonsComponent /lessons
  • 42. Different types of components TasksModule TaskSelectionComponent /tasks/blocks/week/:id TaskDetailsComponent /tasks/task/:id SidebarModule SidebarComponent AccordionModule CardModule AccordionComponent CardComponent TaskPreviewComponent TaskMenuComponent LessonsModule LessonsComponent /lessons
  • 43. Only be generic when needed Page components: Try to be specific. Duplicate page variants, avoid pages that become fuzzy. UI components: Be generic. Don't couple to domain model.
  • 45. Style encapsulation! 😍 Concept from Web Components ● A component has its own "shadow DOM" ● A component can only style its own elements ● Prevents "leaking" styling and unwanted side-effects ● No conventies like BEM, SMACSS, …, needed anymore
  • 46. Example @Component({ selector: 'p1-panel', template: ` <header>...</header> `, styles: [` header { margin-bottom: 1em; } `] }) class PanelComponent {}
  • 47. Don't forget the :host element!
  • 48. Don't forget the :host element! @Component({ selector: 'p1-panel', template: ` <div class="wrapper"> <header>...</header> <footer>...</footer> </div> `, styles: [` .wrapper { display: flex; } `] }) class PanelComponent {}
  • 49. Don't forget the :host element! @Component({ selector: 'p1-panel', template: ` <div class="wrapper"> <header>...</header> <footer>...</footer> </div> `, styles: [` .wrapper { display: flex; } `] }) class PanelComponent {}
  • 50. Don't forget the :host element! @Component({ selector: 'p1-panel', template: ` <header>...</header> <footer>...</footer> `, styles: [` :host { display: flex; } `] }) class PanelComponent {}
  • 51. Don't forget the :host element! @Component({ selector: 'p1-panel', template: ` <header>...</header> <footer>...</footer> `, styles: [` :host { display: flex; } `] }) class PanelComponent {}
  • 53. Robust default styling @Component({ selector: 'p1-panel', template: ` <header>...</header> <footer>...</footer>`, styles: [` :host { /* ... */ } `] }) class PanelComponent {}
  • 54. Robust default styling @Component({ selector: 'p1-some-page', template: ` <p1-panel *ngFor="week of weeks"></p1-panel> `, styles: [` p1-panel { /* ... */ } `] }) class SomePageComponent {} @Component({ selector: 'p1-panel', template: ` <header>...</header> <footer>...</footer>`, styles: [` :host { /* ... */ } `] }) class PanelComponent {}
  • 55. Robust default styling @Component({ selector: 'p1-some-page', template: ` <p1-panel *ngFor="week of weeks"></p1-panel> `, styles: [` p1-panel { /* ... */ } `] }) class SomePageComponent {} @Component({ selector: 'p1-panel', template: ` <header>...</header> <footer>...</footer>`, styles: [` :host { /* ... */ } `] }) class PanelComponent {} More default styling! Less contextual styling!
  • 56. Component styling vs. global styling 🤔 ● Global styling ○ Try to avoid as much as possible! ● Component styling ○ Makes use of style encapsulation.
  • 57. Awesome feature, :host-context() @Component({ selector: 'p1-panel', template: ` <header>...</header> `, styles: [` header { margin-bottom: 1em; } :host-context(p1-sidebar) header { margin-bottom: 0; } `] }) export class PanelComponent {}
  • 58. Styling: lessons learned 1. Prefer component styling over global styling 2. Prevent using "cheats" like ::ng-deep. It's a smell! 💩 3. Don't forget the :host element! 4. Go for robust and flexible default styling 5. Make use of CSS inherit keyword 6. Use EM/REM instead of pixels
  • 61. Unit testing Angular provides the following tools out-of-the-box: ➔ Karma runner with Jasmine as test framework 🤔 ➔ TestBed as test API for Angular Components 🤔
  • 62. TestBed API describe('ButtonComponent', () => { let fixture: ComponentFixture<ButtonComponent>; let instance: ButtonComponent; let debugElement: DebugElement; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ ButtonComponent ] }) .compileComponents(); fixture = TestBed.createComponent(ButtonComponent); instance = fixture.componentInstance; debugElement = fixture.debugElement; })); it('should set the class name according to the [className] input', () => { instance.className = 'danger'; fixture.detectChanges(); const button = debugElement.query(By.css('button')).nativeElement as HTMLButtonElement; expect(button.classList.contains('danger')).toBeTruthy(); expect(button.classList.contains('success')).toBeFalsy(); }); });
  • 63. TestBed API describe('ButtonComponent', () => { let fixture: ComponentFixture<ButtonComponent>; let instance: ButtonComponent; let debugElement: DebugElement; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ ButtonComponent ] }) .compileComponents(); fixture = TestBed.createComponent(ButtonComponent); instance = fixture.componentInstance; debugElement = fixture.debugElement; })); it('should set the class name according to the [className] input', () => { instance.className = 'danger'; fixture.detectChanges(); const button = debugElement.query(By.css('button')).nativeElement as HTMLButtonElement; expect(button.classList.contains('danger')).toBeTruthy(); expect(button.classList.contains('success')).toBeFalsy(); }); }); TL;DR 🤔
  • 64. Hello @netbasal/spectator! 😎 AWESOME library for component testing in Angular ➔ Simple API ➔ Better typings ➔ Custom matchers ➔ Mocking integration ➔ Simple way of querying
  • 65. Spectator API describe('ButtonComponent', () => { const createComponent = createTestComponentFactory(ButtonComponent); let spectator: Spectator<ButtonComponent>; beforeEach(() => { spectator = createComponent(); }); it('should set the class name according to the [className] input', () => { spectator.component.className = 'danger'; spectator.detectChanges(); expect('button').toHaveClass('danger'); expect('button').not.toHaveClass('success'); }); });
  • 67. Go for readable test code! it('should show the pressed key while pointing down', () => { const keys = spectator.queryAll('.key'); const key1 = keys[0]; // q-key const key2 = keys[1]; // w-key spectator.dispatchMouseEvent(key1, 'pointerdown'); expect(key1.classList.contains('key-pressed')).toBe(true); expect(key2.classList.contains('key-pressed')).toBe(false); expect(spectator.component.activeKey).toBe('q'); spectator.dispatchMouseEvent(key1, 'pointerup'); expect(key1.classList.contains('key-pressed')).toBe(false); expect(key2.classList.contains('key-pressed')).toBe(false); expect(spectator.component.activeKey).toBeFalsy(); spectator.dispatchMouseEvent(key2, 'pointerdown'); expect(key1.classList.contains('key-pressed')).toBe(false); expect(key2.classList.contains('key-pressed')).toBe(true); expect(spectator.component.activeKey).toBe('w'); });
  • 68. Go for readable test code! it('should show the pressed key while pointing down', () => { const keys = spectator.queryAll('.key'); const key1 = keys[0]; // q-key const key2 = keys[1]; // w-key spectator.dispatchMouseEvent(key1, 'pointerdown'); expect(key1.classList.contains('key-pressed')).toBe(true); expect(key2.classList.contains('key-pressed')).toBe(false); expect(spectator.component.activeKey).toBe('q'); spectator.dispatchMouseEvent(key1, 'pointerup'); expect(key1.classList.contains('key-pressed')).toBe(false); expect(key2.classList.contains('key-pressed')).toBe(false); expect(spectator.component.activeKey).toBeFalsy(); spectator.dispatchMouseEvent(key2, 'pointerdown'); expect(key1.classList.contains('key-pressed')).toBe(false); expect(key2.classList.contains('key-pressed')).toBe(true); expect(spectator.component.activeKey).toBe('w'); });
  • 69. Go for readable test code! it('should show the pressed key while pointing down', () => { const keys = spectator.queryAll('.key'); const keyQ = keys[0]; const keyW = keys[1]; spectator.dispatchMouseEvent(keyQ, 'pointerdown'); expect(keyQ.classList.contains('key-pressed')).toBe(true); expect(keyW.classList.contains('key-pressed')).toBe(false); expect(spectator.component.activeKey).toBe('q'); spectator.dispatchMouseEvent(keyQ, 'pointerup'); expect(keyQ.classList.contains('key-pressed')).toBe(false); expect(keyW.classList.contains('key-pressed')).toBe(false); expect(spectator.component.activeKey).toBeFalsy(); spectator.dispatchMouseEvent(keyW, 'pointerdown'); expect(keyQ.classList.contains('key-pressed')).toBe(false); expect(keyW.classList.contains('key-pressed')).toBe(true); expect(spectator.component.activeKey).toBe('w'); });
  • 70. Go for readable test code! it('should show the pressed key while pointing down', () => { const keys = spectator.queryAll('.key'); const keyQ = keys[0]; const keyW = keys[1]; spectator.dispatchMouseEvent(keyQ, 'pointerdown'); expect(keyQ.classList.contains('key-pressed')).toBe(true); expect(keyW.classList.contains('key-pressed')).toBe(false); expect(spectator.component.activeKey).toBe('q'); spectator.dispatchMouseEvent(keyQ, 'pointerup'); expect(keyQ.classList.contains('key-pressed')).toBe(false); expect(keyW.classList.contains('key-pressed')).toBe(false); expect(spectator.component.activeKey).toBeFalsy(); spectator.dispatchMouseEvent(keyW, 'pointerdown'); expect(keyQ.classList.contains('key-pressed')).toBe(false); expect(keyW.classList.contains('key-pressed')).toBe(true); expect(spectator.component.activeKey).toBe('w'); });
  • 71. Go for readable test code! it('should show the pressed key while pointing down', () => { const [keyQ, keyW] = spectator.queryAll('.key'); spectator.dispatchMouseEvent(keyQ, 'pointerdown'); expect(keyQ.classList.contains('key-pressed')).toBe(true); expect(keyW.classList.contains('key-pressed')).toBe(false); expect(spectator.component.activeKey).toBe('q'); spectator.dispatchMouseEvent(keyQ, 'pointerup'); expect(keyQ.classList.contains('key-pressed')).toBe(false); expect(keyW.classList.contains('key-pressed')).toBe(false); expect(spectator.component.activeKey).toBeFalsy(); spectator.dispatchMouseEvent(keyW, 'pointerdown'); expect(keyQ.classList.contains('key-pressed')).toBe(false); expect(keyW.classList.contains('key-pressed')).toBe(true); expect(spectator.component.activeKey).toBe('w'); });
  • 72. Go for readable test code! it('should show the pressed key while pointing down', () => { const [keyQ, keyW] = spectator.queryAll('.key'); spectator.dispatchMouseEvent(keyQ, 'pointerdown'); expect(keyQ.classList.contains('key-pressed')).toBe(true); expect(keyW.classList.contains('key-pressed')).toBe(false); expect(spectator.component.activeKey).toBe('q'); spectator.dispatchMouseEvent(keyQ, 'pointerup'); expect(keyQ.classList.contains('key-pressed')).toBe(false); expect(keyW.classList.contains('key-pressed')).toBe(false); expect(spectator.component.activeKey).toBeFalsy(); spectator.dispatchMouseEvent(keyW, 'pointerdown'); expect(keyQ.classList.contains('key-pressed')).toBe(false); expect(keyW.classList.contains('key-pressed')).toBe(true); expect(spectator.component.activeKey).toBe('w'); });
  • 73. Go for readable test code! const pointerDown = element => spectator.dispatchMouseEvent(element, 'pointerdown'); const pointerUp = element => spectator.dispatchMouseEvent(element, 'pointerdown'); it('should show the pressed key while pointing down', () => { const [keyQ, keyW] = spectator.queryAll('.key'); pointerDown(keyQ); expect(keyQ.classList.contains('key-pressed')).toBe(true); expect(keyW.classList.contains('key-pressed')).toBe(false); expect(spectator.component.activeKey).toBe('q'); pointerUp(keyQ); expect(keyQ.classList.contains('key-pressed')).toBe(false); expect(keyW.classList.contains('key-pressed')).toBe(false); expect(spectator.component.activeKey).toBeFalsy(); pointerDown(keyW); expect(keyQ.classList.contains('key-pressed')).toBe(false); expect(keyW.classList.contains('key-pressed')).toBe(true); expect(spectator.component.activeKey).toBe('w'); });
  • 74. Go for readable test code! const pointerDown = element => spectator.dispatchMouseEvent(element, 'pointerdown'); const pointerUp = element => spectator.dispatchMouseEvent(element, 'pointerdown'); it('should show the pressed key while pointing down', () => { const [keyQ, keyW] = spectator.queryAll('.key'); pointerDown(keyQ); expect(keyQ.classList.contains('key-pressed')).toBe(true); expect(keyW.classList.contains('key-pressed')).toBe(false); expect(spectator.component.activeKey).toBe('q'); pointerUp(keyQ); expect(keyQ.classList.contains('key-pressed')).toBe(false); expect(keyW.classList.contains('key-pressed')).toBe(false); expect(spectator.component.activeKey).toBeFalsy(); pointerDown(keyW); expect(keyQ.classList.contains('key-pressed')).toBe(false); expect(keyW.classList.contains('key-pressed')).toBe(true); expect(spectator.component.activeKey).toBe('w'); });
  • 75. Go for readable test code! const pointerDown = element => spectator.dispatchMouseEvent(element, 'pointerdown'); const pointerUp = element => spectator.dispatchMouseEvent(element, 'pointerdown'); it('should show the pressed key while pointing down', () => { const [keyQ, keyW] = spectator.queryAll('.key'); pointerDown(keyQ); expect(keyQ).toHaveClass('key-pressed'); expect(keyW).not.toHaveClass('key-pressed'); expect(spectator.component.activeKey).toBe('q'); pointerUp(keyQ); expect(keyQ).not.toHaveClass('key-pressed'); expect(keyW).not.toHaveClass('key-pressed'); expect(spectator.component.activeKey).toBeFalsy(); pointerDown(keyW); expect(keyQ).not.toHaveClass('key-pressed'); expect(keyW).toHaveClass('key-pressed'); expect(spectator.component.activeKey).toBe('w'); });
  • 76. Class testing or component testing? ➔ Discuss it with your team ➔ What means the "unit" in "unit testing"? ➔ What does a class test prove about the quality of a component? ➔ Technical tests vs. functional tests
  • 77. To mock... or not to mock? Pure unit tests are run in isolation. ➔ Want to mock components/directives/pipes? Hello ng-mocks!
  • 78. To mock... or not to mock? ➔ Pure unit tests are run in isolation. ➔ Want to mock components/directives/pipes? Hello ng-mocks! describe('AudioPlayerComponent', () => { const createHost = createHostComponentFactory({ component: AudioPlayerComponent, declarations: [ MockComponent(EbMediaControlsComponent) ], providers: [ { provide: AUTOPLAY_DELAY, useValue: 1000 } ] });
  • 79. To mock... or not to mock? ➔ Pure unit tests are run in isolation. ➔ Want to mock components/directives/pipes? Hello ng-mocks! But: nothing wrong with a bit of integration testing! ➔ Discuss with your team how you implement a test pyramid
  • 80. Organize your testdata ● Consider complete and type-safe testdata ● Organize fixture data in one place ● Useful when using large & complex domain models ● Don't repeat yourself
  • 81. Hello Jest! ● Fast, robust and powerful test framework ● Replaces Karma/Jasmine ● Simple migration path: compatible with Jasmine syntax (describe/it) ● Integrates very well with Angular (thymikee/jest-preset-angular) ● Integrates very well with Angular CLI (@angular-builders/jest)
  • 82. Unit testing: lessons learned! 1. Consider Spectator instead of Angular TestBed 2. Consider Jest instead of Karma/Jasmine 3. Make unit testing FUN! 4. Go for functional component testing - Don't think in methods, but in user events. - Don't test from component class, but from DOM
  • 84. Code quality - Make your TypeScript compiler as strict as possible - Make your TypeScript linting as strict as possible - Take a look at Prettier - Use modern JavaScript APIs and standards - Don't fear old browsers! - Embrace polyfills!
  • 85. Complex stuff - Take a look at @angular/cdk! - Look at @angular/material for inspiration, to learn 'the Angular way' - Use the power of MutationObserver, ResizeObserver, IntersectionObserver - Don't be afraid to refactor!
  • 86. Spend time on open-source development We use open-source stuff every day. We should care about it. 1. Report your issue, and take time to investigate! ○ If you try hard, others will as well! 2. Try to fix it yourself, be a contributor! ○ Open-source projects are for everyone! 3. Improve yourself, read blogs. Know what's going on.
  • 87. Reduce technical debt 1. Don't go for the best solution the first time! ○ Keep it simple so that you can obtain insights. ○ Prevent "over-engineering". 2. … but always improve things the second time! ○ Don't create refactor user stories, but take your refactor time for every user story! ○ This prevents postponing things and makes it more fun. 3. Too much technical debt? Bring it on the agenda for the upcoming sprint.