MorKadosh
MorKadosh

Reputation: 6006

Angular 2 custom decorator for versions later than RC4, gives 'More than one component' excepion

I am working on my Ionic 2 component ionic2-autocomplete, which was originally developed for RC.4 and earlier versions, and now I am trying to migrate it to Angular 2 final.

As part of the original design, I let the developers overwrite the default template by using a custom decorator AutoCompleteItem which accepts template or templateUrl. The source code of this decorator could be found here.

As you can see, I used the decorator in order to preserve some of the attributes required for my component, alongside letting the users design the component for their own needs.

Now, following the exact same steps I wrote in the docs, I tried to implement a custom template by using:

import {AutoCompleteItem, AutoCompleteItemComponent} from 'ionic2-auto-complete';

@AutoCompleteItem({
  template : `<img src="build/images/flags/{{data.name}}.png" class="flag" /> <span [innerHTML]="data.name | boldprefix:keyword"></span>`,
})
export class CompTestItem extends AutoCompleteItemComponent{
}

Which worked perfectly on the earlier versions (I also added the CompTestItem to the declerations array).

But for some reason, I now encounter the following exception:

polyfills.js:3 Unhandled Promise rejection: Template parse errors: More than one component: AutoCompleteItemComponent,CompTestItem ("
[ERROR ->] ; Task: Promise.then ; Value: Error: Template parse errors:(…) Error: Template parse errors: More than one component: AutoCompleteItemComponent,CompTestItem ("
[ERROR ->]http://localhost:8101/build/main.js:19480:19) at RuntimeCompiler._compileTemplate (http://localhost:8101/build/main.js:27855:51) at http://localhost:8101/build/main.js:27777:83 at Set.forEach (native) at compile (http://localhost:8101/build/main.js:27777:47) at t.invoke (http://localhost:8101/build/polyfills.js:3:13400) at e.run (http://localhost:8101/build/polyfills.js:3:10787) at http://localhost:8101/build/polyfills.js:3:8889 at t.invokeTask (http://localhost:8101/build/polyfills.js:3:14029) at e.runTask (http://localhost:8101/build/polyfills.js:3:11389)o @ polyfills.js:3r @ polyfills.js:3i @ polyfills.js:3 polyfills.js:3 Error: Uncaught (in promise): Error: Template parse errors:(…)o @ polyfills.js:3r @ polyfills.js:3i @ polyfills.js:3

I really don't have a clue why. Could anyone have a clue why this custom decorator wont work on this version of Angular? And why does it say that I have more than one component?

Thanks!

Upvotes: 1

Views: 275

Answers (1)

Paul Samsotha
Paul Samsotha

Reputation: 209004

To explain my comments above more clearly. Look at your decorator

export function AutoCompleteItem( config: AutoCompleteItemMetadata ) {
  return function(cls: any) {
    const _reflect: any = Reflect;
    let annotations = _reflect.getMetadata('annotations', cls) || [];
    let extendedConfig: any = config;

    extendedConfig.selector = 'ion-auto-complete-item';
    annotations.push(new Component(extendedConfig));
    _reflect.defineMetadata('annotations', annotations, cls);

    return cls;
  };
}

You're setting the selector on every component to ion-auto-complete-item. So in this situation

@AutoCompleteItem({
  template: defaultTemplate
})
export class AutoCompleteItemComponent {}

@AutoCompleteItem({})
export class ExtendedComponent extends AutoCompleteComponent {}

You now have two components with the same selector. Now this alone doesn't matter. But when the two of them get into the same room together, Angular can't determine which one to use.

Now this may not need a problem in earlier RC4 because the confinement was smaller than it is now. Before you used directives which is only scoped to the component

@Component({
  directives: [ AutoCompleteItemComponent ]
})
class SomeComponent {}

It's probably rare that you would use the AutoCompleteItemComponent and ExtendedComponent in the same component; so no conflict.

But not that we use modules, the scope of the component becomes a little wider, and this leaves room for more conflict.

Maybe you have a shared module that you import into another module

@NgModule({
  imports: [ FormsModule, CommonModule ],
  declarations: [ AutoCompleteItemComponent ],
  exports: [ AutoCompleteItemComponent ]
})
class SharedModule {}

@NgModule({
  imports: [ SharedModule ],
  declarations: [ ExtendedComponent, ComponentThatUsesExendedComponent ]
  exports: [ ComponentThatUsesExtendedComponent ]
})
class OtherModule {}

In the above case, you will get the error, because now the AutoComponentItemComponent and ExtendedComponent are in the same room together. Angular can't figure out which one to use inside the ComponentThatUsesExendedComponent, so you get the error

More than one component AutoCompleteItemComponent, ExtendedComponent

At this point I'm not sure what you can do to fix this but to just make sure that the two components are never in the same room together, either explicitly or implicitly. What makes it even more difficult is that component classes can only be declared in one module.

Upvotes: 1

Related Questions