AngularDart: cannot cast to a material component - dart

I have a dart app that contains a template with a material-checkbox which I am unable to use in my component. Here is a simple template to demonstrate the problem (two_boxes.html):
<!--
Simple HTML to test checkboxes
-->
<input type="checkbox" id="cb0">
<label>Standard Checkbox</label>
<material-checkbox label="Material Checkbox" id="cb1">
</material-checkbox>
<button (click)="doTest()">Test</button>
and the corresponding component in which I try to use the checkboxes (two_boxes.dart). I can use the standard checkbox in a cast as expected but cannot find a way to do the same with the material checkbox:
// Component for testing
import 'package:angular/angular.dart';
import 'package:angular_components/angular_components.dart';
import 'dart:html';
#Component(
selector: 'two-boxes',
templateUrl: 'two_boxes.html',
directives: const [MaterialCheckboxComponent],
pipes: const [
COMMON_PIPES
])
class TwoBoxes {
// Get the the two checkboxes and see if they are checked
void doTest() {
var checkbox_standard = querySelector("#cb0");
print(checkbox_standard.runtimeType.toString()); // InputElement
print((checkbox_standard as CheckboxInputElement).checked); // Succeeds
var checkbox_material = querySelector("#cb1");
print(checkbox_material.runtimeType.toString()); // HtmlElement
print((checkbox_material as MaterialCheckboxComponent).checked); // Error!
}
}
The last statement fails when I run the app in Dartium following a "pub serve" (no errors) with:
VM54:1 EXCEPTION: type 'HtmlElementImpl' is not a subtype of type
MaterialCheckboxComponent' in type cast where HtmlElementImpl is
from dart:html MaterialCheckboxComponent is from
package:angular_components/src/components/
material_checkbox/material_checkbox.dart
Clearly this way of casting does not work. I searched for hours how to solve this error and find the correct way to do this but obviously in the wrong places. What am I missing here? I am using Dart VM version 1.24.2.

