How-To

Set the nested Accordion

Accordion supports to render nested level of Accordion by using content property. You can give nested Accordion content inside the parent Accordion content property by using id of nested element. The nested Accordion can be rendered with the use of provided events, such as clicked and expanding.

Source
Preview
app.component.ts
app.module.ts
main.ts
import { Component, ViewChild } from '@angular/core';
import { AccordionComponent } from '@syncfusion/ej2-angular-navigations';
import { ExpandEventArgs, Accordion, AccordionClickArgs} from '@syncfusion/ej2-navigations';

@Component({
    selector: 'app-container',
    template: `
     <ejs-accordion #element (expanding)="expanding($event)">
        <e-accordionitems>
          <e-accordionitem expanded='true'>
            <ng-template #header>
              <div>Video</div>
            </ng-template>
            <ng-template #content>
              <div id="nested_video"></div>
            </ng-template>
          </e-accordionitem>
          <e-accordionitem>
            <ng-template #header>
              <div>Music</div>
            </ng-template>
            <ng-template #content>
             <div id="nested_music"></div>
            </ng-template>
          </e-accordionitem>
          <e-accordionitem>
            <ng-template #header>
              <div>Images</div>
            </ng-template>
            <ng-template #content>
             <div id="nested_images"></div>
            </ng-template>
          </e-accordionitem>
        </e-accordionitems>
    </ejs-accordion>
        `
})

export class AppComponent {
    @ViewChild('element') acrdnInstance: AccordionComponent;
    public clicked(e: AccordionClickArgs) {
     let ele: HTMLElement = e.originalEvent.target;
     if (ele.querySelectorAll('.e-accordion').length > 0) {
      return;
     }
     let nestAcrdn_musNew: Accordion = new Accordion({
     items: [
      { header: 'New Track1' },
      { header: 'New Track2' }
     ]
    }, '#nested_musicNew');
    }
    public expanding(e: ExpandEventArgs) {
    if (e.isExpanded && [].indexOf.call(this.acrdnInstance.items, e.item) === 0) {
      if (e.element.querySelectorAll('.e-accordion').length > 0) {
        return;
      }
    let nestAcrdn_vid: Accordion = new Accordion({
    items: [
      { header: 'Video Track1' },
      { header: 'Video Track2' }
    ]
    }, '#nested_video');
    } else if (e.isExpanded && [].indexOf.call(this.acrdnInstance.items, e.item) === 1) {
    if (e.element.querySelectorAll('.e-accordion').length > 0) {
      return;
    }
    let nestAcrdn_mus:Accordion = new Accordion({
      clicked: this.clicked,
      items: [
        { header: 'Music Track1' },
        { header: 'Music Track2' },
        { header: 'Music New', content: '<div id="nested_musicNew"></div>' }
      ]
    }, '#nested_music');
    } else if (e.isExpanded && [].indexOf.call(this.acrdnInstance.items, e.item) === 2) {
    if (e.element.querySelectorAll('.e-accordion').length > 0) {
      return;
    }
    let nestAcrdn_img: Accordion = new Accordion({
      items: [
        { header: 'Track1' },
        { header: 'Track2' },
      ]
    }, '#nested_images');
    }
    }
    ngAfterViewInit() {
    }
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AccordionModule } from '@syncfusion/ej2-angular-navigations';
import { AppComponent } from './app.component';

/**
 * Module
 */
@NgModule({
    imports: [
        BrowserModule, AccordionModule
    ],
    declarations: [AppComponent],
    bootstrap: [AppComponent]
})
export class AppModule { }
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { AppModule } from './app.module';

enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);

Load content through Ajax

Accordion supports to load external contents through AJAX library. Refer the below steps.

  • Import the Ajax module from ej2-base and initialize with URL path.

  • Get data from the Ajax Success event to initialize Accordion with retrieved external path data.

Source
Preview
app.component.ts
app.module.ts
main.ts
ajax.html
import { Component, ViewChild } from '@angular/core';
import { Ajax } from '@syncfusion/ej2-base';
import { AccordionComponent} from '@syncfusion/ej2-angular-navigations';

@Component({
    selector: 'app-container',
    template: `
    <div id="acrdnContnet1" style="display:none">
        <ul style="margin : 0px;padding:0px 16px; list-style-type: none">
          <li>Testing</li>
          <li>Development</li>
        </ul>
    </div>
    <div id="acrdnContnet2" style="display:none">
      <ul style="margin : 0px;padding:0px 16px; list-style-type: none">
        <li>Mobile</li>
        <li>Web</li>
      </ul>
    </div>
    <ejs-accordion #acrdnInstance>
      <e-accordionitems>
        <e-accordionitem header='Department' content = '#acrdnContnet1'></e-accordionitem>
        <e-accordionitem header='Platform' content = '#acrdnContnet2'></e-accordionitem>
        <e-accordionitem header='Employee Details'></e-accordionitem>
      </e-accordionitems>
    </ejs-accordion>
        `
})

export class AppComponent {
    @ViewChild('acrdnInstance') acrdnInstance: AccordionComponent;
    public contentData: string;

    ngOnInit() {
    let ajax: Ajax = new Ajax('./ajax.html', 'GET', true);
    ajax.send().then();
    ajax.onSuccess = (data: string): void => {
       this.acrdnInstance.items[2].content = data;
       this.acrdnInstance.refresh();
    };
    }
}}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AccordionModule } from '@syncfusion/ej2-angular-navigations';
import { AppComponent } from './app.component';

/**
 * Module
 */
@NgModule({
    imports: [
        BrowserModule, AccordionModule
    ],
    declarations: [AppComponent ],
    bootstrap: [AppComponent]
})
export class AppModule { }
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { AppModule } from './app.module';

enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);
<div>
    <div class="comments-list">
        <div class="cont-list">
            <img src="http://js.syncfusion.com/demos/web/content/images/Employee/8.png" alt="contact" />
            <br>
            <div>
                <div class="time-panel">1 hr</div>
                <b class="headername">Erik Linden</b></div><br><br> London, one of the most popular tourist 
            destinations in the world for a reason. A cultural and historical hub, London has an excellent public 
            transportation system that allows visitors to see all the fantastic sights without spending a ton 
            of money on a rental car. London contains four World Heritage Sites.
            <div class="comments">
                <div class="c-list">Retweet</div>
                <div class="c-list">Reply</div>
                <div class="c-list">Share</div>
            </div>
        </div>       
    </div>
</div>
<style>
    .cont-list img {
        border-radius: 10px;
        float: left;
        height: 56px;
    }
    
    .comments {
        padding: 10px;
        color: #074B92;
        font-weight: 600;
        position: relative;
        top: 6px;
    }
    
    .cont-list {        
        padding-bottom: 9px;
    }
    
    .cont-list:last-child {
        border-bottom: none;
        padding-bottom: 10px;
    }
    
    .time-panel {
        float: right;
        color: #2382C3;
        margin-right: 10px;
        margin-top: 7px;
    }
    
    .headername {
        font-size: 16px;
        font-weight: 600;
        color: #074B92;
        position: relative;
        left: 9px;
    }
    
    .c-list {
        float: right;
        margin-top: -11px;
        padding-right: 12px;
    }
</style>

Set custom animation

Accordion supports custom animations for both expand and collapse actions from the provided animation option of Animation library.

Default animation is given as SlideDown for expanding the panel and SlideUp for collapsing the panel. You can also disable the animation by setting animation effect as none.

The sample demonstrates some types of animation that suits for Accordion. You can check all the animation effects here.

Source
Preview
app.component.ts
app.module.ts
main.ts
import { Component, ViewChild } from '@angular/core';
import { DropDownListComponent } from '@syncfusion/ej2-angular-dropdowns';
import { AccordionComponent} from '@syncfusion/ej2-angular-navigations';

@Component({
    selector: 'app-container',
    template: `
    <div style='padding-top: 25px'>
    <div class='row'>
    <div class="col-xs-6 col-sm-6 col-lg-6 col-md-6">
      <label> Expand Animation </label></div>
      <div class="col-xs-6 col-sm-6 col-lg-6 col-md-6">
      <div class='custom_drop'><ejs-dropdownlist #expandAnimation (change)='expandAnimationChange()' index='0' [dataSource]='expandAni' placeholder='Expand Animation'></ejs-dropdownlist></div>
    </div></div>
    <div class='row'>
    <div class="col-xs-6 col-sm-6 col-lg-6 col-md-6">
    <label> Collapse Animation </label></div>
    <div class="col-xs-6 col-sm-6 col-lg-6 col-md-6">
    <div class='custom_drop'><ejs-dropdownlist #collapseAnimation (change)='collapseAnimationChange()' index='1' [dataSource]='expandAni' placeholder='Collapse Animation'></ejs-dropdownlist></div>
    </div></div>
    <div style='padding-top: 25px'>
 <ejs-accordion #acrdnInstance>
        <e-accordionitems>
          <e-accordionitem expanded='true'>
            <ng-template #header>
              <div>ASP.NET</div>
            </ng-template>
            <ng-template #content>
              <div>Microsoft ASP.NET is a set of technologies in the Microsoft .NET Framework for building Web applications and XML Web
                services. ASP.NET pages execute on the server and generate markup such as HTML, WML, or XML that is sent to a desktop
                or mobile browser. ASP.NET pages use a compiled,event-driven programming model that improves performance and enables
                the separation of application logic and user interface.</div>
            </ng-template>
          </e-accordionitem>
          <e-accordionitem>
            <ng-template #header>
              <div>ASP.NET MVC</div>
            </ng-template>
            <ng-template #content>
              <div>The Model-View-Controller (MVC) architectural pattern separates an application into three main components: the model,
                the view, and the controller. The ASP.NET MVC framework provides an alternative to the ASP.NET Web Forms pattern for
                creating Web applications. The ASP.NET MVC framework is a lightweight, highly testable presentation framework that
                (as with Web Forms-based applications) is integrated with existing ASP.NET features, such as master pages and membership-based
                authentication.
              </div>
            </ng-template>
          </e-accordionitem>
          <e-accordionitem>
            <ng-template #header>
              <div>JavaScript</div>
            </ng-template>
            <ng-template #content>
              <div>JavaScript (JS) is an interpreted computer programming language.It was originally implemented as part of web browsers
                so that client-side scripts could interact with the user, control the browser, communicate asynchronously, and alter
                the document content that was displayed.More recently, however, it has become common in both game development and
                the creation of desktop applications.</div>
            </ng-template>
          </e-accordionitem>
        </e-accordionitems>
    </ejs-accordion>
    </div></div>
        `
})

export class AppComponent {
    @ViewChild('acrdnInstance') acrdnInstance: AccordionComponent;
    @ViewChild('expandAnimation') expandInstance: DropDownListComponent;
    @ViewChild('collapseAnimation') collapseInstance: DropDownListComponent;
    public expandAni: string[] = ['SlideDown', 'SlideUp', 'FadeIn', 'FadeOut', 'FadeZoomIn', 'FadeZoomOut', 'ZoomIn', 'ZoomOut', 'None'];
    ngAfterViewInit() {
    }
    public expandAnimationChange(): void {
       this.acrdnInstance.animation.expand.effect = this.expandInstance.value;
    }
    public collapseAnimationChange(): void {
      this.acrdnInstance.animation.collapse.effect = this.collapseInstance.value;
    }
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AccordionModule } from '@syncfusion/ej2-angular-navigations';
import { DropDownListModule } from '@syncfusion/ej2-angular-dropdowns';
import { AppComponent } from './app.component';

/**
 * Module
 */
@NgModule({
    imports: [
        BrowserModule, AccordionModule, DropDownListModule 
    ],
    declarations: [AppComponent, AccordionModule],
    bootstrap: [AppComponent]
})
export class AppModule { }
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { AppModule } from './app.module';

enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);

To keep single pane open always

By default, all Accordion panels are collapsible. You can customize the Accordion to keep one panel as expanded state always. This is applicable for Single expand mode.

Source
Preview
app.component.ts
app.module.ts
main.ts
import { Component, ViewChild } from '@angular/core';
import {ExpandEventArgs, AccordionClickArgs} from '@syncfusion/ej2-navigations';

@Component({
    selector: 'app-container',
    template: `
    <ejs-accordion #element expandMode='Single' (expanding)="beforeExpand($event)" (clicked)="clicked($event)">
        <e-accordionitems>
          <e-accordionitem expanded='true'>
            <ng-template #header>
              <div>ASP.NET</div>
            </ng-template>
            <ng-template #content>
              <div>Microsoft ASP.NET is a set of technologies in the Microsoft .NET Framework for building Web applications and XML Web
                services. ASP.NET pages execute on the server and generate markup such as HTML, WML, or XML that is sent to a desktop
                or mobile browser. ASP.NET pages use a compiled,event-driven programming model that improves performance and enables
                the separation of application logic and user interface.</div>
            </ng-template>
          </e-accordionitem>
          <e-accordionitem>
            <ng-template #header>
              <div>ASP.NET MVC</div>
            </ng-template>
            <ng-template #content>
              <div>The Model-View-Controller (MVC) architectural pattern separates an application into three main components: the model,
                the view, and the controller. The ASP.NET MVC framework provides an alternative to the ASP.NET Web Forms pattern for
                creating Web applications. The ASP.NET MVC framework is a lightweight, highly testable presentation framework that
                (as with Web Forms-based applications) is integrated with existing ASP.NET features, such as master pages and membership-based
                authentication.
              </div>
            </ng-template>
          </e-accordionitem>
          <e-accordionitem>
            <ng-template #header>
              <div>JavaScript</div>
            </ng-template>
            <ng-template #content>
              <div>JavaScript (JS) is an interpreted computer programming language.It was originally implemented as part of web browsers
                so that client-side scripts could interact with the user, control the browser, communicate asynchronously, and alter
                the document content that was displayed.More recently, however, it has become common in both game development and
                the creation of desktop applications.</div>
            </ng-template>
          </e-accordionitem>
        </e-accordionitems>
    </ejs-accordion>
        `
})

export class AppComponent {
    @ViewChild('element') acrdnInstance: AccordionComponent;
    public clickEle: HTMLElement;
    public clicked(e: AccordionClickArgs) {
      this.clickEle = e.originalEvent.target;
    }
   public beforeExpand (e: ExpandEventArgs):void {
  let expandCount: number = this.acrdnInstance.element.querySelectorAll('.e-selected').length;
  let ele: HTMLElement= this.acrdnInstance.element.querySelectorAll('.e-selected')[0];
  if (ele) {
    ele = ele.firstChild
  }
    if (expandCount === 1 && ele === this.clickEle) {
      e.cancel = true;
  }
}
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AccordionModule } from '@syncfusion/ej2-angular-navigations';
import { AppComponent } from './app.component';

/**
 * Module
 */
@NgModule({
    imports: [
        BrowserModule, AccordionModule
    ],
    declarations: [AppComponent],
    bootstrap: [AppComponent]
})
export class AppModule { }
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { AppModule } from './app.module';

enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);

Create wizard using Accordion

Accordion items can be disabled dynamically by passing the index and boolean value with the enableItem method.

The below Wizard sample is designed for Online Shopping model. In this, each Accordion item is integrated with required components to fill the details and designed for getting user details and making payment at the end. Each field is provided with validation for all mandatory option to enable/disable to next Accordion.

Source
Preview
app.component.ts
app.module.ts
main.ts
app.component.html
index.css
import { Component, ViewChild, OnInit } from '@angular/core';
import { enableRipple, isNullOrUndefined as isNOU } from '@syncfusion/ej2-base';
import { DialogComponent } from '@syncfusion/ej2-angular-popups';
import { DatePickerComponent } from '@syncfusion/ej2-calendars';
import { NumericTextBoxComponent } from '@syncfusion/ej2-angular-inputs';
import { AccordionComponent, AccordionItemsDirective, AccordionItemDirective } from '@syncfusion/ej2-angular-navigations';

enableRipple(true);

@Component({
  selector: 'app-container',
  templateUrl: 'app/app.component.html'
})

export class AppComponent implements OnInit {
  @ViewChild('alertDlg') alertDlg: DialogComponent;
  @ViewChild('accordion') accordion: AccordionComponent;
  @ViewChild('mobile') mobile: NumericTextBoxComponent;
  @ViewChild('cardNo') cardNo: NumericTextBoxComponent;
  @ViewChild('date') expiry: DatePickerComponent;
  @ViewChild('cvv') cvv: NumericTextBoxComponent;

  public dlgTarget: HTMLElement;
  public dlgButtons: Object[];
  public success: string = 'Your payment successfully processed';
  public email_alert: string = 'Enter valid email address';
  public mobile_alert: string = 'Mobile number length should be 10';
  public card_alert: string = 'Card number length should be 16';
  public cvv_alert: string = 'CVV number length should be 3';

  public ngOnInit(): void {
    this.dlgTarget = document.body;
    this.dlgButtons = [{
      buttonModel: { content: 'Ok', isPrimary: true },
        click: (() => {
          this.alertDlg.hide();
          if (this.accordion.expandedItems[0] === 2 && this.alertDlg.content === this.success) {
            this.accordion.enableItem(0, true);
            this.accordion.enableItem(1, false);
            this.accordion.enableItem(2, false);
            this.accordion.expandItem(true, 0);
          }
        })
      }];
  }

  public dlgCreated(): void {
    this.alertDlg.hide();
  }

  public acrdnCreated(): void {
   this.accordion.enableItem(1, false);
   this.accordion.enableItem(2, false);
  }

