Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add custom arrow template #2197

Closed
wants to merge 11 commits into from
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,18 @@ import { FormsModule } from '@angular/forms';
export class AppModule {}
```

### Step 3: Include a theme:
### Step 3: Include a theme:
To allow customization and theming, `ng-select` bundle includes only generic styles that are necessary for correct layout and positioning. To get full look of the control, include one of the themes in your application. If you're using the Angular CLI, you can add this to your `styles.scss` or include it in `.angular-cli.json` (Angular v5 and below) or `angular.json` (Angular v6 onwards).

```scss
@import "~@ng-select/ng-select/themes/default.theme.css";
// ... or
// ... or
@import "~@ng-select/ng-select/themes/material.theme.css";

```


### Step 4 (Optional): Configuration
### Step 4 (Optional): Configuration

You can also set global configuration and localization messages by injecting NgSelectConfig service,
typically in your root component, and customize the values of its properties in order to provide default values.
Expand All @@ -117,9 +117,9 @@ typically in your root component, and customize the values of its properties in
constructor(private config: NgSelectConfig) {
this.config.notFoundText = 'Custom not found';
this.config.appendTo = 'body';
// set the bindValue to global config when you use the same
// bindValue in most of the place.
// You can also override bindValue for the specified template
// set the bindValue to global config when you use the same
// bindValue in most of the place.
// You can also override bindValue for the specified template
// by defining `bindValue` as property
// Eg : <ng-select bindValue="some-new-value"></ng-select>
this.config.bindValue = 'value';
Expand Down Expand Up @@ -153,9 +153,9 @@ In template use `ng-select` component with your options
</ng-select>

<!--Using items input-->
<ng-select [items]="cars"
bindLabel="name"
bindValue="id"
<ng-select [items]="cars"
bindLabel="name"
bindValue="id"
[(ngModel)]="selectedCar">
</ng-select>
```
Expand Down Expand Up @@ -272,12 +272,12 @@ export class CustomSelectionModel implements SelectionModel {
```

## Change Detection
Ng-select component implements `OnPush` change detection which means the dirty checking checks for immutable
Ng-select component implements `OnPush` change detection which means the dirty checking checks for immutable
data types. That means if you do object mutations like:

```javascript
this.items.push({id: 1, name: 'New item'})
```
```

Component will not detect a change. Instead you need to do:

Expand All @@ -302,7 +302,7 @@ If you are not happy with default styles you can easily override them with incre
min-height: 0px;
border-radius: 0;
}
.ng-select.custom .ng-select-container {
.ng-select.custom .ng-select-container {
min-height: 0px;
border-radius: 0;
}
Expand All @@ -311,7 +311,7 @@ If you are not happy with default styles you can easily override them with incre
If you are using `ViewEncapsulation`, you could use special `::ng-deep` selector which will prevent scoping for nested selectors altough this is more of a workaround and we recommend using solution described above.

```css
.ng-select.custom ::ng-deep .ng-select-container {
.ng-select.custom ::ng-deep .ng-select-container {
min-height: 0px;
border-radius: 0;
}
Expand Down Expand Up @@ -342,13 +342,13 @@ Perform the _clone-to-launch_ steps with these terminal commands.
git clone https://github.com/ng-select/ng-select
cd ng-select
yarn
yarn run start
yarn start
```
### Testing
```
yarn run test
yarn test
or
yarn run test:watch
yarn test:watch
```

### Release
Expand Down
2 changes: 2 additions & 0 deletions src/demo/app/examples/examples.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { TemplateOptionExampleComponent } from './template-option-example/templa
import { TemplateSearchExampleComponent } from './template-search-example/template-search-example.component';
import { VirtualScrollExampleComponent } from './virtual-scroll-example/virtual-scroll-example.component';
import { SearchEditableExampleComponent } from './search-editable-example/search-editable-example.component';
import { TemplateArrowExampleComponent } from './template-arrow-example/template-arrow-example.component';


const examples = [DataSourceBackendExampleComponent,
Expand Down Expand Up @@ -73,6 +74,7 @@ const examples = [DataSourceBackendExampleComponent,
TemplateDisplayExampleComponent,
TemplateSearchExampleComponent,
TemplateLoadingExampleComponent,
TemplateArrowExampleComponent,
MultiSelectDefaultExampleComponent,
MultiSelectHiddenExampleComponent,
MultiSelectLimitExampleComponent,
Expand Down
5 changes: 5 additions & 0 deletions src/demo/app/examples/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { GroupSelectableExampleComponent } from './group-selectable-example/grou
import { GroupSelectableHiddenExampleComponent } from './group-selectable-hidden-example/group-selectable-hidden-example.component';
import { GroupChildrenExampleComponent } from './group-children-example/group-children-example.component';
import { SearchEditableExampleComponent } from './search-editable-example/search-editable-example.component';
import { TemplateArrowExampleComponent } from './template-arrow-example/template-arrow-example.component';

export interface Example {
component: any;
Expand Down Expand Up @@ -152,6 +153,10 @@ export const EXAMPLE_COMPONENTS: { [key: string]: Example } = {
component: TemplateLoadingExampleComponent,
title: 'Custom loading spinner'
},
'template-arrow-example': {
component: TemplateArrowExampleComponent,
title: 'Custom arrow'
},
'multi-select-default-example': {
component: MultiSelectDefaultExampleComponent,
title: 'Multi select'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<p>Custom arrow using <b>ng-arrow-tmp</b></p>

<ng-select [items]="cities" bindLabel="name" bindValue="name">
<ng-template ng-arrow-tmp>
<div class="custom-arrow">
<svg width="16px" height="16px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.16108 10.0731C4.45387 9.2649 5.02785 8 6.1018 8H17.898C18.972 8 19.5459 9.2649 18.8388 10.0731L13.3169 16.3838C12.6197 17.1806 11.3801 17.1806 10.6829 16.3838L5.16108 10.0731ZM6.65274 9.5L11.8118 15.396C11.9114 15.5099 12.0885 15.5099 12.1881 15.396L17.3471 9.5H6.65274Z" fill="#212121"/>
</svg>
</div>
</ng-template>
</ng-select>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.custom-arrow > svg {
transform: translateY(-2px);
}

.ng-select-opened .custom-arrow {
transform: rotate(180deg);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Component, OnInit } from '@angular/core';

@Component({
selector: 'template-arrow-example',
templateUrl: './template-arrow-example.component.html',
styleUrls: ['./template-arrow-example.component.scss']
})
export class TemplateArrowExampleComponent implements OnInit {

cities = [
{
id: 1,
name: 'Vilnius',
avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x'
},
{ id: 2, name: 'Kaunas', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' },
{
id: 3,
name: 'Pavilnys',
avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15'
},
{
id: 4,
name: 'Siauliai',
avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x'
},
];

ngOnInit() {
}
}
6 changes: 5 additions & 1 deletion src/demo/app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,9 @@ export const appRoutes: Routes = [
component: RouteViewerComponent,
data: { title: 'Append to element', examples: 'append-to' }
},
{ path: 'grouping', component: RouteViewerComponent, data: { title: 'Grouping', examples: 'group' } },
{
path: 'grouping',
component: RouteViewerComponent,
data: { title: 'Grouping', examples: 'group' }
},
];
14 changes: 10 additions & 4 deletions src/ng-select/lib/ng-select.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
}

<div class="ng-input"
role="combobox"
[attr.aria-expanded]="isOpen"
[attr.aria-owns]="isOpen ? dropdownId : null"
role="combobox"
[attr.aria-expanded]="isOpen"
[attr.aria-owns]="isOpen ? dropdownId : null"
aria-haspopup="listbox">

<input #searchInput
Expand Down Expand Up @@ -69,7 +69,13 @@
}

<span class="ng-arrow-wrapper">
<span class="ng-arrow"></span>
<ng-template #defaultArrowTemplate>
<span class="ng-arrow"></span>
</ng-template>

<ng-template
[ngTemplateOutlet]="arrowTemplate || defaultArrowTemplate">
</ng-template>
</span>
</div>

Expand Down
20 changes: 20 additions & 0 deletions src/ng-select/lib/ng-select.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2139,6 +2139,26 @@ describe('NgSelectComponent', () => {
});
}));

