1
votes

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!

1
Finally after adding/removing and moving things around, it appears the problem is exports: [ AutoCompleteItemComponent ]. Not sure why that causes a problem but it makes sense that it wasn't a problem before modules were introduced to Angular. Solution? Not sure, since you need to export it to use it multiple modules. If you always had to extend it, then there is no need to ever declare and/or export it, but it looks like it's a component that could also be used standalonePaul Samsotha
Now that I think about it, I wanna say the real problem is that there are multiple components with the same selector, when you extend it. It seems that's what the [Error ->] is pointing to - the element - Angular can't decide which component to usePaul Samsotha
Maybe a temporary/but unstable fix would be to make sure that any module that uses an extended class doesn't also import the autocomplete module. This might avoid the conflict.Paul Samsotha
@peeskillet thanks for your replies and efforts! but to be honest, I did not really understand your points. could you please try to clarify them? thanks!MorKadosh

1 Answers

1
votes

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.