  public btnClick(e: any): void {
    switch (e.target.id) {
      case 'Continue_Btn':
        let email: string = document.getElementById('email');
        let password: string = document.getElementById('password');
        if(email.value !== '' && password.value !== '') {
          if(this.checkMail(email.value)) {
            email.value = password.value = '';
            this.accordion.enableItem(1, true);
            this.accordion.enableItem(0, false);
            this.accordion.expandItem(true, 1);
          }
          document.getElementById('err1').classList.remove('show');
        } else {
          document.getElementById('err1').classList.add('show');
        }
        break;
      case 'Continue_BtnAdr':
        let name: string = document.getElementById('name');
        let address: string = document.getElementById('address');
        if((name.value !== '') && (address.value !== '') && (!isNOU(this.mobile.value))) {
          if(this.checkMobile(this.mobile.value)) {
            this.accordion.enableItem(2, true);
            this.accordion.enableItem(1, false);
            this.accordion.expandItem(true, 2);
          }
          document.getElementById('err2').classList.remove('show');
        } else {
          document.getElementById('err2').classList.add('show');
        }
        break;
      case 'Back_Btn':
        this.accordion.enableItem(1, true);
        this.accordion.enableItem(2, false);
        this.accordion.expandItem(true, 1);
        break;
      case 'Save_Btn':
        let cardHolder: string =document.getElementById('cardHolder');
        if(!isNOU(this.cardNo.value) && (cardHolder.value !== '') && (!isNOU(this.expiry.value)) && !isNOU(this.cvv.value)) {
          if (this.checkCardNo(this.cardNo.value)) {
            if (this.checkCVV(this.cvv.value)) {
              this.alertDlg.content = this.success;
              this.alertDlg.show();
            }
          }
          document.getElementById('err3').classList.remove('show');
        } else {
          document.getElementById('err3').classList.add('show');
        }
        break;
    }
  }

  public checkMail(mail: string): void {
    if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(mail)) {
      return true;
    } else {
      this.alertDlg.content = this.email_alert;
      this.alertDlg.show();
      return false;
    }
  }

  public checkMobile(mobile: number): void {
    if (/^\d{10}$/.test(mobile)) {
      return true;
    } else {
      this.alertDlg.content = this.mobile_alert;
      this.alertDlg.show();
      return false;
    }
  }

  public checkCardNo(cardNo: number): void {
    if (/^\d{16}$/.test(cardNo)) {
      return true;
    } else {
      this.alertDlg.content = this.card_alert;
      this.alertDlg.show();
      return false;
    }
  }

  public checkCVV(cvv: number): void {
    if (/^\d{3}$/.test(cvv)) {
      return true;
    } else {
      this.alertDlg.content = this.cvv_alert;
      this.alertDlg.show();
      return false;
    }
  }
}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

import { DialogAllModule } from '@syncfusion/ej2-angular-popups';
import { DatePickerAllModule } from '@syncfusion/ej2-angular-calendars';
import { NumericTextBoxAllModule } from '@syncfusion/ej2-angular-inputs';
import { AccordionModule } from '@syncfusion/ej2-angular-navigations';

import { AppComponent } from './app.component';

/**
 * Module
 */
@NgModule({
    imports: [
        BrowserModule,
        DialogAllModule,
        AccordionModule,
        DatePickerAllModule,
        NumericTextBoxAllModule
    ],
    declarations: [AppComponent],
    bootstrap: [AppComponent]
})
export class AppModule { }
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { AppModule } from './app.module';

enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);
<div id="Sign_In_Form" style="display:none; padding: 3px 0">
    <form id="formId">
        <div class="form-group">
            <div class="e-float-input">
                <input type="text" id="email" name="Email" required="" />
                <span class="e-float-line"></span>
                <label class="e-float-text" for="email">Email</label>
            </div>
            <div class="e-float-input">
                <input id="password" type="password" name="Password" required="" />
                <span class="e-float-line"></span>
                <label class="e-float-text" for="password">Password</label>
            </div>
        </div>
    </form>
    <div style="text-align: center">
        <button class='e-btn' id="Continue_Btn" (click)='btnClick($event)'>Continue</button>
        <div id="err1">* Please fill all fields</div>
    </div>
</div>
<div id="Address_Fill" style="display:none; padding: 3px 0">
    <form id="formId_Address">
        <div class="form-group">
            <div class="e-float-input">
                <input type="text" id="name" name="Name" required="" />
                <span class="e-float-line"></span>
                <label class="e-float-text" for="name">Name</label>
            </div>
        </div>
        <div class="form-group">
            <div class="e-float-input">
                <input type="text" id="address" name="Address" required="" />
                <span class="e-float-line"></span>
                <label class="e-float-text" for="address">Address</label>
            </div>
        </div>
        <div class="form-group">
            <ejs-numerictextbox #mobile format='0' placeholder='Mobile' floatLabelType='Auto' [showSpinButton]=false></ejs-numerictextbox>
        </div>
    </form>
    <div style="text-align: center">
        <button class='e-btn' id="Continue_BtnAdr" (click)='btnClick($event)'>Continue</button>
        <div id="err2">* Please fill all fields</div>
    </div>
