Search code examples
dartdart-polymer

When is shadowRoot available to a polymer component?


I'm having trouble understanding when access to the shadowRoot of a component is available. Here is an image of a set of nested components:

enter image description here

So there are a handful of components:

  1. MortgageDetails: Component binding together a group of 3 other components
  2. MoneyInput - Input component with Amount label
  3. NumWithUnitsInput - Input component with Term label
  4. RateInput - Input component with Rate label
  5. PaymentSchedule: Component with corresponding label
  6. DateInput: Component with Start Date label

I've instrumented the created and attached methods with log message printing the shadowRoot ( sr => ...) and I get the following:

mortgageDetails [FINE]: MortgageDetails created sr => null (:1)
moneyInput [FINE]:  MoneyInput created sr => null (:1)
numWithUnitsInput [FINE]:   NumWithUnitsInput created sr => null (:1)
rateInput [FINE]:   RateInput created sr => null (:1)
paymentSchedule [FINE]: PaymentSchedule created sr => null (:1)
dateInput [FINE]:   DateInput created sr => null (:1)
mortgageDetails [FINE]: MortgageDetails attached with sr => Instance of 'ShadowRoot' (:1)

The logging makes sense. Components are created in a reasonable order and then the attaching starts. The problem is though, the mortgage details is attached before its contained moneyInput is attached. If I add one more log statement in the MortgageDetails.attached I can see that its contained MoneyInput object has a shadowRoot:

mortgageDetails [FINE]: Composed moneyInput sr => Instance of 'ShadowRoot' (:1)

This is a problem for the way I'm doing things. I need some initialization event in the MoneyInput component to reach into the shadowRoot and attach some handlers. I can't use created because shadowRoot is not even set yet. I'm trying to use attached. I have code like this currently in the attach of MoneyInput:

  _amountElement = shadowRoot.querySelector('#money-amount')
    ..onBlur.listen((evt) => reformatAmount())
    ..onFocus.listen((evt) => reformatAmount())
    ..onKeyUp.listen((evt) { if(evt.which == 13) reformatAmount(); });

Since MortgageDetails is being attached and MoneyInput has not yet been attached, an instance of MortgageDetails can not use the contained MoneyInput as it would like as it is not fully initialized. For example, in MortgageDetails activate I have:

(mortgageAmountInput = $["mortgage-amount"] as MoneyInput)
  ..label = r" $ Amount of Loan"
  ..onBlur.listen((_) => recalc())
  ..onFocus.listen((_) => recalc());

This fails because MoneyInput's activate has not been called. I think what I really need is an event that says the shadowRoot has been set, then on that event I could do my initialization.

What am I missing on the lifecycle of polymer elements?


Solution

  • Typically, if you're extending PolymerElement the order of the main callbacks will be ready, created, attached. These happen top-down: they're called on ancestors before descendants. It might confusing that ready is called first, but it's because PolymerElement.created calls it after setting up the DOM and even handlers, and because of constructor ordering that happens before your classes created constructor.

    Polymer also adds a domReady method that you can override which is called when your element's children are guaranteed to have been created. That might be what you need.

    See http://www.polymer-project.org/docs/polymer/polymer.html#lifecyclemethods for details on the lifecycle methods.

    First I would see if you can sidestep the ordering issue with data-binding and declarative events, though.