There is no way to get the component instance from an element queried this way.
You can use #ViewChild()
class TwoBoxes implements AfterViewInit {
#ViewChild(MaterialCheckboxComponent) MaterialCheckboxComponent cb;
ngAfterViewInit() {
print(cb.checked);
}
to get a specific one if you have more than one, you can use a template variable
<material-checkbox #foo label="Material Checkbox">
with
#ViewChild('foo') MaterialCheckboxComponent cb;
You can find more information about this topic in this TypeScript answer angular 2 / typescript : get hold of an element in the template
The syntax is a bit different (type annotations on the right and {} around the optional read parameter, which are not used in Dart.

Related

Type inference for MultiToken in Angular Dart

Using Dart 2.5.0, Angular 5.3.
Given the following component:
const MultiToken<String> stringsToken = MultiToken("stringListToken");
#Component(
selector: 'my-app',
template: '<div>{{ myStringValues.join(", ") }}</div>',
providers: [
ValueProvider.forToken(stringsToken, "one"),
ValueProvider.forToken(stringsToken, "two"),
]
)
class AppComponent {
final List<String> myStringValues;
AppComponent(#Self() #Inject(stringsToken) this.myStringValues);
}
I'm getting the exception
dart_sdk.js:101871 EXCEPTION: Type 'List<dynamic>' should be 'List<String>' to implement
expected type 'List<String>'
This happens when the constructor tries to inject the provided List value to the local myStringValues, as if the provisioned List was downcasted to List<dynamic>. Now, if I replace the token creation with
const MultiToken<String> stringsToken = MultiToken<String>("stringListToken");
the component will work as expected, the injected values will be displayed.
How come that, using the first declaration syntax, the type of the MultiToken could not be inferred to String? To add to my confusion, if using the first version of declaration, i.e.
const MultiToken<String> stringsToken = MultiToken("stringListToken");
and then printing the runtimeType of stringsToken, it will print MultiToken<String>.
So where, and how, the bound generic type, is lost in the first case, when trying to inject the value?

Returning anonymous JS function which returns javascript plain object from Dart

Dart Code
import 'dart:html' as html;
import 'dart:js' as js;
import 'package:js/js.dart';
void main() {
var data = new AddLocationData(locationName: "Location1", locationPath: "ThisFolder");
var func = () => data;
html.window.console.log(func);
html.window.console.log(func());
}
#JS("")
#anonymous
class AddLocationData {
external String get locationName;
external String get locationPath;
external factory AddLocationData({String locationName, String locationPath});
}
You would assume that func will be a js function but its not. Its type/name is main_closure. See the screenshot
So the first two lines were printed from Dart code then I used Chrome Inspect Element window and right clicked on main_closure' and selected "Store as global variable" which then printedtemp1` and then I used it to display some information about the generated code.
So it is clear Dart returned an object and not a js function and so is the reason of asking this question.
So I want temp1 to be a function instead of temp1.call$0 so that I can get the data by calling temp1() and not temp1.call$0().
See js package doc:
Passing functions to JavaScript.
If you are passing a Dart function to a JavaScript API, you must wrap it using allowInterop or allowInteropCaptureThis.

Angular 2 Dependency Injection

I have read several articles on dependency injection and cannot seem to get it working the way I think it should. From what I have read, you can use the #Injectable decorator on a class and then the metadata is created for the DI like so:
import {Hero} from './hero.model';
import {Injectable} from 'angular2/angular2'
#Injectable()
export class HeroService {
constructor() {
console.log('Hero Service Created');
}
}
Then in your component, you can use it in your constructor (with the proper import) like this:
constructor(heroService: HeroService) {
console.log('App Component Created');
}
However, I get the following error: Cannot resolve all parameters for AppComponent(?). Make sure they all have valid type or annotations.
I am able to get it working properly if I remove the #Injectable syntax from the service and instead have my constructor like this:
constructor(#Inject(HeroService) heroService: HeroService) {
console.log('App Component Created');
}
With everything that I've read, these should do the same thing, but they aren't. Any ideas as to why? I am using Typescript 1.6.2 with VS 2013, angular 2 v2.0.0-alpha.46, and systemjs v0.19.5.
Make sure that you've specified "emitDecoratorMetadata" option in your TypeScript configuration.

Is it possible to declaratively bind a CustomEvent handler when using Dart Web UI WebComponents?

I've tried to bind a custom event handler to a WebComponent that has an EventStreamProvider exposed via a getter, but it comes back with "Class 'DivElement' has no instance getter 'onMainAction'.".
Trimmed down component .dart code...
import 'dart:html';
import 'dart:async';
import 'package:web_ui/web_ui.dart';
class SegmentedButtonsListComponent extends WebComponent {
static const EventStreamProvider<CustomEvent> mainActionEvent = const EventStreamProvider<CustomEvent>("MainActionEvent");
Stream<CustomEvent> get onMainAction => mainActionEvent.forTarget(this);
}
Trimmed usage of component…
<x-segmented-buttons-list id="segmented-buttons-list" on-main-action="eventHandler($event)"></x-segmented-buttons-list>
Trimmed code from main.dart…
import 'dart:html';
import 'dart:async';
import 'package:web_ui/web_ui.dart';
const EventStreamProvider<CustomEvent> mainActionEvent = const EventStreamProvider<CustomEvent>("MainActionEvent");
void eventHandler(CustomEvent event) {
print("""
Yabba Dabba Doo!
Type: ${event.type}
Detail: ${event.detail}
""");
}
void main() {
mainActionEvent.forTarget(query('#segmented-buttons-list')).listen(eventHandler);
}
The "MainActionEvent" custom events are being dispatched by components instantiated within this "list" component.
As you can see from the above example I can catch the events if I create an EventStreamProvider in main.dart and target the component, that works fine (but by-passes the Stream getter in the component).
It would be great though if I could dispense with the EventStreamProvider in main.dart and simply bind to the onMainEvent getter on the component.
Is that possible?
Update 2013-05-05:
Siggi explains below that at present it is not possible to do this, but there is a way to reference the component's CustomEventProvider's getter via the element's xtag.
I found that I had to use a Timer to query the DOM after main() has completed because xtags aren't populated until the main() event loop has finished.
void postMainSetup() {
query('#segmented-buttons-list').xtag.onMainAction.listen(eventHandler);
}
void main() {
Timer.run(postMainSetup);
}
With the above setup a new CustomEventProvider isn't needed to monitor the component.
Good question!
I see a couple parts to this question:
using custom events directly on a component: Currently web_ui uses different objects to represent your component and the actual dom element it represents. In the future, we plan to extend directly from "DivElement" instead of "WebComponent" and that will allow you to do what you wrote.
Meanwhile, you'll have to be more explicit when you want to use the host or shadow root of your component. In your example, it seems like you want to attach the event to the host, so you would need to write something more like this:
Stream<CustomEvent> get onMainAction => mainActionEvent.forTarget(this.host);
using 'on-someting-foo' syntax in a component: you probably found a bug/missing feature =). Currently we treat attributes in a special way and bind their values to fields of a component if we identify that the target was corresponds to a component. We do this for value bindings, but not yet for binding custom events. A workaround before this feature is added, would be to query for your element and attach the event by hand:
query('#host-node').xtag.onMainAction.listen(...);

Internal Error:... ambiguous reference: 'DataGrid' is defined in library web/DataGrid.dart and also in web/out/DataGrid.dart

I created a bare-bones datagrid using web-ui for testing and had it working just fine. Then I decided to try to declare it as a component. I changed around the library references and now it is giving me the above error when I try to run the application. You can see my file structure below. The reason I am getting the "ambiguous reference" message when I try to run it is that when I went into the auto-generated DataGrid.dart file in the out directory, it had the following declaration
import 'DataGrid.dart';
...
import '../DataGrid.dart';
I am confused as to why the generated code imports them both. One thing that I considered is that it could be because the DataGridPage.html file instantiates my DataGrid component and my DataGridPage.dart file imports DataGrid.dart so that it can have references to DataGridColumn (it needs to set the columns for the DataGrid). In DataGridPage.dart, I also attach to certain DataGrid events such as SortColumnChanged and SelectionChanged so I need to request a copy of my DataGrid instance in DataGridPage.dart (I don't think there is a way to attach to events from the web component instantiation in DataGridPage.html).
Any ideas about what I am doing wrong?
Here is my file structure:
DataGrid.dart
--------------------------------------------
library datagrid;
...
part 'DataGridColumn.dart';
part 'DataGridRow.dart';
class DataGrid extends WebComponent{...}
DataGridRow.dart
--------------------------------------------
part of datagrid;
class DataGridRow {...}
DataGridColumn.dart
--------------------------------------------
part of datagrid;
class DataGridColumn {...}
DataGrid.html
--------------------------------------------
[contains the component declaration UI]
DataGridPage.html
-----------------------------------------
...
<div is="s-datagrid" id="myDataGrid" ItemsSource="{{app.Assets}}" Columns="{{app.Columns}}"></div>
...
DataGridPage.dart
--------------------------------------------
import 'DataGrid.dart';
import 'Asset.dart';
void main() {
}
DataGridApp _app;
DataGridApp get app {
if (_app == null) {
_app = new DataGridApp();
}
return _app;
}
class DataGridApp{
//provides ItemsSource and DataGridColumn data
}
jmesserly has answered this on the github site. He said that you need to remove the component import in your main dart file. So in my example I would remove the import 'DataGrid.dart' statement from the DataGridPage.dart. The IDE will give you a warning but you can ignore it because it will actually be run from the out folder.
GitHub Web-UI Issue 342

Resources