This is a study note of Ionic framework based on Elite Ionice Course that covers testing, performance, and user experience.
1 Performance
There are a few steps for a browser to render a frame of an application: processing JavaScript code that changes a page, applying styles, calculating layout, paiting a frame, compositing layers of painting (such as opacity change). To improve the app performance, you should avoid as many of the steps as possbile and make the necessary steps as efficient as possible. For example, adding contain: layout;
to a modal, the layout calculating is improved significantly.
Using ‘–prod’ option to build an app to enable Ahead-of-Time compilation and optimization such as tree shaking and minimization. You should debug a production build. To see a production build in browser, use npm run ionic:build --prod
.
Reduce resource size is important. TinyPng optimizes images. Add 3rd party packages using npm
.
It is preferred to manage all CSS files in build process. Copy node_module/@ionic/app-scripts/config/sass.config.js
to your config
folder. Edit the file to include the existing CSS files and new paths. Then edit package.json
file to include "config": {"ionic_sass": "./config/sass.config.js"}
. Then add your css file to src/theme/variables.scss
as @import "your-css-file";
.
2 Components and Directives
2.1 Basic Concepts
A component can have multiple selectors separated by a comma. A selector can be selected by an element name or an atrribute. The @Component
decorator tags a class as an Angular component that has change detection and propagation.
A component receives data via input binding @Input() someInput: someType
. A component can trigger event via @output() someEvent = new EventEmitter()
. A component can recieve data or send out data via service injection.
The primary role of a directive is to attach behaviour to the DOM elements. A directive can be added as an attribute to an elment or can exist on its own. For example, ion-list
is a directive but not a component.
2.2 Component Reference and Projected Content
You can grab a reference to the host element of a component or a directive by injecting ElementRef
in its constructor. Though you can access the underlying natvie element via nativeElement
property, you shouldn’t use it directly for portability. InjectRenderer
and use its methods are the recommended way to manipulate host element.
The content supplied between the component tags will be projected into the the ng-content
tag. Angular supports multiple content projections. The ng-content
can have a select
attribute that matches the projected content.
Use @ViewChild(selector) ref: RefType
to get a reference to a component. To get its native element, use @ViewChild(selector, {read: ElementRef}) ref: ElementRef
. The selector can be a class name of the component or an element reference defined using #myRef
. Depending on the attached element of the element reference, the return type could be an element type or ElementRef
if it is a normal HTML element. Use @ViewChild
or @ViewChildren
to grab elements added to the component by yourself. Use @ContentChild
or @ContentChildren
to grap elements projected by ng-content
.
2.3 Listen for Events and Bind to Properties
Use @HostListener('eventName', ['eventArg',...] handler(arg) {...}
to listen for host component events and pass event parameters.
Use HostBinding
to add/remove classes or attributes to/from the host component. @HostBinding('class.my-class') applyTheClass = true;
add the my-class
to host component when applyTheClass
is true. @HostBinding('attr.someAttribute') attributeToApply = 'whatever';
add someAttribute=whatever
to the host component. @HostBinding('id') idToApply = 'whatever';
set host component id to whatever
.
An alternative method is to use host
metadata to define event listener and property binders.
3 Test
3.1 TDD
TDD is about using automated tests to drive the development. Following the TDD philosophy, the recommended development process is:
- write an e2e test for a specific requirement.
- the e2e test fails
- decide what functionality needs pass the e2e test.
- write a unit test for the functionality.
- the unit test fails.
- implement the code to satisfy the unit test.
- pass the unit test, revise the code if it fails.
- pass the e2e test, add/revise functionality if it fails.
The failure steps of both e2e and unit test are necessary to make sure two things: 1) the test is writting correctly, and 2) force to work within the parameters of the test.
To start a project, first come with a list of requirements and corresponding e2e tests. Then prioritize the requirements and e2e tests to make a minimum viable product (MVP) ASAP.
The key difference between a unit test and an E2E test is that a unit test will test code, whereas an E2E will test behaviour. An e2e test tests the page contents that include the current page and the navigated pages. A unit test only checks the logic of the current page. Therefore starting from the homepage, every e2e test tests the current page content and navigation to child pages. Each page’s unit test tests the component logic such as componentent creation and setting member variables from the navigator’s parameters.
3.2 Testing Utilities
3.2.1 Setup e2e Test
Use a help page object to setup navigation and retrieve content.
|
|
3.2.2 Setup Unit Test
Use TestBed
to compile and create a component used in a test. If a component uses an external template, you need to compile it asynchronously. The sample code is as the following:
|
|
In the above code, the DeepLinker
is required for the component to compile properly when using lazy loding (To Be Confirmed).
3.2.3 Test Utitlities
When Ionic navigates from one page to another page, it will activate an overlay that prevents clicks for a short duration. Where there are multiple click events, it could be an issue. Use the following code to wait till it is ready:
|
|
Inside a test case, use const navCtrl = fixture.debugElement.injector.get(NavController)
to get injected objects. If a fixture is not avaialbe (not testing a component), use inject
in a test case to explicitly inject service providers.
Use navParams.get = jasmine.createSpy('get').and.returnValue(...)
to add testing data to navParams
.
3.2.4 Async Test
Run async test cases in fakeAsync()
that catches and controls all asynchrounous operations. Call flashMicrotasks()
, tick()
or tick(timeout)
to clear microtasks (Promise
and Observable
). Call tick(timeout)
to advance timer for a macrotask (setTimeout()
).