Ok so far I was successfully able to bind <paper-radio>, <paper-checkbox> with ngModel by using a Custom ControlValueAccessor for each one of them. Now I'm stuck at <paper-dropdown-menu>
Here the scenario, I can capture the paper-dropdown (iron-select) but cannot bind -two-way, i.e., using ngModel below is the HTML and custom accessor class
My form.html
<paper-dropdown-menu [(ngModel)]="mymodel.selection" label="Your Fix">
<paper-menu class="dropdown-content">
<paper-item value="1" ngDefaultControl>Coffee</paper-item>
<paper-item value="2" ngDefaultControl>Cigarettes</paper-item>
<paper-item value="3" ngDefaultControl>Chivas</paper-item></paper-menu>
</paper-dropdown-menu>
And My Custom Accessor Class
/**
* Created by pratik on 12/5/16.
*/
import {
Query,
Directive,
Renderer,
Self,
forwardRef,
Provider,
ElementRef,
QueryList
} from 'angular2/core';
import {ObservableWrapper} from 'angular2/src/facade/async';
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from 'angular2/common';
import {CONST_EXPR} from 'angular2/src/facade/lang';
const SELECT_VALUE_ACCESSOR = CONST_EXPR(new Provider(
NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => SelectControlValueAccessor), multi: true}));
/**
* Marks `<option>` as dynamic, so Angular can be notified when options change.
*
* ### Example
*
* ```
* <select ngControl="city">
* <option *ngFor="#c of cities" [value]="c"></option> Need to change to paper-item
* </select>
* ```
*/
#Directive({selector: 'option'}) // Tried changing to paper-item but still doesn't work
export class NgSelectOption {
}
/**
* The accessor for writing a value and listening to changes on a select element.
*/
#Directive({
selector: 'paper-dropdown-menu[ngControl],paper-dropdown-menu[ngFormControl],paper-dropdown-menu[ngModel]',
host: {
'(iron-select)': 'onChange($event.target.value)',
'(input)': 'onChange($event.target.value)',
'(blur)': 'onTouched()'
},
bindings: [SELECT_VALUE_ACCESSOR]
})
export class SelectControlValueAccessor implements ControlValueAccessor {
value: string;
onChange = (_) => {};
onTouched = () => {};
constructor(private _renderer: Renderer, private _elementRef: ElementRef,
#Query(NgSelectOption, {descendants: true}) query: QueryList<NgSelectOption>) {
this._updateValueWhenListOfOptionsChanges(query);
}
writeValue(value: any): void {
this.value = value;
this._renderer.setElementProperty(this._elementRef, 'value', value);
}
registerOnChange(fn: () => any): void { this.onChange = fn; }
registerOnTouched(fn: () => any): void { this.onTouched = fn; }
private _updateValueWhenListOfOptionsChanges(query: QueryList<NgSelectOption>) {
ObservableWrapper.subscribe(query.changes, (_) => this.writeValue(this.value));
}
}
Full working example. I didn't find a proper way to apply the ControlValueAccessor to <paper-dropdown-menu> but instead added it to the embeeded <paper-listbox>. The only disadvantage is that you might need a different ControlValueAccessor if you use a different content for <paper-dropdown-menu>, the advantage is that you can use the ControlValueAccessor with <paper-listbox> even when it's not wrapped in a <paper-dropdown-menu>
import {
Component,
Directive,
Renderer,
forwardRef,
Provider,
ElementRef,
ViewEncapsulation,
} from '#angular/core';
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '#angular/common';
const PAPER_MENU_VALUE_ACCESSOR = new Provider(
NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => PaperMenuControlValueAccessor), multi: true});
#Directive({
selector: 'paper-listbox',
host: {'(iron-activate)': 'onChange($event.detail.selected)'},
providers: [PAPER_MENU_VALUE_ACCESSOR]
})
export class PaperMenuControlValueAccessor implements ControlValueAccessor {
onChange = (_:any) => {
};
onTouched = () => {
};
constructor(private _renderer:Renderer, private _elementRef:ElementRef) {
console.log('PaperMenuControlValueAccessor');
}
writeValue(value:any):void {
//console.log('writeValue', value);
this._renderer.setElementProperty(this._elementRef.nativeElement, 'selected', value);
}
registerOnChange(fn:(_:any) => {}):void {
this.onChange = fn;
}
registerOnTouched(fn:() => {}):void {
this.onTouched = fn;
}
}
#Component({
selector: 'my-app',
directives: [PaperMenuControlValueAccessor],
encapsulation: ViewEncapsulation.None,
template: `
<h2>Hello {{name}}</h2>
<paper-menu>
<paper-item>Item 1</paper-item>
<paper-item>Item 2</paper-item>
</paper-menu>
<paper-dropdown-menu label="Dinosaurs" >
<paper-listbox class="dropdown-content" [(ngModel)]="selected">
<paper-item *ngFor="let item of items">{{item}}</paper-item>
</paper-listbox>
</paper-dropdown-menu>
<div>selected: {{items[selected]}}</div>
`,
})
export class AppComponent {
items = ['allosaurus', 'brontosaurus', 'carcharodontosaurus', 'diplodocus'];
selected = 3;
name:string;
constructor() {
this.name = 'Angular2 (Release Candidate!)'
}
ngAfterViewInit() {
//this.selected = this.diplodocus;
}
}
Plunker example
Update
I found a similar answer for PaperDropdownMenu instead of PaperListbox Bind angular 2 model to polymer dropdown
Related
if I add an object to the objectListDrawArea array outside of the original class, it will be added, but my * ngFor cannot find the object. I have absolutely no idea how to solve it, should I use Observable, if so can you leave an example in the comments? Thank you
ao-bar.component.ts
import { AoBarService } from './ao-bar.service';
import { DrawAreaComponent } from '../../../draw-area/draw-area/draw-area.component';
#Component({
selector: 'app-ao-bar',
templateUrl: './ao-bar.component.html',
styleUrls: ['./ao-bar.component.sass']
})
export class AoBarComponent implements OnInit {
objectsList: object[] = new Array();
showObjectsList: boolean;
drawAreaComponent: DrawAreaComponent = new DrawAreaComponent();
constructor(private service: AoBarService) {
this.service.getObject(this.objectsList);
}
ngOnInit() {
console.log(this.objectsList, ' AoBarComponent');
}
private onShowObjectsList() {
this.showObjectsList = !this.showObjectsList;
}
public onDragEnd(event: DragEvent): void {
console.log('drag end', event);
if (this.drawAreaComponent.onDragEnter) {
for (let object of this.objectsList) {
if (object.name == event.path[0].nextElementSibling.innerText) {
object.settings.x = event.x;
object.settings.y = event.y;
this.drawAreaComponent.createObject(object);
}
}
}
}
}
draw-area.component.ts:
import { Component, OnInit } from '#angular/core';
import { CdkDragEnd } from '#angular/cdk/drag-drop';
#Component({
selector: 'app-draw-area',
templateUrl: './draw-area.component.html',
styleUrls: ['./draw-area.component.sass']
})
export class DrawAreaComponent implements OnInit {
objectsList: object[] = new Array();
constructor() {
}
ngOnInit() {
}
public onDragEnter(event: DragEvent): boolean {
console.log('drag enter', event);
return true;
}
public onDragEnd(event: CdkDragEnd): void {
console.log(event);
}
public createObject(objectToCreate: object): void {
this.objectsList.push(objectToCreate);
console.log(`Aktuelle Liste: ${this.objectsList}`);
}
}
draw-area.component.html :
<div id="DrawAreaComponent">
<div
class="example-boundary"
(dragenter)="onDragEnter($event)"
>
<div
id="ContainerObject"
*ngFor="let object of objectsList | async"
cdkDragBoundary=".example-boundary"
cdkDrag
(cdkDragEnded)="onDragEnd($event)"
>
<img id="ImgObject" [src]="object.imgUrl">
</div>
</div>
</div>
this is a change detection issue.
instead of this.objectsList.push(objectToCreate);
use this.objectsList = [...this.objectsList, objectToCreate]; it will work.
read about change detection.
Component HTML
<div>
<select name="cos">
<option selected="selected" >Wybierz kino</option>
<option *ngFor="let kino of kina "[value]="kino.id">{{ kino.name }} | {{ kino.id }}</option>
</select>
<div *ngIf="kino.id" *ngFor="let kin of kina.cinemaProgramme.programmeItems" style="color:white;">
{{ kin.movie.title }}
</div>
</div>
Component TS
import { Component, OnInit } from '#angular/core';
import { ProgrammeService } from '../programme.service';
import { Time } from '#angular/common';
#Component({
selector: 'app-repertuar',
templateUrl: './repertuar.component.html',
styleUrls: ['./repertuar.component.css']
})
export class RepertuarComponent implements OnInit {
film: CinemaProgramme[];
repertuar: CinemaProgramme[];
kina: Cinema[];
programy: Array<ProgrammeItems> = [];
getCinemaProgramme(): void {
this.programmeService.getCinemaProgramme().
subscribe(repertuar => this.repertuar = repertuar);
}
getCinema(): void {
this.programmeService.getCinema().
subscribe(kina => this.kina = kina);
}
getCinemaPrograme(): void {
this.programmeService.getCinemaPrograme().
subscribe(film => this.film = film);
}
getRepertuar(): void {
this.programmeService.getRepertuar().
subscribe(programy => this.programy = programy);
}
constructor(private programmeService: ProgrammeService) { }
ngOnInit() {
this.getCinemaProgramme();
this.getCinema();
}
}
export interface Cinema {
name: string;
id: number;
cinemaProgramme: CinemaProgramme;
}
export interface CinemaProgramme {
id: number;
programmeItems: Array<ProgrammeItems> ;
}
export interface ProgrammeItems {
movie: Movie;
hours: Date[];
}
export interface Movie {
id?: number;
title?: string;
director?: string;
length?: Time;
description?: string;
}
Service
import { Injectable } from '#angular/core';
import { of, Observable } from 'rxjs';
import { ProgrammeItems, CinemaProgramme, Cinema } from './repertuar/repertuar.component';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class ProgrammeService {
private url = 'http://localhost:8080/';
getCinemaPrograme(): Observable<CinemaProgramme[]> {
return this.http.get<CinemaProgramme[]>(this.url + 'cinema/getAll');
}
getCinemaProgramme(): Observable<CinemaProgramme[]> {
return this.http.get<CinemaProgramme[]>('http://localhost:8080/programme/get/6');
}
getCinema(): Observable<Cinema[]> {
return this.http.get<Cinema[]>('http://localhost:8080/cinema/getAll');
}
getRepertuar(): Observable<Array<ProgrammeItems>> {
return this.http.get<Array<ProgrammeItems>>(this.url + 'programme/getAll');
}
constructor(private http: HttpClient) { }
}
I was thinking about getting the cinema id (cinema_name)
which would be in [value] = "kina.id"
and apply it to dependencies in displaying a given repertoire but even JSON's properties from the second ngfora are not displayed at all: / How should I do it ?? ;/
And sorry for my english.
You second div (second ngFor) is not displayed because you have *ngIf="kino.id" but kino is only defined inside
<option *ngFor="let kino of kina "[value]="kino.id">{{ kino.name }} | {{ kino.id }}</option>
and each option created has it's own kino.id. But outside the option kino is undefined. So your condition is false. If you want to check the value selected in your div (second *ngFor) you should declare a variable in your ts a variable to keep the selected id. So something like:
film: CinemaProgramme[];
repertuar: CinemaProgramme[];
kina: Cinema[];
programy: Array<ProgrammeItems> = [];
selectedKinoId: "";
_______________________
and than use it in your selector like this:
<select [(ngModel)]="selectedKinoId" name="cos">...</select>
and your condition in the div with the second *ngIf would become:
<div *ngIf="selectedKinoId"
Please also note that kina.cinemaProgramme does not exists since kina is an array of Cinema elements. So you will probably need a function to get the cinema by id.
I would recoment adding a function like this to your Component TS:
getCinemaById(id){
for(let kin of kina) {
if(kin.id == id) {
return kin;
}
}
}
So div with the second *ngIf would become
<div *ngIf="selectedKinoId" *ngFor="let kin of getCinemaById(selectedKinoId).cinemaProgramme.programmeItems" style="color:white;">
{{ kin.movie.title }}
</div>
I am trying to implement a solution with ag-grid and not got stuck into a problem. I am trying to implement edit and delete button in each row .edit button implementation is successful but problem is with delete button. I have tried best of my knowledge (which is little for angular 2) .Please see the implementation as per below code:-
court.component.ts
import { Component } from '#angular/core';
import { Court } from './court.model';
//import './../utils/array.extensions';
import { GridOptions } from "ag-grid";
import { DataCourtService } from '../services/data-court.service';
import { EditButtonComponent } from "../utils/editbutton.component";
#Component({
selector: 'court',
template: require('./court.component.html'),
providers: [DataCourtService]
})
export class CourtComponent {
private gridOptions: GridOptions;
public courts : Court[];
onQuickFilterChanged(event: any) {
this.gridOptions.api.setQuickFilter(event.target.value);
}
constructor() {
var courtservice = new DataCourtService();
this.gridOptions = {
rowSelection: 'single'
};
// this.gridOptions.angularCompileRows = true;
this.gridOptions.columnDefs = [
{
headerName: "Court Name",
field: "courtname",
editable: true
} ,
{
headerName: "Action",
cellRendererFramework: EditButtonComponent,
colId: "edit"
}
];
this.gridOptions.rowData = courtservice.getCourt();
}
}
EditButton.component.ts
import {Component} from "#angular/core";
import {ICellRendererAngularComp} from "ag-grid-angular/main";
#Component({
selector: 'edit-button',
template: `<button (click)="invokeEditMethod()" class="btn btn-primary btn-xs"><span class="glyphicon glyphicon-edit"></span>Edit</button>
<button (click)="invokeDeleteMethod()" class="btn btn-danger btn-xs"><span class="glyphicon glyphicon-remove"></span>Delete</button>`
})
export class EditButtonComponent implements ICellRendererAngularComp {
public params: any;
agInit(params: any): void {
this.params = params;
}
public invokeDeleteMethod() {
var selectedData = this.params.api.getSelectedRows();
this.params.api.updateRowsData({remove: selectedData});
alert("hi");
}
public invokeEditMethod() {
this.params.api.setFocusedCell(this.params.node.rowIndex, 'courtname');
this.params.api.startEditingCell({
rowIndex: this.params.node.rowIndex,
colKey: 'courtname',
}
);
}
}
In this function
public invokeDeleteMethod() {
var selectedData = this.params.api.getSelectedRows();
this.params.api.updateRowsData({remove: selectedData});
alert("hi");
}
I am recieving an error as UpdateRowData is not an function. Can you please help me to achieve this??
This function was introduced in aggrid 10+ and I am using 8+.updating it resolved the issue.
The real problem with that code is that
this.params.api.updateRowsData({remove: selectedData});
Expects an array in version 22.X.X
this.params.api.updateRowsData({remove: [selectedData]});
I am using observable in angular .Actually my issue when I click button my subscribe function not called why ?
as per documentation subscribe function will call when we call next function
https://plnkr.co/edit/83NaHoVaxiXAeUFoaEmb?p=preview
constructor() {
this.data = new Observable(observer => this.dataObserver = observer);
this.data.subscribe(value => {
console.log('+++')
console.log(value)
})
}
hndle(){
this.name.push({name:"navee"});
this.dataObserver.next(this.name);
}
here is documentation
http://reactivex.io/rxjs/manual/tutorial.html
On basis of Volodymyr Bilyachat suggestion i have modified your code. its working now plz check. Problem was in your way of using dataObserver
//our root app component
import {Component, NgModule} from '#angular/core'
import {BrowserModule} from '#angular/platform-browser'
import 'rxjs/Rx';
import {Observable} from 'rxjs/Observable';
#Component({
selector: 'my-app',
template: `
<div>
<ul>
<li *ngFor ="let n of name">{{n.name}}</li>
</ul>
<button (click)="hndle()">heelo</button>
</div>
`,
})
export class App {
private data:Observable;
private dataObserver:Observer;
name:string;
name[];
constructor() {
this.dataObserver = new Observable(observer => this.dataObserver = observer);
this.dataObserver.subscribe(value => {
console.log('+++')
console.log(value)
});
}
hndle(){
this.name.push({name:"navee"});
this.dataObserver.next(this.name);
}
}
#NgModule({
imports: [ BrowserModule ],
declarations: [ App ],
bootstrap: [ App ]
})
export class AppModule {}
link https://plnkr.co/edit/PO80y2udrOhsVq4QQXc5?p=preview
I believe you are subscribing to the observable 2 times. You should be able to fix it by adding .share()
constructor() {
this.data = new Observable(observer => this.dataObserver = observer).share();
this.data.subscribe(value => {
console.log('+++')
console.log(value)
})
}
hndle(){
this.name.push({name:"navee"});
this.dataObserver.next(this.name);
}
In your case, it's better to use this solution:
constructor() {
this.data = new Subject();
this.data.subscribe(value => {
console.log('+++');
console.log(value);
});
}
hndle() { // TYPO: Probably it was meant to be handle
this.name.push({
name: 'navee'
});
this.data.next(this.name);
}
Don't forget to add:
import { Subject } from 'rxjs/Subject'
Working example:
https://plnkr.co/edit/zB8FHTVEm2QUHiEAYuQB?p=preview
Is there any angular multiselect controller that lets you insert options on the go?
I need to start from a list, let's say:
Option A
Option B
Option C
But the user might insert new items like:
Option D
Option E
And delete some others, like:
Option A
Option C
So the final list will be:
Option B
Option D
Option E
Perhap I am confusing the name and it is not multiselect, it is just a dropdown list.
In my current project I am using Select2 and its angular-ui counterpart with success. Maybe this is an option for you.
It works well with ng-model objects.
You are right this is not a multi-select if you're just adding and deleting items.
Just bind an array to an ng-repeat and modify the array using functions in your controller.
<div class="btn-group">
<button type="button" class="btn btn-secondary dropdown-toggle" (click)="toggleSelect()">
<span class="pull-left" [innerHtml]="header"></span>
<span class="caret pull-right"></span>
</button>
<ul class="dropdown-menu multi-select-popup" [ngStyle]="{display:isOpen ? 'block' : 'none'}" style="display:block;">
<li *ngIf="enableFilter" class="filter-container">
<div class="form-group has-feedback filter">
<input class="form-control" type="text" [value]="filterText" [placeholder]="filterPlaceholder" [formControl]="filterInput" />
<span class="clear-filter fa fa-times-circle-o form-control-feedback" (click)="clearFilter()"></span>
</div>
</li>
<li *ngFor="let item of _items | filter:{label:filterText}">
<a (click)="select(item)" class="dropdown-item">
<i class="fa fa-fw" [ngClass]="{'fa-check': item.checked, 'glyphicon-none': !item.checked}"></i>
<span [innerHtml]="item.label"></span>
</a>
</li>
</ul>
</div>
component
import {Component,Input,Output,OnInit,ViewChild,EventEmitter,ChangeDetectionStrategy, ChangeDetectorRef} from '#angular/core';
import {Pipe, PipeTransform} from '#angular/core';
import {Observable} from 'rxjs/Rx';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/throttleTime';
import 'rxjs/add/observable/fromEvent';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { FormGroup, FormControl } from '#angular/forms';
import { ControlValueAccessor } from '#angular/forms';
#Pipe({
name: 'filter'
})
export class FilterPipe implements PipeTransform {
transform(items: any, filter: any): any {
if (filter && Array.isArray(items)) {
let filterKeys = Object.keys(filter);
return items.filter(item =>
filterKeys.reduce((memo, keyName) =>
(memo && new RegExp(filter[keyName], 'gi').test(item[keyName])) || filter[keyName] === "", true));
} else {
return items;
}
}
}
#Component({
selector: 'multiselect',
templateUrl: 'templates/multiselect.html'
})
export class Multiselect implements OnInit {
public _items: Array<any>;
public _selectedItems: Array<any>;
public isOpen: bool = false;
public enableFilter: bool;
public header: string = "Select some stuff";
public filterText: string;
public filterPlaceholder: string;
public filterInput = new FormControl();
private _subscription: Subscription;
#Input() items: Observable<any[]>;
// ControlValueAccessor Intercace and mutator
propagateChange = (_: any) => {};
get selectedItems(): any {
return this._selectedItems;
};
writeValue(value: any) {
if (value !== undefined) {
this._selectedItems = value;
} else {
this._selectedItems = [];
}
}
registerOnChange(fn: any) {
this.propagateChange = fn;
}
registerOnTouched(fn: any) : void
constructor(private changeDetectorRef: ChangeDetectorRef) {
}
select(item: any) {
item.checked = !item.checked;
}
toggleSelect() {
this.isOpen = !this.isOpen;
}
clearFilter() {
this.filterText = "";
}
ngOnInit() {
this._subscription = this.items.subscribe(res => this._items = res);
this.enableFilter = true;
this.filterText = "";
this.filterPlaceholder = "Filter..";
this.filterInput
.valueChanges
.debounceTime(200)
.distinctUntilChanged()
.subscribe(term => {
this.filterText = term;
this.changeDetectorRef.markForCheck();
console.log(term);
});
}
}
Angular JS Multi Select would be a good choice. I used it in my project: http://isteven.github.io/angular-multi-select/#/demo-dynamic-update
I really like angular material way of multiselecting items - check this out
https://material.angularjs.org/latest/demo/select
scroll to Option Groups section - there is also code snippet for doing this kind of thing, have fun!