</div>
<div id="Card_Fill" style="display:none; padding: 3px 0;">
    <div class="col-xs-6 col-sm-6 col-lg-6 col-md-6">
        <div class="form-group">
            <ejs-numerictextbox #cardNo format='0' placeholder='Card No' floatLabelType='Auto' [showSpinButton]=false></ejs-numerictextbox>
        </div>
    </div>
    <div class="col-xs-6 col-sm-6 col-lg-6 col-md-6">
        <div class="form-group">
            <div class="e-float-input">
                <input type="text" id="cardHolder" name="cardHolder" required="" />
                <span class="e-float-line"></span>
                <label class="e-float-text" for="cardHolder">CardHolder Name</label>
            </div>
        </div>
    </div>
    <div class="col-xs-6 col-sm-6 col-lg-6 col-md-6">
        <ejs-datepicker #date width='100%' format='MM/yyyy' placeholder='Expiry Date' floatLabelType='Auto'></ejs-datepicker>
    </div>
    <div class="col-xs-6 col-sm-6 col-lg-6 col-md-6">
        <div class="form-group">
            <ejs-numerictextbox #cvv format='0' placeholder='CVV' floatLabelType='Auto' [showSpinButton]=false></ejs-numerictextbox>
        </div>
    </div>
    <div style="text-align: center">
        <button class='e-btn' id="Back_Btn" (click)='btnClick($event)'>Back</button>
        <button class='e-btn' id="Save_Btn" (click)='btnClick($event)'>Save</button>
        <div id="err3">* Please fill all fields</div>
    </div>
</div>
<ejs-dialog #alertDlg header='Alert' width=200 isModal=true content='' [target]='dlgTarget' [buttons]='dlgButtons' (created)='dlgCreated()'></ejs-dialog>

<ejs-accordion #accordion (created)='acrdnCreated()'>
    <e-accordionitems>
        <e-accordionitem expanded='true' header='Sign In' content='#Sign_In_Form'></e-accordionitem>
        <e-accordionitem header='Delivery Address' content='#Address_Fill'></e-accordionitem>
        <e-accordionitem header='Card Details' content='#Card_Fill'></e-accordionitem>
    </e-accordionitems>
</ejs-accordion>
#container {
    max-width: 450px;
    margin: 0 auto;
}

#loader {
    color: #008cff;
    height: 40px;
    left: 45%;
    position: absolute;
    top: 45%;
    width: 30%;
}

.accordion-control-section {
    margin: 0 10% 0 10%;
}

@media screen and (max-width: 768px) {
    .accordion-control-section {
        margin: 0;
    }
}

#err1, #err2, #err3 {
    display: none;
    color: red;
    margin-top: 10px;
    font-weight: 500;
}

#err1.show,
#err2.show,
#err3.show {
    display: block;
}

.e-dialog {
    max-height: 300px !important; /* csslint allow: important */
}

.template_title {
    text-align: center;
    padding: 10px 0;
    margin: 20px 0;
    text-overflow: ellipsis;
    font-weight: bold;
    font-size: 16px;
}

Load Accordion with DataSource

You can bind any data object to Accordion items, by mapping it to header and content  property.

In the below demo, Data is fetched from an OData service using DataManager. The result data is formatted as a JSON object with header and content fields, which is set to items property of Accordion.

Source
Preview
app.component.ts
app.module.ts
main.ts
import { Component, OnInit, ViewChild } from '@angular/core';
import { AccordionComponent } from '@syncfusion/ej2-angular-navigations';
import { DataManager, Query, ODataAdaptor, ReturnOption } from '@syncfusion/ej2-data';

const SERVICE_URI: string = 'https://js.syncfusion.com/ejServices/Wcf/Northwind.svc/Employees';

@Component({
    selector: 'app-container',
    template: `<ejs-accordion #element></ejs-accordion>`
})

export class AppComponent implements OnInit {
  @ViewChild('element') accordionObj: AccordionComponent;
  public itemsData: any = [];
  public mapping =  { header: 'FirstName', content: 'Notes' };

  public ngOnInit(): void {
    new DataManager({ url: SERVICE_URI, adaptor: new ODataAdaptor})
    .executeQuery(new Query().range(1, 4)).then((e: ReturnOption) => {
      let result: any = e.result;

      for(let i: number = 0; i < result.length; i++) {
        this.itemsData.push({ header: result[i][this.mapping.header], content: result[i][this.mapping.content] });
      }
      this.accordionObj.items = this.itemsData;
      this.accordionObj.refresh();
    });
  }
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AccordionModule } from '@syncfusion/ej2-angular-navigations';
import { AppComponent } from './app.component';

/**
 * Module
 */
@NgModule({
    imports: [
        BrowserModule, AccordionModule
    ],
    declarations: [AppComponent],
    bootstrap: [AppComponent]
})
export class AppModule { }
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { AppModule } from './app.module';

enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);

Render Accordion content using ng-content

To render the Accordion contents using ng-content, we need to use ng-template inside the each e-accordionitem tag with #content attribute, which is mandatory to render content. Now include ng-content inside the ng-template tag with select attribute of id or class name for mapping required content.

  <e-accordionitem expanded='true' header='Athletics'>
    <ng-template #content>
      <ng-content select='div.content0'></ng-content>
    </ng-template>
  </e-accordionitem>

 Here div.content0 mapped to ng-content is reusable content. It can be used in multiple scenarios within the application.

Source
Preview
app.component.html
reusable-content.html
app.component.ts
app.module.ts
main.ts
<!-- Render the Accoridon Component by using ng-content -->

<ejs-accordion expandMode='Single'>
    <e-accordionitems>
        <e-accordionitem expanded='true' header='Athletics'>
            <ng-template #content>
                <ng-content select="div.content0"></ng-content>
            </ng-template>
        </e-accordionitem>
        <e-accordionitem header='Water Games'>
            <ng-template #content>
                <ng-content select="div.content1"></ng-content>
            </ng-template>
        </e-accordionitem>
        <e-accordionitem header='Racing'>
            <ng-template #content>
                <ng-content select="div.content2"></ng-content>
            </ng-template>
        </e-accordionitem>
        <e-accordionitem header='Indoor Games'>
            <ng-template #content>
                <ng-content select="div.content3"></ng-content>
            </ng-template>
        </e-accordionitem>
    </e-accordionitems>
