3

I am trying ionic2 beta9 and am trying to create a custom component with ion-slide, but I am getting an error.

Parent Component

<ion-content class="home" padding>    
  <ion-slides loop="true">
    <slide-item *ngFor="let image of [1,2,3,4,5]" [imgIdx]="image"></slide-item>
  </ion-slides>
</ion-content>

slide-item Custom Component

<ion-slide>
        <div class="bcontent">
            <div class="bimg">
                <img data-src="images/slide{{imgIdx}}.jpeg">
            </div>
            <p class="info">My text</p>
        </div>
</ion-slide>

slide-item.ts

@Component({
  selector: 'slide-item',
  templateUrl: 'build/components/slide-item/slide-item.html',
  directives: [Slides, Slide]
})
export class SlideItem {

  @Input()
  imgIdx: number;

  constructor() {
    console.log("SlideItem::constructor...imgIdx="+this.imgIdx);
  }
}

I am getting the following error:

zone.js:461

Unhandled Promise rejection: Template parse errors:
No provider for Slides ("
  for more info on Angular 2 Components.
-->
[ERROR ->]<ion-slide>
        <div class="bcontent">
            <div class="bimg">
"): SlideItem@6:0 

; Zone: angular ; Task: Promise.then ; Value:

BaseException {message: "Template parse errors:↵No provider for Slides ("↵ …↵            <div class="bimg">↵"): 
SlideItem@6:0", stack: "Error: Template parse errors:↵No provider for Slid…ndroid_asset/www/build/js/app.bundle.js:30622:41)"}message: 
"Template parse errors:↵No provider for Slides ("↵  for more info on Angular 2 Components.↵-->↵[ERROR ->]<ion-slide>↵        <div class="bcontent">↵            <div class="bimg">↵"): SlideItem@6:0"

stack: "Error: Template parse errors:↵No provider for Slides ("↵  for more info on Angular 2 Components.↵-->↵[ERROR ->]<ion-slide>↵        <div class="bcontent">↵            <div class="bimg">↵"): SlideItem@6:0↵    at new BaseException (file:///android_asset/www/build/js/app.bundle.js:1760:23)↵    at TemplateParser.parse (file:///android_asset/www/build/js/app.bundle.js:16401:19)↵    at file:///android_asset/www/build/js/app.bundle.js:14643:64↵    at ZoneDelegate.invoke (file:///android_asset/www/build/js/zone.js:323:29)↵    at Object.onInvoke (file:///android_asset/www/build/js/app.bundle.js:30631:41)↵    at ZoneDelegate.invoke (file:///android_asset/www/build/js/zone.js:322:35)↵    at Zone.run (file:///android_asset/www/build/js/zone.js:216:44)↵    at file:///android_asset/www/build/js/zone.js:571:58↵    at ZoneDelegate.invokeTask (file:///android_asset/www/build/js/zone.js:356:38)↵    at Object.onInvokeTask (file:///android_asset/www/build/js/app.bundle.js:30622:41)"__proto__: ErrorconsoleError @ zone.js:461_loop_1 @ zone.js:490drainMicroTaskQueue @ zone.js:494ZoneTask.invoke @ zone.js:426
vikram
  • 135
  • 2
  • 10
  • Did you get anywhere with this? I added `providers: [Slides]` in the `Slide.decorators` in **ionic-angular/components/slides/slides.js** and the error goes away. The slides are not rendered correctly, though, so I'm still working on it. – jmilloy Dec 20 '16 at 16:31

1 Answers1

0

Why it doesn't work

I don't think it is possible with the current design of the Slide and Slides components. An ion-slide must be a child of an ion-slides element and it must be able to find its parent Slides within the same template.

Here's the source:

@Component({
  selector: 'ion-slide',
  template: '<div class="slide-zoom"><ng-content></ng-content></div>',
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
})
export class Slide {

  ele: HTMLElement;

  @Input() zoom: any;

  constructor(
    elementRef: ElementRef,
    @Host() public slides: Slides
  ) {
    this.ele = elementRef.nativeElement;
    this.ele.classList.add('swiper-slide');

    slides.rapidUpdate();
  }

  ngOnDestroy() {
    this.slides.rapidUpdate();
  }
}

You can see that the parent Slides is required so that a Slide can trigger the Slides to update when one is dynamically added or destroyed. It is restricted to the current template with the @Host decorator.

You get a template error when you try to use ion-slide in your custom template because there is no Slides object within the template to be passed to the Slide.

One Partial-Solution

I played around with this and you can get it to work like this:

  1. Remove the reference slides to the parent Slides object, sacrificing the ability to automatically update the Slides when you dynamically add/remove slides
  2. Add the "slide-zoom" class to your custom slide.

The modified source for slides.ts:

@Component({
  selector: 'ion-slide',
  template: '<div class="slide-zoom"><ng-content></ng-content></div>',
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
})
export class Slide {

  ele: HTMLElement;

  @Input() zoom: any;

  constructor(
    elementRef: ElementRef
  ) {
    this.ele = elementRef.nativeElement;
    this.ele.classList.add('swiper-slide');
  }
}

And your slide item:

@Component({
  selector: 'slide-item',
  templateUrl: 'slide-item.html',
})
export class SlideItem {

  @Input()
  imgIdx: number;

  constructor(elementRef: ElementRef) {
    console.log("SlideItem::constructor...imgIdx="+this.imgIdx);
    elementRef.nativeElement.classList.add('swiper-slide');
  }
}

I say that this is a partial solution because you have to modify the ionic source and because now you have to manually call update the Slides update when you add/remove slides.

I did make some progress further modifying the ionic source to maintain the automatic update on add/remove slides including custom slide containers such as yours. But it just wasn't looking worth it compared to the option(s) below.

Partial Solution 2

The code for a Slide is really quite simple. You're best bet is probably just to define your own component directly (rather than wrapping ion-slide):

@Component({
  selector: 'slide-item',
  templateURL: 'slite-item.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
})
export class SlideItem {

  ele: HTMLElement;

  @Input() zoom: any;
  @Input() imgIdx: number;

  constructor(
    elementRef: ElementRef
  ) {
    this.ele = elementRef.nativeElement;
    this.ele.classList.add('swiper-slide');

    console.log("SlideItem::constructor...imgIdx="+this.imgIdx)
  }
}

and template

<div class="slide-zoom bcontent">
  <div class="bimg">
    <img data-src="images/slide{{imgIdx}}.jpeg">
  </div>
  <p class="info">My text</p>
</div>

I'm calling this one a partial solution also because you have to maintain some extra source that mirrors Ionic. If the ionic slides implementation changes, you may have to update your custom slide.

Improved Solution

Here is a SO answer about extending a component. I haven't gotten this to work because as of now it isn't up to date with Angular (ComponentMetaData no longer exists), but I think it is a nicer approach. You will use this decorator to import and extend the Slide component.

export function CustomComponent(annotation: any) {
  return function (target: Function) {
    var parentTarget = Object.getPrototypeOf(target.prototype).constructor;
    var parentAnnotations = Reflect.getMetadata('annotations', parentTarget);

    var parentAnnotation = parentAnnotations[0];
    Object.keys(parentAnnotation).forEach(key => {
      if (isPresent(parentAnnotation[key])) {
        annotation[key] = parentAnnotation[key];
      }
    });
    var metadata = new ComponentMetadata(annotation);

    Reflect.defineMetadata('annotations', [ metadata ], target);
  }
}

Your template will be the same as the previous solution, but the class is simplified to something like the following.

import { Slide } from 'ionic-angular';

@CustomComponent({
  selector: 'slide-item',
  templateURL: 'slite-item.html'
})
export class SlideItem extends Slide {
  @Input() imgIdx: number;
}
Community
  • 1
  • 1
jmilloy
  • 7,875
  • 11
  • 53
  • 86