AUR0504

Error Message

AUR0504: Synthetic view at <ControllerName> is being activated with null/undefined scope.

Where <ControllerName> is the name of the controller for the synthetic view.

Description

This error occurs when Aurelia attempts to activate a "synthetic" view controller, but the necessary binding scope is missing (null or undefined). Synthetic views are typically views created dynamically or manually (not through standard template compilation and custom element definition), and they require an explicit scope to be provided during activation because they don't automatically inherit scope in the same way standard components do.

Cause

  1. Manual View Creation/Activation: You might be manually creating a view using IViewFactory and then attempting to activate its controller without providing a valid scope object during the controller.activate() call.

  2. Dynamic Composition Issues: Custom logic involving dynamic composition (e.g., using low-level rendering APIs) might fail to propagate or provide the required scope to a dynamically created view before activating it.

  3. Incorrect API Usage: Using internal or advanced Aurelia APIs related to view/controller creation without fully understanding the requirement to supply a scope for synthetic views.

Solution

  1. Provide Scope During Activation: When manually activating a synthetic view controller, ensure you pass a valid IScope object as part of the activation context. The scope typically includes the binding context (often the parent view model or a specific data object) and any necessary override contexts.

  2. Use Standard Composition: Prefer using standard Aurelia mechanisms for composition like <au-compose> or template controllers (if.bind, repeat.for, etc.) whenever possible, as they handle scope propagation automatically.

  3. Review Dynamic Logic: If manual creation is necessary, carefully review the code that creates and activates the synthetic view. Ensure a scope, derived correctly from the parent or context, is created and passed during activation. Use Scope.create(bindingContext, parentScope | null) or Scope.fromParent(parentScope, bindingContext) to create appropriate scopes.

Example

import {
  IViewFactory,
  Scope,
  ICustomElementController,
  ISyntheticView, // Represents the controller for a synthetic view
  Controller // Low-level API
} from 'aurelia'; // Adjust imports as needed

export class ManualCompositionViewModel {
  public viewFactory!: IViewFactory; // Assume this is injected or created
  public parentController!: ICustomElementController<this>; // Assume available
  public dynamicView: ISyntheticView | null = null;

  async createAndShowView() {
    // Create the view
    this.dynamicView = this.viewFactory.create();

    // --- Incorrect: Activating without providing scope ---
    try {
      // This might throw AUR0504 because synthetic views need an explicit scope
      await this.dynamicView.activate(this.parentController);
    } catch (e) {
      console.error("Activation failed (potentially AUR0504):", e);
    }
    // --------------------------------------------------

    // --- Correct: Creating and providing scope ---
    // Define the data context for the new view
    const bindingContext = { message: 'Hello from dynamic view!' };
    // Create a scope for the view, linking it to the parent scope
    const scope = Scope.fromParent(this.parentController.scope, bindingContext);

    try {
      // Activate with the created scope
      await this.dynamicView.activate(this.parentController, null, { scope: scope });
      // Attach the view to the DOM (simplified)
      this.dynamicView.appendTo(document.body /* or some target element */);
    } catch (e) {
      console.error("Corrected activation failed:", e);
    }
    // ---------------------------------------------
  }

  async createAndShowViewWithLowLevelController() {
    // --- Low-level Controller example ---
    const host = document.createElement('div');
    const definition = Controller.getDefinition(class MyData {}); // Get definition if needed
    const lowLevelController = Controller.forSyntheticView(
      this.parentController.container.get(IViewFactory), // Usually need factory
      definition,
      this.parentController.container
    );

    // --- Incorrect: Activating without scope ---
    try {
      await lowLevelController.activate(this.parentController, host); // Missing scope
    } catch(e) {
      console.error("Low-level activation failed (potentially AUR0504):", e);
    }
    // --- Correct: Activating with scope ---
    const bindingContext = { info: 'Low level view data' };
    const scope = Scope.fromParent(this.parentController.scope, bindingContext);
    try {
      await lowLevelController.activate(this.parentController, host, { scope }); // Provide scope
      console.log("Low-level activation successful.");
    } catch(e) {
      console.error("Corrected low-level activation failed:", e);
    }
  }
}

class MyData {} // Example class for definition

Debugging Tips

  • Identify the component or code location where the synthetic view is being created and activated.

  • Examine the parameters being passed to the controller.activate() method for the synthetic view. Is a scope object being included?

  • Inspect the value of the scope being passed. Is it correctly created using Scope.create or Scope.fromParent? Does it contain the expected binding context?

  • Set breakpoints in the Controller#activate method (around line 543 in templating/controller.ts) to inspect the controller type (isCustomElement, isSynthetic), the provided activationContext, and the resulting scope.

  • Consider if standard Aurelia composition features can achieve the same result more simply and robustly, avoiding the need for manual synthetic view management.

Last updated

Was this helpful?