it('should display custom arrow template', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
`<ng-select [items]="cities"
[(ngModel)]="selectedCity">

<ng-template ng-arrow-tmp>
<div class="custom-arrow">
⬇︎
</div>
</ng-template>
</ng-select>`);

fixture.whenStable().then(() => {
tickAndDetectChanges(fixture);
const arrow = fixture.debugElement.queryAll(By.css('.custom-arrow'));
expect(arrow.length).toBe(1);
});
}));

it('should update ng-option state', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectTestComponent,
Expand Down
4 changes: 3 additions & 1 deletion src/ng-select/lib/ng-select.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ import {
NgLoadingTextTemplateDirective,
NgMultiLabelTemplateDirective,
NgTagTemplateDirective,
NgLoadingSpinnerTemplateDirective
NgLoadingSpinnerTemplateDirective,
NgArrowTemplateDirective
} from './ng-templates.directive';

import { ConsoleService } from './console.service';
Expand Down Expand Up @@ -188,6 +189,7 @@ export class NgSelectComponent implements OnDestroy, OnChanges, OnInit, AfterVie
@ContentChild(NgLoadingTextTemplateDirective, { read: TemplateRef }) loadingTextTemplate: TemplateRef<any>;
@ContentChild(NgTagTemplateDirective, { read: TemplateRef }) tagTemplate: TemplateRef<any>;
@ContentChild(NgLoadingSpinnerTemplateDirective, { read: TemplateRef }) loadingSpinnerTemplate: TemplateRef<any>;
@ContentChild(NgArrowTemplateDirective, { read: TemplateRef }) arrowTemplate: TemplateRef<any>;

@ViewChild(forwardRef(() => NgDropdownPanelComponent)) dropdownPanel: NgDropdownPanelComponent;
@ViewChild('searchInput', { static: true }) searchInput: ElementRef<HTMLInputElement>;
Expand Down
7 changes: 5 additions & 2 deletions src/ng-select/lib/ng-select.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import {
NgHeaderTemplateDirective,
NgLabelTemplateDirective,
NgLoadingSpinnerTemplateDirective,
NgArrowTemplateDirective,
NgLoadingTextTemplateDirective,
NgMultiLabelTemplateDirective,
NgNotFoundTemplateDirective,
NgOptgroupTemplateDirective,
NgOptionTemplateDirective,
NgTagTemplateDirective,
NgItemLabelDirective,
NgTypeToSearchTemplateDirective
NgTypeToSearchTemplateDirective,
} from './ng-templates.directive';
import { DefaultSelectionModelFactory } from './selection-model';

Expand All @@ -35,6 +36,7 @@ import { DefaultSelectionModelFactory } from './selection-model';
NgLoadingTextTemplateDirective,
NgTagTemplateDirective,
NgLoadingSpinnerTemplateDirective,
NgArrowTemplateDirective,
NgItemLabelDirective
],
imports: [
Expand All @@ -53,7 +55,8 @@ import { DefaultSelectionModelFactory } from './selection-model';
NgTypeToSearchTemplateDirective,
NgLoadingTextTemplateDirective,
NgTagTemplateDirective,
NgLoadingSpinnerTemplateDirective
NgLoadingSpinnerTemplateDirective,
NgArrowTemplateDirective
],
providers: [
{ provide: SELECTION_MODEL_FACTORY, useValue: DefaultSelectionModelFactory }
Expand Down
6 changes: 6 additions & 0 deletions src/ng-select/lib/ng-templates.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,9 @@ export class NgTagTemplateDirective {
export class NgLoadingSpinnerTemplateDirective {
constructor(public template: TemplateRef<any>) { }
}

// eslint-disable-next-line @angular-eslint/directive-selector
@Directive({ selector: '[ng-arrow-tmp]' })
export class NgArrowTemplateDirective {
constructor(public template: TemplateRef<any>) { }
}
1 change: 1 addition & 0 deletions src/ng-select/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export {
NgItemLabelDirective,
NgLabelTemplateDirective,
NgLoadingSpinnerTemplateDirective,
NgArrowTemplateDirective,
NgLoadingTextTemplateDirective,
NgMultiLabelTemplateDirective,
NgNotFoundTemplateDirective,
Expand Down
Loading