Update (5/11/2016): Our Angular 2 components now support NG2 RC. We plan to officially release our components when Angular 2 ships. Learn more about our Angular 2 support.
We're happy to introduce our first official Beta version of Wijmo components for Angular 2. The components allow you to use Wijmo controls in Angular 2 templates' markup. Though this is a Beta release, we're offering Angular 2 components for all the Wijmo controls right from the beginning, along with additional Angular-specific features like FlexGrid cell and detail row templates, ListBox and Menu item templates, and so on. The functional coverage is virtually the same as what we offer for the Angular 1 framework.
Why Angular 2?
Angular 1 has been wildly successful for Google and for Wijmo. So when Angular 2 came around, of course we took notice. Angular 2 is quite different from Angular 1. Here are some key reasons why you might want to consider Angular 2:
- To stay up-to-date. Angular 2 is the natural next step from Angular 1, and therefore is the version that will get the most community support, updates, and improvements from now on.
- Angular 2 offers better encapsulation. Components are reusable TS classes with views, units that can be combined in an arbitrary manner to construct application view and logic.
- Angular 2 has many improvements, including performance gains obtained with fast initial loads through server-side pre-rendering, offline compiles for fast startup, and ultrafast change detection and view caching for smooth virtual scrolling and snappy view transitions.
- Angular 2 is actually simpler than Angular 1, because it delegates many common tasks to the JavaScript interpreter (and TypeScript transpiler). The net result is cleaner/smaller/better applications that leverage the HTML5 platform more efficiently.
- More integration with TypeScript. Although you don’t have to use TypeScript with Angular 2, you can. And if you do, you will get great benefits including strong-typing, compile-time error checking, and IntelliSense support. This support includes your entire application, along with Angular 2 itself and any TypeScript-based controls you use (including of course the Wijmo 5 controls).
The Explorer sample
Our Explorer sample is a full-blown Angular 2 Single Page Application (SPA) with routing, custom services and pipes. As you probably already guessed, the Explorer uses Wijmo components for Angular 2. The Explorer sample for Angular 2 resembles its Angular 1 counterpart (shipped as a part of the Wijmo library distribution). Comparing its implementation with its Angular 1 version answers some key questions:
- How do I implement familiar Angular 1 functionality in Angular 2?
- How do I use Angular 2 components and directives for Wijmo controls in Angular 2 application markup?
Notes on the sample and versions:
- The sample is based on Angular 2 Beta 8.
- Angular 2 team releases new versions frequently, and we’ll continue to update the sample and Wijmo control components so they're current with the latest available version.
- The implementation of Wijmo control components is comprehensive. They support:
- Binding to all available control properties
- Two-way binding to properties for which it makes sense (like InputNumber’s "value" and "text")
- Event bindings
- Binding via ngFor for child components like WjFlexGridColumn, and so on.
Let’s consider key parts of the Explorer for Angular 2 vs. its Angular 1 counterpart.
The Angular 2 Application
In Angular 1 version of Explorer, we added ng-app=app directive to the root element of the default.htm page, and registered the root app application module in the scripts/app.js file. For the navigation parts of default.htm, we also included ng-controller=navigationCtrl directive that references a controller implemented in the scripts/controllers/navigationCtrl.js file. Let’s look at how it’s done in its Angular 2 counterpart. The default.htm page’s body looks like:
<body>
<app-cmp></app-cmp>
</body>
The
Think of a component as of a single unit which is a model, controller and a view in one entity.
The component comprises two core parts:
- TypeScript class:
- Implemented in a file with the .ts extension
- Implements the model and controller and exposes properties and events that can be used in the view
- This is a "code-behind" for the component’s view
- View:
- Implemented in an .html file
- This defines the look of the component.
- The view may include other components and bind this component’s members to their properties and events.
The component represented by the
- Provides the whole SPA application look, with a navigation menu and a special place where the current "page" should be shown. This is an analogue of the element in Angular 1 application, marked with the ng-view directive.
- Define application routing.
- Bootstrap the application.
The src/app.ts file defines the AppCmp TypeScript class. The @Component decorator has the selector: 'app-cmp' definition, which maps the
Routing
The AppCmp class has also a @RouteConfig decorator. This is where we define the application routing rules, and it’s an analogue of the $routeProvider.when(…).when(…)… code in the app.js file of the Angular 1 Explorer. In Angular 1, each route item defines a virtual path exposed in browser’s address field, URL of HTML file (a view) that will be shown in the tag marked by ng-view directive, and a controller that will back this html file. Let’s look at route item definition example in the Angular 2 application:
{ path: '/input/listbox', component: ListBoxCmp, as: 'InputListBox' },
Each route also defines a virtual path and… a component! Yes, the target of Angular 2 navigation is a component (the ListBoxCmp component in this example), but not an arbitrary html file. When the user clicks a link associated with a certain route item, Angular 2:
- Creates an instance of the component class specified in the route
- Creates its view
- Adds the view DOM to a special tag in the application view (We’ll explain how to define such a tag later in this post.)
Navigation UI
The view of the AppCmp component is defined in the src/app.html file. The AppCmp class has the @View decorator with the templateUrl: src/app.html property, which defines a path to the component’s view file. Let’s look into this html file. As you see, its content looks similar to the content of the default.htm file from the Angular 1 Explorer. At the bottom of the file you may find two interesting lines of markup:
<a [routerLink]="['/' + link.alias]">{{ link.text }}</a>
Its Angular 1 counterpart:
<a ng-href="{{ link.url }}">{{ link.text }}</a>
In Angular 2, we use [routerLink] attribute directive instead of the ng-href directive in Angular 1. This directive should be assigned to route name (alias) instead of the route virtual path as we did in in Angular 1. In the route definition example above, such an alias is specified using the as: InputListBox property. The other important piece of markup is the tag where Angular will add current component’s view. We marked this tag with ng-view directive in Angular 1 Explorer. In Angular 2, it’s the
Bootstrap
The last line of the app.ts file looks like:
bootstrap(explorer.AppCmp, [
ROUTER_PROVIDERS,
provide(LocationStrategy, { useClass: HashLocationStrategy }),
MenuSvc,
DataSvc,
SparkSvc
]);
This call to the Angular bootstrap method creates our AppCmp component instance and initializes it as an application root component. In other words: it creates and runs the application. We also pass some additional parameters here:
- We specify that routing will work using hash location strategy. That is, virtual paths will be added after the hash ("#’) symbol to the root path.
- We add MenuSvc, DataSvc and SparkSvc services. These are our own custom services implemented in the src/services folder; they are analogues of the same named services in the Angular 1 Explorer. By passing them in the bootstrap method call, we make them available to the all components in the application.
To get an instance of a certain service in a component implementation, we should just add a parameter of the service type to the component constructor, and Angular will pass an instance of the wanted service to this parameter. We don’t need to create it by ourselves. For example, the constructor signature of the ListBoxCmp component class looks like this:
constructor( @Inject(DataSvc) dataSvc: DataSvc)
It defines the parameter of the DataSvc service type that receives an instance of this service, which will be available for usage right in the constructor code. It’s worth mentioning here that services are implemented as regular TypeScript classes.
Folder structure
The Explorer sample consists of the following folders:
- "scripts" folder
- Subfolders
- vendor: Contains Wijmo run-time JavaScript files, including wijmo.angular2.min.js file containing Wijmo components for Angular 2 implementation.
- definition: Contains TypeScript definition (.d.ts) files for Wijmo library modules. Note that definition files for Wijmo Angular 2 components are not present here.
- Subfolders
- "node_modules" folder
- Contains NPM installation of Angular 2 and supporting libraries.
- Subfolders
- wijmo: Contains TypeScript definition (.d.ts) files for Wijmo Angular 2 components. Note that these files don't appear as items of the sample project.
- "src" folder
- This is where all the application components, services and pipes are implemented.
- Subfolders
- components: Includes implementations of components representing separate Explorer example "pages."
- Subfolders input, grid, chart, etc: Includes implementations pertaining to specific Wijmo modules
- Each component represents a single example. For example, GridIntroCmp and GridGroupingCmp components in the grid subfolder represent FlexGrid "Introduction" and "Grouping" example.
- Each component implementation is represented by two sibling files:
- .ts file defines component class and decorations
- .html file defines component view
- Example: GridIntroCmp.ts and gridIntroCmp.html
- The corresponding code in Angular 1 Explorer can be found in the scripts/controllers and partials folders, containing JavaScript controllers and HTML views implementation respectively.
- "services" subfolder contains TypeScript classes implementing application services, the analogues of Angular 1 Explorer services implemented in the "scripts/services" folder.
- "pipes" subfolder implements custom "pipes." The pipe is an Angular 2 name for the Angular 1 filter notion, with the similar usage. The Angular 1 Explorer counterpart for this folder is "scripts/filters."
- "resources" and "translations" folders have the same meaning and content as in the Angular 1 Explorer
Components for Wijmo controls
The components for Wijmo controls are implemented in the wijmo.angular2.min.js file. Though this file is included in the sample in the same way as the other Wijmo modules, by referencing it in the script tag in the default.htm page its content should be consumed differently. Technically it's constructed in a different way. Wijmo library modules are "internal" in terms of TypeScript, and to reference their content, you use global property paths like wijmo.input.Menu. In contrast, Wijmo Angular 2 modules are "external" and their content should be consumed using TypeScript import statements. The wijmo.angular2.min.js file comprises a set of named SystemJS modules, with the names like "wijmo/wijmo.angular2.input", each module contains Angular 2 components for corresponding Wijmo library module. For example, consider the following import statement:
import * as wjInput from 'wijmo/wijmo.angular2.input';
You may reference specific components from the module using expressions like wjInput.WjMenu or wjInput.WjInputNumber. In the folder structure description above, we already mentioned that TypeScript definition (.d.ts) files for Wijmo Angular 2 components are placed in a different location than definition files for Wijmo library modules (in the node_modules\wijmo folder). That's because they represent "external" TypeScript modules, and a special NodeJS module resolution algorithm is used by the TypeScript compiler to resolve module names to actual definition files location. For example, when you import the module "wijmo/wijmo.angular2.input", TypeScript will search for the wijmo.angular2.input.d.ts file in the node_modules\wijmo folder. The usage of Angular 2 components for Wijmo controls in HTML markup is virtually the same as for their Angular 1 counterparts. For example, this markup creates Wijmo Menu with items:
<wj-menu [(value)]="itemCount"
[header]="'Items'"
(itemClicked)="menuItemClicked(menu3, $event)">
<wj-menu-item [value]="5">5</wj-menu-item>
<wj-menu-item [value]="50">50</wj-menu-item>
<wj-menu-item [value]="500">500</wj-menu-item>
</wj-menu>
The only difference with Angular 1 is property binding syntax. For Angular 2, you'll need to wrap the property name in brackets as follows:
- One-way binding: Square brackets
- Ex.: [header]
- Event binding: Round brackets
- Ex.: (itemClicked)
- Two-way binding: Square and round brackets
- Ex.: [(value)]
Components for Wijmo controls are derived directly from control classes they represent. The definition of component class representing InputNumber control looks like:
export class WjInputNumber extends wijmo.input.InputNumber
So if you obtained a reference to control component instance, you automatically have a reference to the control itself and can use its API. The reference to the component can be obtained in a usual way via the local template variable. For example, this markup will provide the "flex" variable that references FlexGrid instance:
<wj-flex-grid #flex [itemsSource]="data"></wj-flex-grid>
Implementing a Specific Component
Let’s consider some interesting details of implementation of a specific component.
Single controller for multiple views
In Angular 1, a developer has full freedom to define views and controllers separately and combine them in an arbitrary manner. The developer may proclaim any part of an HTML page as bound to a specific controller, and even use the same controller in multiple HTML pages. Angular 1 Explorer extensively utilizes this capability. For example, the sample defines the basicCtrl controller used in many FlexGrid example pages (like intro.htm, grouping.htm and paging.htm). In Angular 2 this seems impossible, as a component defines both a model/controller logic and a view as a single unit. You can’t define them separately and combine as you need. How do we manage this challenge? The answer is very simple: utilize TypeScript class inheritance capability. We derive classes in order to implement specific views in them, while use shared model/controller logic implemented in the base class. So, multiple views provided by multiple derived classes inherit the same model/controller implementation from the base class. Let’s look at the class definitions of the following components representing FlexGrid examples:
export class GridIntroCmp extends GridBaseCmp
export class GridGroupingCmp extends GridBaseCmp
As you see, both component classes representing Grid Introduction and Grouping examples are derived from the single GridBaseCmp class. The base GridBaseCmp class:
- Implements all the model/controller behavior
- Exposes all the necessary properties for binding views
- Exposes all the methods to call from view events
Once we have the base, we can derive a specific component class (like GridGroupingCmp), but all the controller behavior and API is automatically derived from the base GridBaseCmp class. This is essentially the same as a single controller in multiple views in Angular 1.
Getting a reference to control from a component
Sometimes you may need to refer to a control defined in the component's view. In Angular 1, you retrieved it by binding the Wijmo directive's control proptery to a controller property. In Angular 2, you'll need to add a "local template variable" to the control's component element in the view.
For example, in the GridIntroCMP component's view, you may define the "flex" template variable for FlexGrid:
<wj-flex-grid #flex …>
In the component code, you may declare the following variable that automatically receives a reference to the FlexGrid instance with the #flex variable defined in markup:
// references FlexGrid named 'flex' in the view
@ViewChild('flex') flex: wijmo.grid.FlexGrid;
Note that we have this declaration in the GridBaseCmp class (a base class for GridIntroCmp), but it will still receive a reference defined in the GridIntroCmp view.
Respond to component property changes
In Angular 1, if you need to perform some actions on some controller property change, you subscribe a callback using the $scope.$watch function, which is called by Angular when the property value changes. In Angular 2, we simply declare a true ES5 property with getter and setter and perform necessary actions in the property setter. For example, this declaration defines the dataMaps; property in the GridBaseCmp class, and calls the _updateDataMaps method on property change:
get dataMaps(): boolean {
return this._dataMaps;
}
set dataMaps(value: boolean) {
if (this._dataMaps != value) {
this._dataMaps = value;
this._updateDataMaps();
}
}
Another way to act on property changes is to add a special onChanges method to the component class. Angular will automatically call the method on each property change.
Services
The service is just an arbitrary class that exposes some methods or properties. It should be marked with the @Injectable() decorator so that Angular is able to inject it in your components. For example, the definition of the DataSvc service looks like this:
@Injectable()
export class DataSvc {
To inject it into a component, you specify it as the component constructor parameter with the @Inject decorator:
constructor( @Inject(DataSvc) dataSvc: DataSvc) {
Pipes
Pipe is an analogue of Angular 1 Filter. A pipe is implemented as a class with the @Pipe decorator that exposes special transform method. For example, here’s the implementation of the "glbz" pipe, an analog of Angular 1 Explorer’s glbz filter:
@Pipe({
name: 'glbz',
// stateful pipe
pure: false
})
export class GlbzPipe {
transform(value: any, args: string[]): any {
return wijmo.Globalize.format(value, args[0]);
}
}
And its usage:
{{passengers | glbz:'n0'}}
Next Steps
We're still moving quickly alongside the Angular 2 team to make sure we have components to ship when Angular 2 releases. We'll continue to provide updated builds to stay in sync with the Angular 2 Beta. Our components are already very stable, but we continue to ensure they're ready for production when Angular 2 officially releases.
Update (3/15/2016): This post has been updated to announce the release of our Angular 2 components that support NG2 Beta 8. Learn more about our Angular 2 support.