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
- Add a controller to the AngularJS directive so bindToController bindings have a destination.
- Remove 'bindToController' and put binding definitions back on 'scope' if no controller is intended.
- 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
- Adopt AngularJS 1.5 component() definitions before upgrading; they always pair bindings with a controller.
- Lint directives for bindToController without controller.
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
- Binding definitions on scope and controller at the same time
- Unexpected mapping '${bindingType}' in '${json}' in '${name}
- Upgraded directive '${name}' contains unsupported feature: '
- Tried too often to get the ending mark: ${loopCount}
- Invalid URL (${url}) with base (${base})
AI-assisted analysis of angular/angular@51cb07e980 (2026-08-22).
Data as JSON: /api/errors/b65fa35f99160de2.
Report an issue: GitHub.