Do something after attribute value got assigned - dart

I would like to do some mapping after the members have been set by angular dart:
#Component(
selector: 'travel-step',
templateUrl: 'packages/TravelPlanner/travelstep/travel_step_component.html',
useShadowDom: false,
publishAs: 'cmp')
class TravelStepComponent {
// Deprecated but impossible to replace, since the new syntax is not ready
#NgTwoWay('step')
TravelStep step;
TravelStepComponent() {
// step is null at the moment
}
}
I'm using angular v. 0.12. When the constructor is called, step is still null.
I could do it with a watch expression but I only want to do it once, so this workaround is not how I want to do it.

You can implement AttachAware and put your code into the attach() method.
Similar behavior can be achieved by implementing ShadowRootAware and onShadowRoot().
You need to give Angular some time to evaluate the bindings and assign the values. Use one of these methods according to your requirements.
Sometimes it might help to (additionally) wrap your code into a
new Future(() {
your code here
});
to delay the execution of your code.
Another approach is to implement a setter and execute your logic there
#NgTwoWay('step')
TravelStep _step;
TravelStep get step => _step;
set step(TravelStep s) {
// your code here
_step = s;
// or here
}

Related

How do I run some code only once in Dart?

I wonder if there's a language sugar/SDK utility function in Dart that allows to protect a certain code from running more than once?
E.g.
void onUserLogin() {
...
runOnce(() {
handleInitialMessage();
});
...
}
I know I can add a global or class static boolean flag to check but it would be accessible in other functions of the same scope with a risk of accidental mixup in the future.
In C++ I could e.g. use a local static bool for this.
There is no built-in functionality to prevent code from running more than once. You need some kind of external state to know whether it actually did run.
You can't just remember whether the function itself has been seen before, because you use a function expression ("lambda") here, and every evaluation of that creates a new function object which is not even equal to other function objects created by the same expression.
So, you need something to represent the location of the call.
I guess you could hack up something using stack traces. I will not recommend that (very expensive for very little advantage).
So, I'd recommend something like:
class RunOnce {
bool _hasRun = false;
void call(void Function() function) {
if (_hasRun) return;
// Set after calling if you don't want a throw to count as a run.
_hasRun = true;
function();
}
}
...
static final _runOnce = RunOnce();
void onUserLogin() {
_runOnce(handleInitialMessage);
}
It's still just a static global that can be accidentally reused.

programatically adding an on-tap attribute referencing a dart function for a HtmlElement

I have a dart function:
_addSelection(HtmlElement ele){
ele.classes.add("selection");
}
I would either want 1 or 2 things to occur, either A) execute an on-tap and on-track function given the selection class.... OR Dynamically add the on-tap and on-track attributes referencing reflected dart functions.
I have 2 functions:
#reflectable
onTap(CustomEventWrapper cew, params){
//...
}
#reflectable
onTrack(CustomEventWrapper cew, params){
//...
}
I was looking at the HtmlElement class and documentation and I wasnt quite understanding how to do this.
Edit if I were using jQuery and Javascript, I would be doing something as simple as:
$(document).on("tap", function(){});
$(document).on("track", function(){});
Edit2 Added Angular Dart because both designs leverage Dart backend and markup front end.
You could do:
_addSelection(HtmlElement ele) {
ele.classes.add("selection");
ele.onTouchEnd.listen((TouchEvent touch) {
...
});
}
That would give you something close to the tap event. If you wanted to get really fancy you could also listen for onTouchStart and only call your onTap handler when the time between the start and end is small.

Angular dart component init

Is there a way to wait for the component to be initialized?
#Component(
selector: "my-component",
templateUrl: 'component/my.html',
useShadowDom: false,
publishAs: "ctrl"
)
class MyComponent {
#NgAttr('foo')
String foo;
}
#NgAttr('bar')
String bar;
}
MyComponent(){
print(foo);
print(bar);
}
}
Usage:
<my-component foo="bar" bar="baz"></my-component>
When i use the component like this, the constructor prints: null, null
I could write a setter for foo and bar and check on every set if they are both set. But if no value is provided.. my init is never fired.
Do i have to implement a interface which provides a init method or something?
It's the same as shown here Connecting 2 controllers and have access to the first controllers propertie in the second controller
Implement the AttachAware interface. The values of the instance can't be set before the element is constructed therefore there is no chance to have the fields set from the outside when the constructor is executed.
As Günter said implement either AttacheAware or!! ShadowRootAware. onShadowRoot comes after onAttachAware. The Param it gives you (ShadowRoot) is your custom element. The name is misleading - it also works if useShadowDom is false.

Wait for dom ready without useShadowDom

I want to wait until my component is fully loaded. The current approach would be to implement the ShadowRootAware interface. However this does not work, if the component disables the use of shadow dom:
#Component(
selector: 'travel-step',
templateUrl: 'packages/TravelPlanner/travelstep/travel_step_component.html',
useShadowDom: false,
publishAs: 'cmp')
class TravelStepComponent extends AttachAware{
I need to disable the usage of ShadowDom, because I want to use styles from my parent object (e.g. Bootstrap). Is there another way to wait for the dom to be ready?
I want to reference a file upload input. At the moment (angular v.012) there seems to be no other way to upload a file.
You can implement ShadowRootAware interface. For example:
class NgFreeTree implements ShadowRootAware {
void onShadowRoot(ShadowRoot shadowRoot) { ... }
}
It should work regardless of useShadowDom attribute.
It does not give you the error message if you use the following signature:
void onShadowRoot(Node n) {
HtmlElement element = n;
...
}

Provide callback for custom component

I made a custom component which basically wraps a d3 line chart. Now I want to be able to register a callback for clicks on the lines in the chart.
I gave the component a #NgCallback parameter, which I then send events to:
class NetworkSummaryComponent implements NgShadowRootAware {
#NgCallback('callback')
Function callback;
void onShadowRoot(ShadowRoot shadowRoot) {
...
chart.callMethod('listen', ['line-click', (ev) {
var name = ev.callMethod('getLineName');
print(name);
callback({'name': name});
}]);
}
}
When using the component, I specify a function of my controller as callback:
<network-summary
...
callback="ctrl.lineClicked">
</network-summary>
However, that function is never actually called, put I know the callback arrives from the JS side because the print in the first snippet is executed.
If I instead specify the attribute as callback="ctrl.lineClicked()" I get a strange exception:
Closure call with mismatched arguments: function 'call'
I could not find any official documentation on how to properly do callbacks, so I'm not exactly sure what I'm doing wrong.. Any ideas?
It turns out that I had to explicitly name the expected arguments in the attributes:
<network-summary
...
callback="ctrl.lineClicked(name)">
</network-summary>
Hope this is useful to the next person having this problem.

Resources