</ejs-accordion>
<my-thing>
    <div class="content0">
        <div id='athletics'>
            <li><span class='e-acrdn-icons e-content-icon marathon'></span> Marathon</li>
            <li><span class='e-acrdn-icons e-content-icon javelin'></span> Javelin Throw</li>
            <li><span class='e-acrdn-icons e-content-icon discus'></span> Discus Throw</li>
            <li><span class='e-acrdn-icons e-content-icon highjump'></span> High Jump</li>
            <li><span class='e-acrdn-icons e-content-icon longjump'></span> Long Jump</li>
        </div>
    </div>
    <div class="content1">
        <div id='racing_games'>
            <li><span class='e-acrdn-icons e-content-icon dive'></span> Diving</li>
            <li><span class='e-acrdn-icons e-content-icon swimming'></span> Swimming</li>
            <li><span class='e-acrdn-icons e-content-icon marathan_swim'></span> Marathon Swimming</li>
            <li><span class='e-acrdn-icons e-content-icon sync_swim'></span> Synchronized Swimming</li>
            <li><span class='e-acrdn-icons e-content-icon waterpolo'></span> Water Polo</li>
        </div>
    </div>
    <div class="content2">
        <div id='water_games'>
            <li><span class='e-acrdn-icons e-content-icon cycle_BMX'></span> Cycling BMX</li>
            <li> <span class='e-acrdn-icons e-content-icon cycle_Mountain'></span> Cycling Mountain Bike</li>
            <li> <span class='e-acrdn-icons e-content-icon cycle'></span> Cycle Racing</li>
            <li> <span class='e-acrdn-icons e-content-icon sailing'></span> Sailing</li>
            <li> <span class='e-acrdn-icons e-content-icon rowing'></span> Rowing</li>
        </div>
    </div>
    <div class="content3">
        <div id='indoor_games'>
            <li><span class='e-acrdn-icons e-content-icon tennis'></span> Table Tennis</li>
            <li> <span class='e-acrdn-icons e-content-icon badminton'></span> Badminton</li>
            <li> <span class='e-acrdn-icons e-content-icon volleyball'></span> Volleyball</li>
            <li> <span class='e-acrdn-icons e-content-icon boxing'></span> Boxing</li>
            <li> <span class='e-acrdn-icons e-content-icon swimming_In'></span> Swimming</li>
        </div>
    </div>
  </my-thing>
import { Component, ViewEncapsulation, Inject } from '@angular/core';

@Component({
  selector: 'my-thing',
  templateUrl: 'app/app.component.html'
})
export class AccordionComponent {}

@Component({
  selector: 'control-content',
  templateUrl: 'app/reusable-content.html',
  styleUrls: ['app/app.component.css'],
  encapsulation: ViewEncapsulation.None
})
export class MyApp {}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, ModuleWithProviders, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

import { AccordionModule } from '@syncfusion/ej2-angular-navigations';

import { AccordionComponent, MyApp } from './app.component';

@NgModule({
    imports: [
        BrowserModule,
        AccordionModule
    ],
    declarations: [AccordionComponent, MyApp],
    bootstrap: [MyApp]
})
export class AppModule { }
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { AppModule } from './app.module';

enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);

Load accordion items dynamically

Accordion items can be added dynamically by passing the item and index value with the addItem method.

In the following demo, you can add the accordion content by expanding any accordion header content.

  • Data is fetched from the data source.

  • The data is formatted as a JSON object with header and content fields.

  • Here last index is calculated to append the new accordion at the end.

Source
Preview
app.component.ts
app.module.ts
datasource.ts
main.ts
index.html
index.css
import { Component, ViewChild } from '@angular/core';
import { AccordionComponent } from '@syncfusion/ej2-angular-navigations';
import { Accordion, ExpandEventArgs, AccordionClickArgs, AccordionItemModel } from '@syncfusion/ej2-navigations';
import { accordion } from './datasource.ts';

let dbFlag: number = 0;
let dynamciAcrdnCount: number = 2;
@Component({
    selector: 'app-container',
    template: `
            <ejs-accordion #element (expanded)="expanded($event)">
        <e-accordionitems>
          <e-accordionitem expanded='true'>
            <ng-template #header>
              <div>ASP.NET</div>
            </ng-template>
            <ng-template #content>
              <div>Microsoft ASP.NET is a set of technologies in the Microsoft .NET Framework for building Web applications and XML Web
                services. ASP.NET pages execute on the server and generate markup such as HTML, WML, or XML that is sent to a desktop
                or mobile browser. ASP.NET pages use a compiled,event-driven programming model that improves performance and enables
                the separation of application logic and user interface.</div>
            </ng-template>
          </e-accordionitem>
          <e-accordionitem>
            <ng-template #header>
              <div>ASP.NET MVC</div>
            </ng-template>
            <ng-template #content>
              <div>The Model-View-Controller (MVC) architectural pattern separates an application into three main components: the model,
                the view, and the controller. The ASP.NET MVC framework provides an alternative to the ASP.NET Web Forms pattern for
                creating Web applications. The ASP.NET MVC framework is a lightweight, highly testable presentation framework that
                (as with Web Forms-based applications) is integrated with existing ASP.NET features, such as master pages and membership-based
                authentication.
              </div>
            </ng-template>
          </e-accordionitem>
        </e-accordionitems>
    </ejs-accordion>
        `
})

export class AppComponent {
    @ViewChild('element') acrdnInstance: AccordionComponent;
    public expanded(e: ExpandEventArgs) {
         let Elementindex = document.getElementsByClassName("e-expand-state e-selected e-active")[0];
         if([].slice.call(e.element.parentElement.children).indexOf(e.element) == [].slice.call(e.element.parentElement.children).indexOf(Elementindex)) {
            let array: AccordionItemModel[] = accordion as AccordionItemModel[];
            for(let i: number = 0 ; i < dynamciAcrdnCount; i++)
            {
            if (dbFlag === array.length) {
                return; }
            this.acrdnInstance.addItem( array[dbFlag] , this.acrdnInstance.items.length );
            ++dbFlag;
            }
         }
    }
    ngAfterViewInit() {
    }
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AccordionModule } from '@syncfusion/ej2-angular-navigations';
import { AppComponent } from './app.component';

/**
 * Module
 */
