Angular PWA

Angular Progressive Web Applicatoin (PWA)

This article documents the steps to add PWA support to an existing Angular 5.1.1 project generated from Angular cli. This Angular Service Worker blog gives a good introduction of Angular PWA.

Use Google lighthouse tool to assess the quality of an application. Use the command npm i -g lighthouse to install it and run lighthouse https://yourwebsite.url --view to check and view the result.

PWA Requirements

The above blog summarizes the PWA requirements:

  • An application shell: the app can start both online and offline.
  • External files: fonts or icons used by application shell but not included in dist folder.
  • API runtime caching: cahed data for network calls.
  • Push notification: subscription and displaying the sever notification.
  • Auto update of applicaton shell: updating to the latest version.

1 The Angular CLI Support

To see what’s added by enabling PWA in Angular CLI, run the following two commands to generagte two projects and check the differences:

ng new web-no-pwa
ng new web-with-pwa --service-work

By copying the web-with-pwa files into the web-no-pwa folder, the git reports the following differences:

  • /.angular-cli.json: the "serviceWorker": true is added to the first item in apps: [].
  • /package.json: a new dependency "@angular/service-worker".
  • /src/ngsw-config.json: this is a new file only in pwa.
  • /src/app/app.module.ts: three new lines to import and use service work module. import { ServiceWorkerModule } from '@angular/service-worker' and import { environment } from '../environments/environment' at the top as well as ServiceWorkerModule.register('/ngsw-worker.js', { enabled: environment.production }) in the imports: [] section.

Angular only generates the required service worker file in production mode. ng build --prod generates /dist/ngsw.json file from src/ngsw-config.json file and copies the generic service worker from service worker nmp module to /dist/ngsw-worker.js. Because by default ng serve is not configured to serve this file, you need a HTTP server to serve files from the /dist folder. You can use the nmp http-server package to test it.

2 Service Worker Configuration

2.1 Top Level Config Interface

The config interface is examplianed in a service worker theory blog. The top level config interface is as the following:

export interface Config {
    appData?: {};
    index: string;
    assetGroups?: AssetGroup[];
    dataGroups?: DataGroup[];
}

The appData contains application metadata that is not used by the service worker but is delivered as part of update notifications. The index specifies the path to the index.html file.

2.2 assetGroups

The assetGroups contains named groups of resources explicitly known at build time. Applicatoin shell resources should be declared here to be cached. All static assets are delcard here too. It has the following interface:

export interface AssetGroup {
    name: string;
    installMode?: 'prefetch' | 'lazy';
    updateMode?: 'prefetch' | 'lazy';
    resources: {
        files?: Glob[];
        versionedFiles?: Glob[];
        urls?: Glob[];
    };
}

The installMode means the first installation while the updateMode means a newer version is available for update. The prefetch value specifies immediate installation/cache while the layz value means on-demand installation/cache.

There are three types of resources:

  • files: a list of files whose contents will be hashed and stored in ngsw.json file.
  • versionedFiles: a list of files whose names include content hash.
  • urls: A list of external URLs (usally CDN resources) that should be cached. These resources are only updated whenever the configuration file changes.

Following is an exmaple:

{
  "index": "/index.html",
  "assetGroups": [{
    "name": "app",
    "installMode": "prefetch",
    "resources": {
      "files": [
        "/favicon.ico",
        "/index.html"
      ],
      "versionedFiles": [
        "/*.bundle.css",
        "/*.bundle.js",
        "/*.chunk.js"
      ]
    }
  }, {
    "name": "assets",
    "installMode": "lazy",
    "updateMode": "prefetch",
    "resources": {
      "files": [
        "/assets/**"
      ],
      "urls": [
        "https://fonts.googleapis.com/**"
      ]
    }
  }]
}

2.3 DataGroups

The dataGroup contains named groups of resources to be cached during runtime, usually these are on demand data requrested from the network. The following is its interface:

export interface DataGroup {
    name: string;
    urls: Glob[];
    version?: number;
    cacheConfig: {
        maxSize: number;
        maxAge: Duration;
        timeout?: Duration;
        strategy?: 'freshness' | 'performance';
    };
}

You specify a list of urls that to be cached. The maxSize is the maximum number of responses cached per group. maxAge is how long the cached response is valid. It could be seconds, minutes, hours or days. timeout is used by freshness strategy. strategy has two options: freshness for network-first and performance for cache-rist. The following is an example:

"dataGroups": [
  {
    "name": "tasks-users-api",
    "urls": ["/tasks", "/users"],
    "cacheConfig": {
      "strategy": "freshness",
      "maxSize": 20,
      "maxAge": "1h",
      "timeout": "5s"
    }
  }
]

3 Push Notification and Update Flow

The push notification works without any additional setup. You can use JavaScript’s native method or the SwPush class provided by the ServiceWorkerModule.

You can customize the update flow using the SwUpdate class provided by the ServiceWorkModule.

This github repository has sample code for the above concepts.

Read More

Angular Router

Angular Router Notes

This notes is based on the Angular router book.

1 Router Concepts

A router defines the screen layout of application components using routes and outlets. A router uses a tree structure to config, manage, and navigate routes. Each tree node represents a route. An outlet is defined in a component to specify a screen place where a child component is located. A component may define multiple outlets using different outlet names.

A nodes may assciate with one components. Mutiple nodes can assciaote with the same component.

