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.
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:
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:
Notes on the sample and versions:
Let’s consider key parts of the Explorer for Angular 2 vs. its Angular 1 counterpart.
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:
The component represented by the
The src/app.ts file defines the AppCmp TypeScript class. The @Component decorator has the selector: 'app-cmp' definition, which maps the
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:
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
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:
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.
The Explorer sample consists of the following folders:
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:
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>
Let’s consider some interesting details of implementation of a specific component.
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:
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.
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.
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.
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) {
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'}}
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.