@NgModule({
    imports: [
        BrowserModule, AccordionModule
    ],
    declarations: [AppComponent],
    bootstrap: [AppComponent]
})
export class AppModule { }
export let accordion: Object[] = [
    {
        header:  ' ASP.NET Razor ',
        content: ' Razor is an ASP.NET programming syntax used to create dynamic web pages with the C# or Visual Basic .NET programming languages. Razor was in development in June 2010 and was released for Microsoft Visual Studio 2010 in January 2011. Razor is a simple-syntax view engine and was released as part of MVC 3 and the WebMatrix tool set. '
    },
    {
        header:  ' EmberJS ',
        content: ' EmberJS is an open-source JavaScript web framework, based on the Model–view–viewmodel (MVVM) pattern. It allows developers to create scalable single-page web applications by incorporating common idioms and best practices into the framework. '
    },
    {
        header:  ' Hypertext Markup Language ',
        content: ' Hypertext Markup Language (HTML) is the standard markup language for creating web pages and web applications. With Cascading Style Sheets (CSS) and JavaScript, it forms a triad of cornerstone technologies for the World Wide Web. '
    },
    {
        header:  ' HTML5 ',
        content: ' HTML5 is a markup language used for structuring and presenting content on the World Wide Web. It is the fifth and current major version of the HTML standard. '
    },
    {
        header:  ' JavaScript ',
        content: ' JavaScript (JS) is an interpreted computer programming language. '
    },
    {
        header:  ' JSP ',
        content: ' JavaServer Pages (JSP) is a technology that helps software developers create dynamically generated web pages based on HTML, XML, or other document types. Released in 1999 by Sun Microsystems, JSP is similar to PHP and ASP, but it uses the Java programming language. '
    },
    {
        header:  ' Perl ',
        content: ' Perl is a family of high-level, general-purpose, interpreted, dynamic programming languages.  Perl was originally developed by Larry Wall in 1987 as a general-purpose Unix scripting language to make report processing easier. '
    },
    {
        header:  ' PHP ',
        content: ' PHP is a server-side scripting language designed for web development but also used as a general-purpose programming language. Originally created by Rasmus Lerdorf in 1994, the PHP reference implementation is now produced by The PHP Group. PHP originally stood for Personal Home Page, but it now stands for the recursive acronym PHP: Hypertext Preprocessor '
    },
    {
        header:  ' Ruby ',
        content: ' Ruby is a dynamic, reflective, object-oriented, general-purpose programming language. It supports multiple programming paradigms, including functional, object-oriented, and imperative. It also has a dynamic type system and automatic memory management. '
    },
    {
        header:  ' Typescript ',
        content: ' TypeScript is an open-source programming language developed and maintained by Microsoft. It is a strict syntactical superset of JavaScript, and adds optional static typing to the language. '
    },
];
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { AppModule } from './app.module';

enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);
<!DOCTYPE html>
<html lang="en">

<head>
    <title>Essential JS 2 Accordion Sample</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Typescript Toolbar Controls" />
    <meta name="author" content="Syncfusion" />
    <link href="index.css" rel="stylesheet" />
    <link href="//cdn.syncfusion.com/ej2/material.css" rel="stylesheet" />

    <script src="https://unpkg.com/core-js/client/shim.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/core-js/2.4.1/core.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.6.25/zone.min.js"></script>
    <script src="https://unpkg.com/reflect-metadata@0.1.3"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
    <script src="systemjs.config.js"></script>
</head>
<body>
    <app-container>
        <div id='loader'>LOADING....</div>
    </app-container>
</body>
</html>
#container {
    visibility: hidden;
}

#loader {
    color: #008cff;
    height: 40px;
    left: 45%;
    position: absolute;
    top: 45%;
    width: 30%;
}

.accordion-control-section {
    margin: 0 10% 0 10%;
}

@media screen and (max-width: 768px) {
    .accordion-control-section {
      margin: 0;
  }
}

Integrate Essential JS 2 TreeView inside the Accordion

Accordion supports to render other Essential JS 2 Components by using content property. You can give content as an element string like below, for initializing the component.

content: '<div id="element"> </div>'

The other component can be rendered with the use of provided events, such as clicked and expanding. The following procedure is to render a TreeView within the Accordion,

  • Import the TreeView module from ej2-navigations, for adding TreeView. Please refer the TreeView initialization steps

  • You can initialize the TreeView component in expanding event, by getting the element and defining the required TreeView properties.

Source
Preview
app.component.ts
app.module.ts
datasource.ts
main.ts
index.html
index.css
import { Component, ViewChild } from '@angular/core';
import { AccordionComponent } from '@syncfusion/ej2-angular-navigations';
import { Accordion, ExpandEventArgs, TreeView } from '@syncfusion/ej2-navigations';
import { DocDB, DownloadDB, PicDB } from './datasource.ts';

@Component({
    selector: 'app-container',
    template: `
    <ejs-accordion #element (expanding)="expanded($event)">
        <e-accordionitems>
          <e-accordionitem expanded='true'>
            <ng-template #header>
              <div>Documents</div>
            </ng-template>
            <ng-template #content>
              <div id="treeDoc"></div>
            </ng-template>
          </e-accordionitem>
          <e-accordionitem>
            <ng-template #header>
              <div>Downloads</div>
            </ng-template>
            <ng-template #content>
              <div id="treeDownload"></div>
            </ng-template>
          </e-accordionitem>
          <e-accordionitem>
            <ng-template #header>
              <div>Pictures</div>
            </ng-template>
            <ng-template #content>
             <div id="treePic"></div>
            </ng-template>
          </e-accordionitem>
        </e-accordionitems>
    </ejs-accordion>
        `
})