A route state is a subtree that can be serealized/represented as a URL path. Different nodes in the path may assciate to different components thus togerth they form the component tree in the screen. The current screen represent the activated route state where all associated components are activated. One outlet can only have one activated component. The root component is always asscioated with an activated route state.

The router’s primary job is to manage navigation between all potention states, which include activing or deactivating components involved in the state transition. Because a URL is a serialzied router state, the Angular router takes care of managing the URL to make sure that it’s in-sync with the route state.

2 The Routing Process

The Angular router takes a URL, then:

  1. Applies redirects
  2. Recognizes router states
  3. Runs guards and resolves data
  4. Activates all the needed components
  5. Manages navigation

A router uses parentheses to serialize secondary segments. For example, /inbox/33(popup:compose). The colon syntax specifies the outlet. A router us param=value shyntax to specify route-specific parameters. For example, /inbox/33;open=true/messages/44.

A router state consists of activated routes. An activated route can be associated with a component. When a route state is recognized, we have a future router state. The router will check that transitioning to the future state is permitted by running guards.

When configure routers, an option is to define a resolve property that is called to retrieve data for that router. It is called after checking route guards. You can acces the resolved data by injecting the activated route object into a component. Following is an example:

class MyCmp {
    dataArray: Observable<data[]>
    id: Observable<string>
    constructor(route: ActivatedRoute) {
        this.dataArray = route.data.pluck('myDataPropertyName')
        this.id = route.paramMap.map( p => p.get('id'))
    }
}

The current route state can be injected to a component using an ActivatedRoute class. The url, data, params, queryParams, fragment, and paramMap properties of ActivatedRoute are observables. To access those data immediately, use snapshot property to access the instant properties.

3 Matching URLs

In Angular, a URL is just a serialized router state. Navaigation that changes a state results in a URL change.

3.1 URL Structure

A URL has a type of Observable<UrlSegment[]>. UrlSegment interface contains a path and the matrix parameters assoicated with the segment.

interface UrlSegment {
    path: string,
    parameters: {[name: string]: string}
}

The path /inbox;a=v1/33;b1=v1;b2=v2 has two segments as shown below:

