angular/angular · error · Error

Upgraded directive '${this.directive.name}' specifies 'bindT

Error message

Upgraded directive '${this.directive.name}' specifies 'bindToController' but no controller.

What it means

Thrown by UpgradeComponent.ngOnInit() (upgrade_component.ts:145) when the upgraded AngularJS directive sets 'bindToController' (truthy) but defines no 'controller'. Bindings declared with bindToController must land on a controller instance; with no controller there is nowhere to put them, so the upgrader refuses to proceed.

Source

Thrown at packages/upgrade/static/src/upgrade_component.ts:145

  ngOnInit() {
    // Collect contents, insert and compile template
    const attachChildNodes: ɵangular1.ILinkFn | undefined = this.helper.prepareTransclusion();
    const linkFn = this.helper.compileTemplate();

    // Instantiate controller
    const controllerType = this.directive.controller;
    const bindToController = this.directive.bindToController;
    let controllerInstance = controllerType
      ? this.helper.buildController(controllerType, this.$componentScope)
      : undefined;
    let bindingDestination: ɵupgradeHelper.IBindingDestination;

    if (!bindToController) {
      bindingDestination = this.$componentScope;
    } else if (controllerType && controllerInstance) {
      bindingDestination = controllerInstance;
    } else {
      throw new Error(
        `Upgraded directive '${this.directive.name}' specifies 'bindToController' but no controller.`,
      );
    }
    this.controllerInstance = controllerInstance;
    this.bindingDestination = bindingDestination;

    // Set up outputs
    this.bindOutputs(bindingDestination);

    // Require other controllers
    const requiredControllers = this.helper.resolveAndBindRequiredControllers(controllerInstance);

    // Hook: $onChanges
    if (this.pendingChanges) {
      this.forwardChanges(this.pendingChanges, bindingDestination);
      this.pendingChanges = null;
    }

View on GitHub (pinned to 51cb07e980)

Solutions

  1. Add a controller to the AngularJS directive so bindToController bindings have a destination.
  2. Remove 'bindToController' and put binding definitions back on 'scope' if no controller is intended.
  3. Prefer migrating the directive to a true AngularJS 1.5 'component()' definition (which enforces controller presence) before upgrading.

Example fix

// before
angular.module('h').directive('heroDetail', () => ({
  bindToController: true,
  scope: {hero: '='},
  template: '<span>{{ctrl.hero.name}}</span>',
}));

// after
angular.module('h').directive('heroDetail', () => ({
  bindToController: true,
  controller: class { hero: any; },
  controllerAs: 'ctrl',
  scope: {hero: '='},
  template: '<span>{{ctrl.hero.name}}</span>',
}));
Defensive patterns

Strategy: type-guard

Validate before calling

// Screen directives before wrapping them
const ddo = $injector.get('heroDetailDirective')[0];
if (ddo.bindToController && !ddo.controller) { throw new Error('Directive needs a controller for bindToController.'); }

Type guard

function hasControllerForBindings(d: any): boolean { return !d.bindToController || !!d.controller; }

Prevention

When it happens

Trigger: Wrapping with UpgradeComponent an AngularJS directive that has bindToController: true (or a bindings object) but no controller property; also when the controller is falsy (e.g. undefined due to DDO manipulation).

Common situations: Upgrading component-style directives written for AngularJS 1.5+ where the author moved bindings to bindToController but the controller was accidentally removed, renamed, or conditionally defined.

Related errors


AI-assisted analysis of angular/angular@51cb07e980 (2026-08-22). Data as JSON: /api/errors/b65fa35f99160de2. Report an issue: GitHub.