export class AppComponent {
    @ViewChild('element') acrdnInstance: AccordionComponent;
    public expanded(e: ExpandEventArgs) {
  if (e.isExpanded && [].indexOf.call(this.acrdnInstance.items, e.item) === 0 && e.element.querySelector('#treeDoc').childElementCount === 0) {
    //Initialize TreeView component
        let treeObj: TreeView = new TreeView({
        fields: { dataSource: DocDB, id: 'nodeId', text: 'nodeText', child: 'nodeChild', iconCss: 'icon', imageUrl: 'image' },
        sortOrder: 'Ascending'
    });
    //Render initialized TreeView component
    treeObj.appendTo('#treeDoc');
  }
    if (e.isExpanded && [].indexOf.call(this.acrdnInstance.items, e.item) === 1 && e.element.querySelector('#treeDownload').childElementCount === 0) {
        let treeObj: TreeView = new TreeView({
        fields: { dataSource: DownloadDB, id: 'nodeId', text: 'nodeText', child: 'nodeChild', iconCss: 'icon', imageUrl: 'image' },
        sortOrder: 'Ascending'
    });
    treeObj.appendTo('#treeDownload');
  }
      if (e.isExpanded && [].indexOf.call(this.acrdnInstance.items, e.item) === 2 && e.element.querySelector('#treePic').childElementCount === 0) {
        let treeObj: TreeView = new TreeView({
        fields: { dataSource: PicDB, id: 'nodeId', text: 'nodeText', child: 'nodeChild', iconCss: 'icon', imageUrl: 'image' },
        sortOrder: 'Ascending'
    });
    treeObj.appendTo('#treePic');
  }

    }
    ngAfterViewInit() {
    }
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AccordionModule } from '@syncfusion/ej2-angular-navigations';
import { AppComponent } from './app.component';

/**
 * Module
 */
@NgModule({
    imports: [
        BrowserModule, AccordionModule
    ],
    declarations: [AppComponent],
    bootstrap: [AppComponent]
})
export class AppModule { }
export let DocDB: { [key: string]: Object }[] = [
    {
            nodeId: '03', nodeText: 'Documents', icon: 'folder',
            nodeChild: [
                { nodeId: '03-01', nodeText: 'Environment Pollution.docx', icon: 'docx' },
                { nodeId: '03-02', nodeText: 'Global Water, Sanitation, & Hygiene.docx', icon: 'docx' },
                { nodeId: '03-03', nodeText: 'Global Warming.ppt', icon: 'ppt' },
                { nodeId: '03-04', nodeText: 'Social Network.pdf', icon: 'pdf' },
                { nodeId: '03-05', nodeText: 'Youth Empowerment.pdf', icon: 'pdf' },
            ]
        },
    ]
    
  export let DownloadDB: { [key: string]: Object }[] = [
        {
            nodeId: '05', nodeText: 'Downloads', icon: 'folder',
            nodeChild: [
                { nodeId: '05-01', nodeText: 'UI-Guide.pdf', icon: 'pdf' },
                { nodeId: '05-02', nodeText: 'Tutorials.zip', icon: 'zip' },
                { nodeId: '05-03', nodeText: 'Game.exe', icon: 'exe' },
                { nodeId: '05-04', nodeText: 'TypeScript.7z', icon: 'zip' },
            ]
        }
    ]
    
  export let PicDB: { [key: string]: Object }[] = [
        {
            nodeId: '04', nodeText: 'Pictures', icon: 'folder', expanded: true,
            nodeChild: [
                {
                    nodeId: '04-01', nodeText: 'Camera Roll', icon: 'folder', expanded: true,
                    nodeChild: [
                        { nodeId: '04-01-01', nodeText: 'WIN_20160726_094117.JPG', image: 'http://npmci.syncfusion.com/development/demos/src/images/employees/9.png' },
                        { nodeId: '04-01-02', nodeText: 'WIN_20160726_094118.JPG', image: 'http://npmci.syncfusion.com/development/demos/src/images/employees/3.png' },
                    ]
                },
                { nodeId: '04-02', nodeText: 'Wind.jpg', icon: 'images' },
                { nodeId: '04-03', nodeText: 'Stone.jpg', icon: 'images' },
            ]
        }
    ]
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { AppModule } from './app.module';

enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);
<!DOCTYPE html>
<html lang="en">

<head>
    <title>Essential JS 2 Accordion Sample</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Typescript Toolbar Controls" />
    <meta name="author" content="Syncfusion" />
    <link href="index.css" rel="stylesheet" />
    <link href="//cdn.syncfusion.com/ej2/material.css" rel="stylesheet" />

    <script src="https://unpkg.com/core-js/client/shim.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/core-js/2.4.1/core.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.6.25/zone.min.js"></script>
    <script src="https://unpkg.com/reflect-metadata@0.1.3"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
    <script src="systemjs.config.js"></script>
</head>
<body>
    <app-container>
        <div id='loader'>LOADING....</div>
    </app-container>
</body>
</html>
#container {
    visibility: hidden;
}

#loader {
    color: #008cff;
    height: 40px;
    left: 45%;
    position: absolute;
    top: 45%;
    width: 30%;
}

.accordion-control-section {
    margin: 0 10% 0 10%;
}


.e-treeview .e-list-img {
    width: 25px;
    height: 25px;
}
/* Loading sprite image for TreeView */
.e-treeview .e-list-icon {
    background-repeat: no-repeat;
    background-image: url("./file_icons.png");
    height: 20px;
}
/* Specify the icon positions based upon class name */
/* csslint ignore:start */
.e-treeview .e-list-icon.folder { background-position: -10px -552px }
.e-treeview .e-list-icon.docx { background-position: -10px -20px }
.e-treeview .e-list-icon.ppt { background-position: -10px -48px }
.e-treeview .e-list-icon.pdf { background-position: -10px -104px }
.e-treeview .e-list-icon.images { background-position: -10px -132px }
.e-treeview .e-list-icon.zip { background-position: -10px -188px }
.e-treeview .e-list-icon.audio { background-position: -10px -244px }
.e-treeview .e-list-icon.video { background-position: -10px -272px }
.e-treeview .e-list-icon.exe { background-position: -10px -412px }
/* csslint ignore:end */