[
    {path: 'inbox', parameters: {a: 'v1'}},
    {path: '33`, parmaters: {b1: 'v1', b2: 'v2'}}
]

The parameters separated by ; are called matrix or route-specific parmaters. Matrix parameters are scoped.

Query parameters (with a leading question mark ?) and fragment parameters (with a leading sharp #) are not scoped and are shared across many activated routes.

Angular allows secondary children that is defined in /path() for root children or /path/() for secondary children in a child path. Multiple secondary children are separated by //. For example, /inbox/33/(mssages/44//side:help) defines two children for a path of /inbox/33.

3.2 Matching URLs

A router use two parts to define a route:

  • How it matches the URL segment
  • Take what action once the URL is matched

The second action part doesn’t affect the first matching part.

Angular routers uses depth-first strategy to matche router configuration one by one until it find a match that consume all parts of a URL. During the process, it may backtrack and will use the first one that matches the whole URL.

There are three types of segments:

  • constant segment: for example, path: 'message'.
  • variable segment: use semicolon to define a variable. For example path: ':folder'.
  • wildcard segment: the path: '**' matches every thing.

A special case of constant segment is the empty string path defined as path: ''. It matches any string because it interpret every string to begin with the empty string. Empty path route can have children and they inherite matrix paramters of their parents.

The router has a matching strategy. By default, the pathMatch value is prefix that matches the path prefix. For redirectTo route, you should define pathMatch: 'full' for empty path.

A path that dosen’t have redirectTo or component property is a a componentless route that consume a URL segment. The parameters captured by a componentless route will be merge with its children. When two or more siblings share some data, use a componentless route to share data.

3.3 Redirect

Redirects can be local and absolute (the redirectTo value starts with a /). Local redirects replace a single URL segment with a different one. Absolute redirects replace the whole URL.

The router applies one redirect for one level. Mutiple redirects must be in different levels.

4 Router State

4.1 Route Data Structure

As mentioned before, a route state is organized as a tree where each node is a route. Both the state and route have an observable interface and a normal interface.

The router uese an routeState: RouteState property to represent the route state. The RouteState is a tree of activated routes that have a type of ActivatedRoute.

The RouteState also has a snapshot: RouteStateSnapShot property. During a navigation, after redirects have been applied, the router creates the RouterStateSnapshot object that is a tree of ActivatedRouteSnapshot.

The ActivatedRoute interface has the following members:

  • url: Observable<UrlSegment[]>: the URL segment matched by a route.
  • params: Observable<Params>: the positional parameter and matrix parameters scoped to this route.
  • queryParams: Observable<Params>: the query parameters shared by all the routes.
  • fragment: Observable<string>: the URL fragment shared by all the routes.
  • data: Observable<Data>: the static and resolved data of this route.
  • outlet: string: the outlet name of the route.
  • component: Type<any>|string|null: the component of this route.
  • snapshot: ActivatedRouteSnapshot: the current snapshot of this route.
  • root: ActivatedRoute: the root of the route state.
  • parent: ActivatedRoute: the parent of this route.
  • firstchild: ActivatedRoute: the first child of this route.
  • children: ActivatedRoute[]: the children of this route.

The ActivatedRouteSnapshot interface has the similar properties as the ActivatedRoute excpet that its properties are immediate values that are not obverable.

4.2 Access Route State

There are two methods to access the route state from a component. Both use injected objects.

First, each component can be inject with an Router object that has a routeState: RouteState property. The root property is the root ActivatedRoute object that allows you to access the whole tree.

Second, each component can be injected with an ActivatedRoute object that is a route tree node associated with the component.

For both cases, use the snapshot property to access the corresponding snapshot version object.

In data resolve method, you have access to both ActivatedRouteSnapshot and RouteStateSnapshot.

To navigate imperatively, use Router.navigate() or Router.navigatebyUrl(). Using router.navigateByUrl is the same as changing the location in address bar. It will create a whole new route state with the new URL. Router.navigate() apply passed-in commands, a path to the current URL.

For declarative, use <a [RouterLink]=...>.

To navigate to /inbox/33:details=true/messages/44;mode=preview, use router.navigate['/inbox', 33, {details: true}, 'message', 44, {mode: 'preview}]). The string URL is a syntactic sugar for the array URL.

Use router.navigate([{outlets: {popup: 'message/22'}}]) to update secondary segment. Navigation can be relative. You can preserve query parameter and fragment in navigation.

Behind the scene, RouterLink just calls router.navigate with the provided commands. Use routerLinkActive to add CSS classes. Use routerLinkActivateOptions to set exact matching.

The router’s navigation is URL-based and its parameters are just an array of URL segments like ['/contact', id, 'detial', {full: true}]. Therefore it’s able to link into lazily-loaded modules and generate synchronous link. The router doesn’t have the notion of route names and doesn’t have to use any configuration to generate links.

6 Guards and Events

The router uses guards to make sure that navigation is permitted, which can be useful for security, confirmation, monitoring purpose. There are four properties used in route defintion: canLoad, canActivate, canActivateChild, and canDeactivate. The paramter is an array of classes or functions that injected by Augular DI.

The router emits the following events:

  • RouteConfigLoadStart: when the router starts loading a lazy-loaded configuration.
  • RouteConfigLoadEnd: when the router is done loading a lazy-loaded configuration.
  • NavigationStart: when the router start navigating.
  • RoutesRecognized: when the router parses the URL into a RouterStateSnapshot.
  • GuardsCheckStart: when the router starts running the gurads.
  • GuardsCheckEnd: when the router is done running the guards.
  • ResolveStart: when the router starts running the resolvers.
  • ResolveEnd: when the router is done navigating.
  • NavigationEnd: when the router is done navigating.
  • NavigationCancel: when the router cancels navigation.
  • NavigationError: when the router cancels navigation with an error.

All event belong to a navigation have the same event.id.

7 Configuration

The RouterModule.forRoot() creates a module that contains all the router directives, the given routes, and the router service itself. The RouterModule.forChild() create a module that contains all the directives and the given routes, but doesn’t not include the service. The reason is that a routr service manages a shared mutable location resource and only one service is allowed.

You can eanble tracing by import [RouterModule.forRoot(routes, {enableTracing: true})]. To listen to events, subscribe to the events observable : constructor(r: Router) { r.events.subscribe(e=>...)}.

The router can use hash, history or custom location strategy.

By default, RouterModule.forRoot will trigger the initial navigation. It can be disabled by {initialNavigation: false}.

Use {errorHandler: customHandler} to customize naviation error event.

Read More

Angular Style

To style an Angluar application, we need two types of tools: a layout tool and component style tool. The Angular team builds both for developers: Angular Flex Layout and Angular Material design.

1. Flexbox Introduction

The following notes are based on the following sites:

  • https://medium.freecodecamp.org/an-animated-guide-to-flexbox-d280cf6afc35
  • https://medium.freecodecamp.org/even-more-about-how-flexbox-works-explained-in-big-colorful-animated-gifs-a5a74812b053
  • https://css-tricks.com/snippets/css/a-guide-to-flexbox/

1.1 Container and Flow Direction

Angular Flex layout is based on flexbox and is independent of Angular material. Flexbox is one-dimension layout. All items should be put into a container that has a CSS style of display: flex. Items are arranged in one direction defined in flex-direction property. By default, it is flex-direction: row that arranges items from left to right. The direction defined by flex-direction is called main direction or main-axis and the other direction is called cross direction or cross-axis.

By default, items are laid out in the source order. The direction flow can be reversed by using row-reverse or column-reverse values. For individual item, use order: <integer>; /* default is 0 */ to change its order.

1.2 Item Alignment

Use justify-content to control main-axis alignment and align-items to control cross-axis alignment.

There are five values for justify-content: flex-start, flex-end, center, space-between and space-around. In space-between mode, there is equal space between two items, but not between an item and its container. In space-around, there is equal space between two items and between an item and its container.

The align-items also has five values: flex-start, flex-end, center, stretch, and baseline. For stretch mode, items take up the entirety of the cross-axis if their height properties are auto.

Use align-self to overide align-items for an individual item.

1.3 Item Size

The width and height control item size regardless of the flex direction. However, flex-basis is axis-aware and site the size for the corresponding main axis: width for row and height for colun.

By default, an item has a flex-grow: 0 property that means an item styas with its size property and doesn’t grow to take up the space in the container. The flex-grow value is a relative value that is proportional to the others. The value is about change rate. A value of 3 has three times as much as grow rate of a value of 1.

Similar to flex-grow, the flex-shrink property is a relative value that shrinks an item when the container shrinks. To disable shrinking, use flex-shrink: 0.

The flex property is a shortcut for flex-grow, flex-shrink and flex-basis. The default value is flex: 0 1 auto.

1.4 Wrap

The flex-wrap controls the wraping behavior of items. The default is nowrap. Use wrap and wrap-reverse to enable multiple lines.

When there are multiple lines (some items are wrapped), the align-content controls the alignment when there is extra space in the cross-axis. It has 6 values: flex-start, flex-end, center, space-between, space-around, and stretch (the default).

2. Angular Flex Layout Directives

There is a live dome hosted in https://tburleson-layouts-demos.firebaseapp.com/#/docs.

2.1 Use Angular Flex Layout

You need to install angular/flex-layout npm package and import FlexLayoutModule in main module file.

For stable version, use npm install @angular/flex-layout --save. For the latest build, use npm install https://github.com/angular/flex-layout-builds --save.

Use it in the main module:

import { FlexLayoutModule } from '@angular/flex-layout';
...
@NgModule({
  imports: [FlexLayoutModule],
  ...
})

2.2 The API

2.2.1 Container Styles

To control dirction, use fxLayout that has values of row (the default), column, row-reverse, and column-reverse. An optional wrap value can be combined with the direction value to control flow behavior.

The fxLayoutAlign controls the item sizes of both the main-axis and the cross-axis using a pattern <main-axis> <corss-axis>. The corss-axis doesn’t support the baseline value.

Use fxLayoutGap to set gap between items. The value could be %, px, vw and vh.

Use fxLayoutWrap to set the wrap behavior of children.

2.2.2 Element Styles Within Containers

Use fxFlex to control the <grow> <shrink> <basis> of an item.

Use fxFlexOrder to control order.

Use fxFlexOffset to control offset.

Use fxFlexAlign to control alignment of this single child.

The fxFlexFill maximizes width and height of elemtn in a container.

2.2.3 Special Reponsive Features

There are help directives include fxHide, fxShow, ngClass and ngStyle to add styles. Theses directives can be combined with breakpoints. The breakpoints alias are: xs, sm, md, lg, xl, lt-sm, lt-md, lt-lg, lt-xl, gt-xs, gt-sm, gt-md, and gt-lg.

Two examples, <div fxShow fxHide.xs="false" fxHide.lg="true"> ... </div> and <div fxFlex="50%" fxFlex.gt-sm="100%"> ... </div>.

3. Material Design

Using Angular material design is straightforward. First, follow the getting started guide in https://material.angular.io/guide/getting-started to setup Angular to use a specific theme.

Then, check the documents in https://material.angular.io/components/categories to learn how to use each component.

You can customize a theme as described in https://material.angular.io/guide/theming.

If your app’s content is not placed inside of a mat-sidenav-container element, you need to add the mat-app-background class to your wrapper element (for example the body). This ensures that the proper theme background is applied to your page.

Use Google materical icons and fonts by including <link href="https://fonts.googleapis.com/icon?family=Material+Icons|Roboto:300,400,500" rel="stylesheet"> in index.html. Then add the body style as body { font-family: 'Roboto', sans-serif; } in style.css file.

Read More

Angular i18n

A note based on serveral documents from the following sites:

  • https://angular.io/guide/i18n
  • http://www.ngx-translate.com/
  • https://github.com/ngx-translate/core
  • https://medium.com/letsboot/translate-angular-4-apps-with-ngx-translate-83302fb6c10d
  • https://medium.com/@TuiZ/how-to-split-your-i18n-file-per-lazy-loaded-module-with-ngx-translate-3caef57a738f
Read More

Angular NgModule and DI Note

1. Introduction

NgModules consolidate components, directives, and pipes into cohesive blocks of functionality such as a feature area, a business domain, a workflow or a collection of related utilities.

An NgModule is a class decorated with @NgModule metadata that has the following data:

  • Declare which components, directives, and pipes belong to the module.
  • Make some those classes public so that other component templates can use them.
  • Import other modules used by this module.
  • Provide services that any module can use.

This Angular Modules Blog provides an in-depth explanation of how modules work.

2. Root Module and Bootstrap

Every Angular app has at least a root module that bootstraps the application. By convention, the root module is called AppModule defined in app.module.ts. Every browser app needs the BrowserModule that registers service providers and directives such as NgIf and NgFor.

The root module’s bootstrap defines the bootstrap component that will placed inside index.html.

Angular offers a variety of bootstraping options targeting multiple platforms. There are two options targeting the browser. The first is the dynamice option that compiles just-in-time (JIT): platformBrowserDynamic().bootstrapModule(AppModule). The static option compiles ahead-of-time (AOT) that produces a collection of class factories. One is the AppModuleNgFactory that is generated from AppModule and is used to bootstrap the app: platformBrowser().bootstrapModuleFactory(AppModuleNgFactory). In dynamic option, the AppModuleNgFactory is created on the fly in the memory of browser. The above boostrap code in main.ts doesn’t change for both the dynamic and static options.

2.1. Entry Components

Entry components are not loaded declaratively via its selector. These components include the dynamically created components or those used in material design’s MatDialog. The root component and components in route defintions are entry components. Angular adds components in NgModule.bootstrap list and route definitions to the entryComponent list.

3. Feature Modules

A feature module is imported by other module to provide exported components, directives or pipes . The basic directives such as NgIf and NgFor are defined in CommonModule that should be imported into a module.

A feature module can only use components and modules (including router modules) declared or imported in its module file. Services registered in the root injector can be used by any module that uses the root injector.

Use forRoot() as the following to define routes in root module.

@NgModule({
  imports: [RouterModule.forRoot(appRoutes)],
  exports: [RouterModule],
})

A feature module may define its own router module using forChild() as the following:

@NgModule({
  imports: [RouterModule.forChild(childRoutes)],
  exports: [RouterModule],
})

Components, directives and pipes can only be declared once. You can use a common module that declars shared functions once and exports them. Then import the common module should enable sharing.

Add a lazy loading route path in a syntax of {path: 'lazy-path', loadChildren: 'file_path/filename#ModuleClassName', canLoad: [ChildGuard]} for lazy loading of a module. The canLoad parameter is to guard the navigation for lazy loading modules. For lazy loading module route, use an empty top level path to define all children paths: {path '', component: LazyComponent, children: [...]}.

4. Tips

A module is a great way to aggregate classes from other modules and re-export them in a consolidated module. It helps organize the app structure.

Use forRoot to config root level modules, forChild for a child module. Add a {preloadingStrategy: PreloadAllModules} to preload lazy loding moudles.

If a lazy-loaded module doesn’t register a service provider, it uses root inject by default. If a lazy-module defines a service provider, it has a child injector and its provided services are only visible to that module. Be careful of shared module service providers – if it’s used by a lazy-loaded module, a separte service will be created for the lazy-loaded module. You should never register a service provider in a shared module.

In general, prefer registering feature-specific providers in modules (@NgModule.providers) to registering in components (@Component.providers). For a non-root componet, providers in modules are shored by all instance of the component while providers in component will create services for each component instance. Always register application-wide services with the root AppModule, not the root AppComponent.

Register a provider with a component when you must limit the scope of a service instance to that component and its component tree. Apply the same reasoning to registering a provider with a directive.

Don’t specify app-wide singleton providers in a shared module because lazy-loaded module makes its own copy of the service. Put singleton service into a so-called CoreModule and only import it once in AppModule. Add a guard to a module to check if the module was previously loaded.

Create a SharedModule with the components, driectives and pipes that are used widely in your app. Shared modules may re-export other widget modules but should not have providers. There is no need to declare modules in imports to export them in exports. Group shared modules together and export them in a shared module makes code simpler.

Create a CoreModule with provides as a pure service module for the singleton services. It performs the following functions:

  • declares root level components.
  • exports components used by app module selector (no need to export routed root-level components), root route module.
  • declares shared singlton service providers.

Import it int he root AppModule only.

5. DI

Angular creates an application-wide injector during the bootstrap process. You need to register the providers that create the servces the application requires. You can either register a provider within an NgModule or in application components. A provider in an NgModule is registered in the root injector and is accessible in the entire application. A provider in a component is component-scoped, i.e., is accessible only to that component and its children.

Angular creates an injector when it creates a component, either vis an HTML template or via route. The injectors of root component and its children form an injector tree.

A component must ask for services in its constructor. When defining a service, it’s better to use @Injectable() decorator, whether or not it has an injectable constructor parameter, for consistency. Components, directives and pipes don’t need it because @Component, @Directive and @Pipe are subtype of @Injectable.

A provider has two fields: provide and useClass. For example: [{ provide: Logger, useClass: BetterLogger }]. The provide is a token that serves as the key for both locating a dpendency value and registering the provider. Usually the token is the constructor parameter class. The useClass is a provider definition object – a recipe for creating the dependency value. If only a value is given, it’s used as both the provide and useClass.

Use useExisting to define an alias. For example, [ NewLogger, { provide: OldLogger, useExisting: NewLogger}] let both NewLogger and OldLogger use the same NewLogger class.

Use useValue to use a ready-made object rather than ask the injector to create an instance.

When a service needs runtime data, use a service factory to create a service instance. The proiver definition requires three fields: provide, useFactory and deps. The useFactory specifies a service factory. deps lists an array of provider tokens used byu the factory.

For non-class dependencies, you cann’t use interface because it’s gone at runtime. One method is to use InjectToken to link a dependency to a non-class value. Alternatively, use useValue to inject a value directly.

For optional dependency, annotate the constructor argument with an @Optional().

@Host() limit to the host component’s service provider.

For directives, ElementRef can be injected into a directive constructor.

For a derived component, if its base component has injected dependencies, you must re-porvide and re-inject them in the derived class and then pass them down to the base class in the constructor.

Because every component instance is added to an injector’s container, you can use Angular dependency injection to reach a parent component. You can inject a parent component by its concrete class, not by its base class. Use aliase provider to inject a parent by interface. Here is a help method:

const provideParent =
  (component: any, parentType?: any) => {
    return { provide: parentType || Parent, useExisting: forwardRef(() => component) };
  };

It uses forwardRef to break cicular reference.

Use @SkipSelf to skip itself in searching a service.

6. Routing

6.1. Basics

If the app folder is the application root, set <base href="/"> as the first child in <head> tag in index.html. It’s a prerequsite to use history.pushState.

An Angular application has a singleton instance of the Router service. Import RouterModule.forRoot(routes) to config routes in the root module. Adding { enableTracing: true } as the 2nd argument of forRoot method enables router event tracing.

Each route maps a URL path to a component. There are no leading slashes in the path. Use somePath/:param to set a route parameter. Use data property to store arbitrary read-only static data associated with a specific route.

The empty path "" represents the default path, i.e., when the path in the URL is empty. You can set redirectTo: '/defaultPath and PathMatch: 'full' to map it to an existing path. The ** path is a wildcard. The order of the routes matters because the match strategy is first-match wins. More specific routes should be placed above less specific routes.

The <router-outlet></router-outlet> in a template is the place for matched component. Use <a routerLink="/path" routerLinkActive="active"> to navigate to a path. The RouterLinkActive directive adds `“active” CSS class to the element when the router link is active. This directive can be added to the anchor or its parent element.

After the end of a successful navigation lifecycle, the router builds a tree of ActivatedRoute objects that make up the current state of the router. ActivatedRoute is a service that is provided to each route component that contains route specific information such as route parameters, static data, resolve data, global query params, and the global fragment.

Use the routerState property of the Router service to access the current state. The current state of the router including a tree of the currently activated routes together with convenience methods for traversing the route tree.

An Angular component with a RouterOutlet that displays views based on router navigations is called a routing component.

The Router emits many events during a navigation. They are NavigationStart, RoutesRecognized, RouteConfigLoadStart, RouteConfigLoadEnd, NavigationEnd, NavigationCancel, and NavigationError.

When subscribing to an observable in a component, you almost always arrange to unsubscribe when the component is destroyed. There are a few exceptional observables where this is not necessary. The ActivatedRoute observables are among the exceptions. The ActivatedRoute and its observables are insulated from the Router itself. The Router destroys a routed component when it is no longer needed and the injected ActivatedRoute dies with it. Feel free to unsubscribe anyway. It is harmless and never a bad practice.

When a component is not reused, i.e., is created everytime it is accessed for sure, you can use route.snapshot to avoid subscribing to paraMap.

A router’s parameter can be required or optional. In general, prefer a required route parameter when the value is mandatory (for example, if necessary to distinguish one route path from another); prefer an optional parameter when the value is optional, complex, and/or multivariate.

Use this.router.navigate(['/heroes', { id: heroId, foo: 'foo' }]); to navigate to a different path. If the path doesn’t define parameters, the navigation generates a path like localhost:3000/heroes;id=15;foo=foo that is a matrix URL notation.

To navigate a relative path with the Router.navigate method, you must supply the ActivatedRoute to give the router knowledge of where you are in the current route tree. After the link parameters array, add an object with a relativeTo property set to the ActivatedRoute. The router then calculates the target URL based on the active route’s location.

When using a RouterLink to navigate instead of the Router service, you’d use the same link parameters array, but you wouldn’t provide the object with the relativeTo property. The ActivatedRoute is implicit in a RouterLink directive.

Angular uses secondary routes and named outlets to have multiple outlets in a template. The router can keep track of multiple branches in a navagition tree and generating a representation of that tree in the URL like http://.../heroes(popup:compose). The string in parentheses consists of outlet name and path. Each secondary outlet has its own navigation, independent of the navigation driving the primary outlet. Changing a current route that displays in the primary outlet has no effect on the popup outlet.

You can inspect the router’s current configuration any time by injecting it and examining the router.config property.

6.2. Route Guards

You use route guard to allow or deny navigation to a target, to fetch some data, or to take action when leave a component. If a guard returns true, the navagition proceeds, otherwise, the navigation stops and the user stays put. A guard can also navigate to a different route. The guard might be async or sync with a type of Observable<boolean> | Promise<boolean> | boolean. The router supports multiple guard interfaces:

  • CanActivate to mediate navigation to a route
  • CanActivateChild to mediate navigation to a chilid route
  • CanDeactivate to mediate navigation away
  • Resolve to retrieve route data before route activation
  • CanLoad to mediate navigation to a feature module loaded asynchronously

Use a component-less route to group and guard child routes. The Router supports empty path routes; use them to group routes together without adding any additional path segments to the URL.

A better user experience is to use Resolve to retrieve data before the route is activated.

6.3. Async Routing

In route configuration, use loadChildren: 'module-path#ModuleClass' to load a module lazily. Use CanLoad to guard the lazy loading.

The Router offers two preloading strategies out of the box: no preloading (the default), preloading all. Set preloadingStrategy: PreloadAllModules to preload all. Features guarded by CanLoad are blocked from preloading.

You can customize the preloading strategy by implement PreloadingStrategy.preload() method.

Read More

Meteor Notes

1. Introduction

Meteor is a full-stack JavaScript platform, a build tool and a set of packages for developing reactive application for both web and mobile devices. It uses data on the wire to provide connected-client reactivity.

Read More

TypeScript Vue Tour Part 2

1. Refine Webpack

There are a couple of things we like to improve on part 1. We want to move the /index.html to /src/index.html because it’s also a source code file. We let webpack to inject the build output instead of hardcoding the output in this template file. We also want to extrac the webpack manifest and vendor packages into separate bundles.

Read More

TypeScript Vue Tour Part 1

1. The Plan

This is the first part of a series of tours exploring progressive Web applciation (PWA) deveopmenet using TypeScript and Vue. To fully explore the concepts in both PWA and the develpment experience of TypeScript and Vue, we create everything from scratch except some obvious code/resources copied from vue-cli templates.

Read More

VS Code notes

1. Introduction

VS is a code editor at its heart. You can open up to three editors side by side.

Read More

Bootstrap v4 Study Note

1. Setup

Bootstrap requires jQuery, Tether and its JavaScript plugin to work. It uses H5 doctype and viewport meta tag. A sample template is as the following (based on https://v4-alpha.getbootstrap.com/getting-started/introduction/):

Read More

React Ecosystem

There are many pieces in the react ecosystem, either direcly or indirectly needed to build a great web application.

Read More

Querydsl Note

This is a note summurizing the key concepts and practices of QueryDSL. It is based on http://www.querydsl.com/static/querydsl/4.1.3/reference/html_single/ and https://leanpub.com/opinionatedjpa/read.

Read More

GraphQL Java Note

The GraphQL Java implementation https://github.com/graphql-java/graphql-java is not documented well. Its source code is not documented at all. We have to learn it from its source code.

Read More

Chakram REST API testing

1. Chakram Introduction

Chakram is a framework to test JSON REST endpoint. It use promises and offers a BDD testing style. It is built on node.js, mocha test framework and chai BDD assertion library. It use the request lib to provide comprehensive REST request capaiblities.

Read More

Product Design

1. Overview

In any e-commerce application, the product, product variant and category are core entities that define many features of a Web site. This article describes the product-related design of http://dev.commercetools.com/.

Read More

Product Price Design

1. Price Calculation

In http://dev.commercetools.com/, a product variant can have a set of prices represented as an array of Price values. A price is defined with a scope (customer group, channel and country) and/or an optional period (valid from and/or valid to). A price has a regular value and an optional discounted price. When calculate a price, there are two steps: price selection and discount checking.

Read More

Basic Kubernetes

K8s Architecture

First we need a set of computers, physic or virtual, to urn K8s, these computers are called node in K8s. Nodes are often organized as a cluster to provide high availability and scalability. In each node there is a kubelet node agent, a kube-proxynetwork proxy and a docker daemon to run one or more pods. In K8s, a pod consists of a set of docker containers. A pod is the minimum management unit to create, run and schedule.

Read More

Spring Security with OAuth2

For a cloud-native application, security is a big concern and brings new challenges. Luckily, the “silver bullet”, i.e, the open source software, makes life so easy. This time, it’s Spring security and OAuth2.

Read More

Application Logging Tips

为什么需用Logging

理解了logging的目的,那么所有的Tips都不难理解了。当软件变得复杂,尤其是分布式异步并行计算程序比如Cloud Native程序成为主流是,基于IDE单步执行的Debug无法工作。而且多数的bug通常发生在用户现场的生产系统,这时候能够像单步执行那样清晰的给出程序执行各个重要节点的完整状态就非常宝贵。我的经验证明,有了好的application logging,多数的debug工作就变得轻而易举:如果所有的关键步骤的状态数据都有记录,错误的原因也就清楚了。

Read More

简单看板开发流程

1. 需要简化

看板的使用有一个学习和适应过程,多层看板流程的想法比较理想,缺点是层次带来了复杂性。在启动阶段我们希望流程管理和流程工具尽可能简单。我们跳过比较正式复杂的看板系统比如JIRA,其中的一个主要考量就是希望看板工具简单、好用。采用Trello这个极简的看板系统可以达到开发流程可视化的主要目的。同时在开始阶段,我们只采用一层看板,看板把每个人近期几个月内的工作放在一个标准化的简单流程里面。

Read More

用看板管理软件开发流程

1. Scrum or Kanban?

做了多年软件,感觉软件开发的过程是没办法“管理”的。软件开发从本质上讲是一种理想的逻辑思维工作, 高水平的软件开发很大程度上取决于程序员个人的主动性、工作能力和学习能力。可是实际工作中又需要产品开发过程的可视化。可视化意味着:

  1. 做基本的产品功能计划。计划包括任务分解和时间估算。
  2. 了解产品开发过程中任务的完成状态。其中最主要的是工作量、开发速率和开发瓶颈。
Read More

Python Docstring Using Sphinx and reStructuredText

Introduction

Python is a dynamic script programming language that has no static type checking. For Python, documentation becomes very important for developers to understand and use Python components such as modules, classes, functions and methods.

Read More

Odoo Start Process

The Boot Code

In Odoo 8.0, odoo.py, openerp-gevent and openerp-server execute import openerp and openerp.cli.main() method to boot the Odoo backend services.

Read More

How Does Odoo Load Addons and Modules

Method load_addons in http.py

Odoo starts loading addons when it receives the first HTTP request. In the __call__ method of http.py file, it calls load_addons to load all addons in the specified addon paths.

Read More

Odoo Cron Job

Odoo makes running a background job easy: simply insert a record to ir.cron table and Odoo will execute it as defined. The ir.cron table is defined in openerp/addons/base/ir/ir_cron.py file. It has the following columns:

Read More

Understand Odoo Model Part 3

1 Old-style Fields

Because most Odoo addons still use old-style field definition. It is necessary for a developer to understand the old old-style field definitions. These fields are in openerp/osv/field.py file.

Read More

Odoo Product Model

1 Overview

The core Odoo addon for product is called product (“Products & Pricelists”). The followings are features listed in the module description:

Read More

Understand Odoo Model Part 2

1 Fields in Odoo Model

A model represents a business entity that is stored in a database. Fields represent entity properties and the corresponding table columns in the database. Odoo also uses fields representing entity relationships. Odoo 8.0 introduces a new field syntax but keeps the old syntax for backward compatibility. Here we focus on the new syntax defined in openerp/fields.py.

Read More

Understand Odoo Model Part 1

1 Not An Easy Task

Model is the core concept in Odoo. A model is a Python class that represents a business entity and related operations. All Odoo business entities are implemented as models that are stored in a backend PostgreSQL database. Understanding how it works is a must for an Odoo developer.

Read More

Understand Odoo Environment

In Odoo 8.0, openerp/api.py defines two classes managing “environments” of a database access for a client request: the Environments class and the Environment class.

Read More

Replacing Decorated Methods in Python

The Question

In my application, I need to replace a decorated class method using a new function that performs some actions before and after the replaced method but still keep the same replaced method interface, i.e., taking same input and return the same output. In general programming terms, I implement a decorator pattern for some “decorated” Python methods.

Read More

Guide to Odoo Community Association Quality Tools Part 2

In the [Guide to Odoo Community Association Quality Tools Part 1] (http://www.mindissoftware.com/2014/10/28/guide-Odoo-OCA-QA-tools-part1/), we discussed how to link a GitHub repository with the OCA quality tools to run tests and code coverage. Here we investigate the implementation details of the Odoo test framework and the OCA quality tools.

Read More

Guide to Odoo Community Association Quality Tools Part 1

Choices of Odoo 8.0 Testing Services

Odoo 8.0 has a new test framework/platform called runbot. It looks good but is evolving and lack of documents. On the other hand, Odoo Community Association (OCA) has a Maintainer Quality Tools project that helps to ensure the quality of Odoo addons. It can run module unit tests and report test coverage. It uses Travis CI continuous integration service and Coveralls code coverage reporting service. Both are free services for open source projects. Both services support GitHub integration and automatically run test and report results to GitHub projects. This article describe the the setup of these two service for an Odoo addon project.

Read More

Guide to boto Amazon MWS Python package

Overview

boto is a Python package for Amazon web service APIs. It makes it easy to use Amazon marketplace web service (MWS) APIs by providing supporting functions such as connection pool, auto-retry, response parsing and iterative calls for extra data. However, there is no document and the Python code is not well commented. Below is an attempt to summarize boto implementation and usage.

Read More

How to save and load module configuration in Odoo

Finding a Solution

Often a custom Odoo module needs some parameters to run. However, [my previous analysis of Odoo’s own implementation] (http://www.mindissoftware.com/2014/10/16/Odoo-Module-configuration-settings/) doesn’t give me an obvious answer on how to implement a configuration function for a custom module. Nonetheless, the analysis helps me to evaluate answers I found online today.

Read More

Create Odoo 8.0 Ubuntu Docker Image

Overview

Odoo 8 was released on 09/18/2014. We created a self-contained [Odoo 8.0 Docker image] (https://registry.hub.docker.com/u/yingliu4203/odoo8nightly/) that was installed from [the latest Odoo 8.0 nightly build] (http://nightly.openerp.com/8.0/nightly/deb/). The Docker image building source code is in [a GitHub repository] (https://github.com/YingLiu4203/odoo-v8-nightly-docker).

Read More

Run and Debug Odoo using PyCharm in Ubuntu

As a beginner, being able to debug through the Odoo source code is a great learning experience. PyCharm Professional Edition is a wonderful tool to develop/debug Python applications. The following are steps to run/debug Odoo using PyCharm in Ubuntu. Though not officially stated, running Odoo in Windows is not a good idea. I started with a freshly installed Ubuntu 14.04.1 LTS desktop.

Read More

Python Package and Import

In Python, a module is a file that contains Python code. The first time a module is imported, all statements in the module are executed from top to bottom. The variables, functions, and classes declared in the module become accessible to other modules. Later imports, unless using reload(), use the already-imported module without executing the module code again. The module concept is easy to understand.

A project usually has many modules. A package is a directory that contains modules and a __init__.py file. The package concept means two things in Python:

Read More

Odoo connector events

Odoo Connector (https://github.com/OCA/connector) is a framework used to integrate Odoo with other systems. It makes development of bi-direction data synchronization easier. Among other functions, it hooks into Odoo data model to intercept record creation, update and deletion calls and raise corresponding events that can be subscribed by an integration application. In this blog we look at the implementation details of its event mechanism.

Read More

Python Decorator Tutorial

Decorator is widely used in Python programs. As a beginner Python developer, I am curious about the theories and typical uses of a decorator. This blog summarizes what I learned about decorator.

Read More

Build a new addon in Odoo V8 part 2

This is the second part of the Open Adademy example. In part one we created a simple module layout. This part demonstrates model relationships, default values and the use of onchange api. All code are listed at the end of this blog. When making a change of an addon code, you may need to re-start Odoo or update the addon or do both.

Read More

Create Odoo V8 Docker image from GitHub source code using shell script

The following steps are based on [ANDRÉ SCHENKELS’s installation script.] (https://github.com/aschenkels-ictstudio/openerp-install-scripts/blob/master/odoo-v8/ubuntu-14-04/odoo_install.sh) with below changes. Some changes such as local settings are necessary because we starts from a base Ubuntu operating system.

Read More

Install Odoo V8 in Ubuntu from GitHub source code

This blog is based on [ANDRÉ SCHENKELS’s post.] (http://www.schenkels.nl/2014/07/install-odoo-v8-0-from-github-ubuntu-14-04-lts-formerly-openerp/) The whole process can be fully automated by download and run [his installation script.] (http://www.schenkels.nl/2014/07/odoo-v8-install-script-github-ubuntu-14-04-lts/)

Read More