Cannot read property of undefined Angular 2 - angularjs

I am trying to do a simple import but I am getting a massive stack trace issue.
I have tried searching everywhere for issues related to this but to me, the stack trace doesn't provide much information.
EDIT: I have tried setting it a variable that isn't fetched from Firebase and it works fine. I guess the question now is how do I handle this information from Firebase so that it loads when it is ready.
Here are the relevant files:
main.ts:
import { bootstrap } from '#angular/platform-browser-dynamic';
import {AppComponent} from './app.component';
import { HTTP_PROVIDERS } from '#angular/http';
bootstrap(AppComponent, [HTTP_PROVIDERS]);
player.services.ts:
import { Injectable } from '#angular/core';
import {Player} from "../classes/player";
#Injectable()
export class PlayerService {
player: Player;
getPlayer()
{
return Promise.resolve(this.player);
}
createPlayer(uid: string, name: string, firebaseRef: Firebase)
{
this.player = {
'uid': uid,
'name': name,
'level': 1,
'maxHealth': 100,
'health': 100,
'maxEnergy': 50,
'energy': 50,
'fun': 1,
'skill': 1,
'knowledge': 1
}
firebaseRef.child('players').child(uid).set(this.player);
}
setPlayer(player: Player)
{
this.player = player;
}
}
app.component.ts
import { Component, OnInit } from '#angular/core'
import { PlayerDetailComponent } from './components/player-detail.component';
import {PlayerService} from "./services/player.service";
import {FirebaseEventPipe} from "./firebasepipe";
import {Player} from "./classes/player";
#Component({
selector: "my-app",
templateUrl: 'app/views/app.component.html',
directives: [PlayerDetailComponent],
providers: [PlayerService],
pipes: [FirebaseEventPipe]
})
export class AppComponent implements OnInit{
title = "title";
authData: any;
private firebaseUrl: string;
private firebaseRef: Firebase;
private loggedIn = false;
player: Player;
constructor(private playerService: PlayerService) {
this.firebaseUrl = "https://!.firebaseio.com/";
this.firebaseRef = new Firebase(this.firebaseUrl);
this.firebaseRef.onAuth((user) => {
if (user) {
this.authData = user;
this.loggedIn = true;
}
});
}
getPlayer() {
this.firebaseRef.once("value", (dataSnapshot) => {
if (dataSnapshot.child('players').child(this.authData.uid).exists()) {
this.firebaseRef.child('players').child(this.authData.uid).once("value", (data) => {
this.player = data.val();
this.playerService.setPlayer(this.player);
console.log(this.player);
});
} else {
this.playerService.createPlayer(this.authData.uid, this.getName(this.authData), this.firebaseRef);
this.playerService.getPlayer().then(player => this.player);
console.log(this.player);
}
});
}
ngOnInit() {
this.getPlayer();
}
authWithGithub() {
this.firebaseRef.authWithOAuthPopup("github", (error) =>
{
if (error) {
console.log(error);
}
});
}
authWithGoogle() {
this.firebaseRef.authWithOAuthPopup("google",(error) =>
{
if (error) {
console.log(error);
}
});
}
getName(authData: any) {
switch (authData.provider) {
case 'github':
return authData.github.displayName;
case 'google':
return authData.google.displayName;
}
}
}
player-detail.component.ts
import { Component, Input, OnInit } from '#angular/core';
import { Player } from '../classes/player';
#Component({
selector: "player-details",
templateUrl: "app/views/player-detail.component.html",
styleUrls: ['app/style/player-detail.component.css'],
})
export class PlayerDetailComponent implements OnInit{
#Input() player: Player;
ngOnInit() { console.log(this.player)}
}
app.component.html
<nav class="navbar navbar-default">
<div class="container">
<ul class="nav navbar-nav">
<li class="navbar-link">Home</li>
</ul>
</div>
</nav>
<div class="jumbotron" [hidden]="loggedIn">
<div class="container">
<h1>Angular Attack Project</h1>
<p>This is a project for the Angular Attack 2016 hackathon. This is a small project where set goals
in order to gain experience as a player and person. In order to begin, please register with on of the following services</p>
<button class="btn btn-social btn-github" (click)="authWithGithub()"><span class="fa fa-github"></span>Sign Up With Github </button>
<button class="btn btn-social btn-google" (click)="authWithGoogle()"><span class="fa fa-google"></span>Sign Up With Github </button>
</div>
</div>
<player-details [player]="player" [hidden]="!loggedIn"></player-details>
player-detail.component.html
<div id="player" class="panel panel-default">
<div id="player-stats" class="panel-body">
<img id="player-image" class="img-responsive" src="../app/assets/images/boy.png"/>
<div class="health-bars">
<div class="health-bar">HEALTH:<br/><progress value="{{ player.health }}" max="{{ player.maxHealth }}"></progress></div>
<div class="energy-bar">ENERGY:<br/><progress value="{{ player.energy }}" max="{{ player.maxEnergy }}"></progress></div>
<div class="player-attributes"><span class="fa fa-futbol-o player-attr fun">: {{ player.fun }} </span><span class="fa fa-cubes player-attr skill">: {{ player.skill }}</span> <span class="fa fa-graduation-cap player-attr knowledge">: {{ player.knowledge }}</span></div>
</div>
</div>
</div>

In your service you don't have to return with the promise. You can use a getter
private player: Player;
get CurrentPlayer()
{
return this.player;
}
Then in your component:
getPlayer() {
this.firebaseRef.once("value", (dataSnapshot) => {
if (dataSnapshot.child('players').child(this.authData.uid).exists()) {
this.firebaseRef.child('players').child(this.authData.uid).once("value", (data) => {
this.playerService.setPlayer(this.player);
console.log(this.player);
});
} else {
this.playerService.createPlayer(this.authData.uid, this.getName(this.authData), this.firebaseRef);
console.log(this.player);
}
});
ngOnInit() {
this.player = this.playerService.CurrentPlayer();
this.getPlayer();
}
If you setup the reference first, it should automatically update. You can also throw an *ngIf player-details component definition in the DOM and only show it once the player object isn't undefined.
Edit
Just saw someone else posted about *ngIf prior to me, so if that is the solution, please mark theirs.

The player variable was undefined when the PlayerDetailComponent was loaded therefore there was no such object as player.
To fix this, OnChanges can be implemented like this:
import { Component, Input, OnChanges, SimpleChange } from '#angular/core';
import { Player } from '../classes/player';
import {HealthBarComponent} from "./health-bar.component";
import {ChecklistComponent} from "./checklist.component";
#Component({
selector: "player-details",
templateUrl: "app/views/player-detail.component.html",
styleUrls: ['app/style/player-detail.component.css'],
directives: [HealthBarComponent, ChecklistComponent],
})
export class PlayerDetailComponent implements OnChanges{
#Input()
player: Player;
ngOnChanges(changes: {[propName: string]: SimpleChange}) {
}
}
and then we can add *nfIf="player" within the template to ensure that the player object isn't blank before loading the element.

Related

Ionic5 FavoriteService that doesn’t duplicate

I’ve been following tutorials to create an Ionic5 StarWars app and although it’s almost completed, have ran into trouble understanding how I can get ‘favorite’ star button on all tabs (ie) Films, People, Planets. I'm not very familiar with Ionic5 and trying to figure out how to add favorite buttons across all tabs. Have done a lot of research and spent time trying to get this to work.
So far there is only a function to ‘favorite’ films and not people or planets. When I try to replicate the code for Films to extend to people and planets, I can’t and get errors that duplication is not allowed.
Would really appreciate any help with this, as I want to get all - Films, People and Planets to be starred as favorites. Thanks for any help with this.
The code in favorite.service.ts is as follows:-
import { Injectable } from '#angular/core';
import { Storage } from '#ionic/storage';
const STORAGE_KEY = 'favoriteFilms';
#Injectable({
providedIn: 'root'
})
export class FavoriteService {
constructor(private storage: Storage) {
}
getAllFavoriteFilms() {
return this.storage.get(STORAGE_KEY);
}
isFavorite(filmId) {
return this.getAllFavoriteFilms().then(result => {
return result && result.indexOf(filmId) !== -1;
});
}
favoriteFilm(filmId) {
return this.getAllFavoriteFilms().then(result => {
result = result || [];
result.push(filmId);
return this.storage.set(STORAGE_KEY, result);
});
}
unfavoriteFilm(filmId) {
return this.getAllFavoriteFilms().then(result => {
if (result) {
var index = result.indexOf(filmId);
result.splice(index, 1);
return this.storage.set(STORAGE_KEY, result);
}
})
}
}
This is exactly how I tried to import the service into the components:-
import { Injectable } from '#angular/core';
import { Storage } from '#ionic/storage';
import { SQLite, SQLiteObject } from '#ionic-native/sqlite';
const STORAGE_KEY = 'favoriteFilms';
const STORAGE_KEY1 = 'favoritePlanets';
#Injectable({
providedIn: 'root'
})
export class FavoriteService {
constructor(private storage: Storage) {
}
getAllFavoriteFilms() {
return this.storage.get(STORAGE_KEY);
}
getAllFavoritePlanets() {
return this.storage.get(STORAGE_KEY1);
}
isFavorite(filmId) {
return this.getAllFavoriteFilms().then(result => {
return result && result.indexOf(filmId) !== -1;
});
}
isFavorite(planetId) {
return this.getAllFavoritePlanets().then(result => {
return result && result.indexOf(planetId) !== -1;
});
}
favoriteFilm(filmId) {
return this.getAllFavoriteFilms().then(result => {
result = result || [];
result.push(filmId);
return this.storage.set(STORAGE_KEY, result);
});
}
favoritePlanet(planetId) {
return this.getAllFavoriteFilms().then(result => {
result = result || [];
result.push(planetId);
return this.storage.set(STORAGE_KEY1, result);
});
}
unfavoriteFilm(filmId) {
return this.getAllFavoriteFilms().then(result => {
if (result) {
var index = result.indexOf(filmId);
result.splice(index, 1);
return this.storage.set(STORAGE_KEY, result);
}
})
}
unfavoriteFilm(planetId) {
return this.getAllFavoritePlanets().then(result => {
if (result) {
var index = result.indexOf(planetId);
result.splice(index, 1);
return this.storage.set(STORAGE_KEY1, result);
}
})
}
}
This is the error message I am getting (x4 times) for each duplication:-
Duplicate function implementation. ts(2393)
The components page (planet-details.page.ts) is as follows:-
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { ApiService } from 'src/app/services/api.service';
import { EmailComposer } from '#ionic-native/email-composer/ngx';
import { FavoriteService } from 'src/app/services/favorite.service';
#Component({
selector: 'app-planet-details',
templateUrl: './planet-details.page.html',
styleUrls: ['./planet-details.page.scss'],
})
export class PlanetDetailsPage implements OnInit {
planet: any;
isFavorite = false;
planetId = null;
constructor(private activatedRoute: ActivatedRoute, private api: ApiService,
private emailComposer: EmailComposer, private favoriteService: FavoriteService) { }
ngOnInit() {
let id = this.activatedRoute.snapshot.paramMap.get('id');
this.api.getPlanet(id).subscribe(res => {
this.planet = res;
console.log(res);
});
}
favoritePlanet() {
this.favoriteService.favoritePlanet(this.planetId).then(() => {
this.isFavorite = true;
});
}
unfavoritePlanet() {
this.favoriteService.unfavoritePlanet(this.planetId).then(() => {
this.isFavorite = false;
});
}
sharePlanet() {
let email = {
to: "",
subject: `I love this planet: ${this.planet.name}`,
body: `Do you like it?<br><br>"${this.planet.opening_crawl}"`,
isHtml: true
};
this.emailComposer.open(email);
}
}
The planet-details.page.html is as follows:-
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button defaultHref="/tabs/planets"></ion-back-button>
</ion-buttons>
<ion-title>{{ planet?.title }}</ion-title>
<ion-buttons slot="end">
<ion-button (click)="unfavoritePlanet()" *ngIf="isFavorite">
<ion-icon name="star" slot="icon-only" color="secondary"></ion-icon>
</ion-button>
<ion-button (click)="favoritePlanet()" *ngIf="!isFavorite">
<ion-icon name="star-outline" slot="icon-only"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content padding>
<ion-card *ngIf="planet" class="planet-card">
<ion-item class="planet-card" lines="none">
<ion-icon name="cloudy-night-outline" slot="start"></ion-icon>
Climate for {{ planet.name }}: {{ planet.climate }}
</ion-item>
<ion-item class="planet-info" lines="none">
<ion-icon name="planet" slot="start"></ion-icon>
Rotation Period: {{ planet.rotation_period }}
</ion-item>
<ion-item class="planet-info1" lines="none">
<ion-icon name="people-outline" slot="start"></ion-icon>
Population: {{ planet.population }}
</ion-item>
</ion-card>
<ion-button expand="full" (click)="sharePlanet()">Share by Email</ion-button>
</ion-content>
The two errors I am getting are same two errors as outlined above (Duplicate Function Implementation) ts.2393 in favorite.service.ts, but now only get 2 errors, instead of 4. Both errors are due to repetition of 'isFavorite(filmId)' and 'isFavorite(planetId).
ERROR in src/app/services/favorite.service.ts:24:3 - error TS2393:
Duplicate function implementation. [ng] [ng] 24
isFavorite(filmId) { [ng] ~~~~~~~~~~ [ng]
src/app/services/favorite.service.ts:30:3 - error TS2393: Duplicate
function implementation. [ng] [ng] 30 isFavorite(planetId)
{ [ng] ~~~~~~~~~~

Angular - How can I change the data downloaded from JSONA depending on the options in the selection

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>

Angular displaying correct array item in buttons

I have an array with 4 items, and 4 buttons on a dashboard. I want to assign item1 with button1, and item2 with button2 etc. Right now it displays 4 buttons for each "hero" for a total of 16 buttons. I tried {{hero.name[2]}} and similar things but that just grabs letters and not the actual array items. I would appreciate any help.
dashboard.component.html
<h3>Calibrations</h3>
<div class="grid grid-pad">
<a *ngFor="let hero of heroes" [routerLink]="['/detail', hero.id]">
<button style ="min-height: 70px" (click)="gotoClick(1)">{{hero.name}}</button>
<button style ="min-height: 70px" (click)="gotoClick(2)">{{hero.name}}</button>
<button style ="min-height: 70px" (click)="gotoClick(3)">{{hero.name}}</button>
<button style ="min-height: 70px" (click)="gotoClick(4)">{{hero.name}}</button>
</a>
</div>
dashboard.component.ts
import { Component, OnInit } from '#angular/core';
import { Hero } from '../hero.class';
import { HeroService } from '../hero.service';
import { StepService } from '../step.service';
#Component({
moduleId: module.id,
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
heroes: Hero[] = [];
constructor(private heroService: HeroService, private _stepService: StepService, private _teststepService: StepService) { }
ngOnInit() {
this.heroService.getHeroes().subscribe(heroes => this.heroes = heroes);
}
private test: number;
gotoClick(value: number){
this._stepService.setTest(value);
this._teststepService.apiURL();
}
}
hero.service.ts
import { Injectable } from '#angular/core';
import { Headers, Http, Response } from '#angular/http';
import { Hero } from './hero.class';
import { Observable } from "rxjs/Rx";
#Injectable()
export class HeroService {
private headers = new Headers({'Content-Type': 'application/json'});
private heroesUrl = 'api/heroes'; // URL to web api
constructor(private http: Http){ }
getHeroes(): Observable<Hero[]> {
return this.http.get(this.heroesUrl)
.map(response => response.json().data as Hero[]);
}
getHero(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/${id}`;
return this.http.get(url)
.map(response => response.json().data as Hero);
}
}
You can use the built-in index property of *ngFor:
<div class="grid grid-pad">
<a *ngFor="let hero of heroes; let i = index;" [routerLink]="['/detail', hero.id]">
<button style ="min-height: 70px" (click)="gotoClick(i)">{{hero?.name}</button>
</a>
</div>
Documentation: https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html

Ionic2, Http request don´t work correctly in starting page

I created a simple app (sidemenu template) and a provider (conexao-home.ts). In a new page called teste i created a function buscarUsuarios (associated a one button), and it calls the function getRemoteUsers in provider.
In ionViewDidLoad i put the same call to function getRemoteUsers.
When the page teste starts, it makes the the call to function and read data from http, but don´t return in the back variable data read.
When i make the call from button, it returns the data from the first read and show it in the page.
How to solve this?
teste.ts
import { Component } from '#angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { ConexaoHome } from '../../providers/conexao-home';
#Component({
selector: 'page-teste',
templateUrl: 'teste.html',
})
export class Teste {
public users: any;
public teste: any;
constructor(public navCtrl: NavController, public navParams: NavParams, public conexaoServico: ConexaoHome) {
}
buscarUsuarios() {
this.users = this.conexaoServico.getRemoteUsers('Pegando os usuários');
console.log('chamando...');
console.log(this.users);
console.log('retornando...' + this.users);
}
buscar() {
this.teste = this.conexaoServico.getRemoteTeste('testando...');
console.log(this.teste);
}
ionViewDidLoad() {
console.log('ionViewDidLoad Teste');
//this.buscarUsuarios();
this.users = this.conexaoServico.getRemoteUsers('Pegando os usuários');
console.log(this.users);
}
}
teste.html
<ion-header>
<ion-navbar>
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Teste</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding="false">
<button ion-button (click)="buscarUsuarios()">Pegar Dados</button>
<br>
<button ion-button (click)="buscar()">Pegar Dados 2</button>
{{ teste }}
<br>
<ion-list>
<button ion-item *ngFor="let user of users">
<ion-avatar item-left>
<img src="{{ user.picture.medium }}">
</ion-avatar>
<h2 text-wrap>{{ user.name.title }} {{ user.name.first }} {{ user.name.last }}</h2>
<h3 text-wrap>{{ user.email }}</h3>
</button>
</ion-list>
</ion-content>
provider conexao-home.ts
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class ConexaoHome {
public usuarios: any;
public areas: any;
constructor(public http: Http) {
console.log('Hello ConexaoHome Provider');
}
getRemoteUsers(tipo) {
this.http.get('https://randomuser.me/api/?results=10').
map(res => res.json()
).subscribe(data => {
console.log(data.results);
console.log(tipo);
this.usuarios = data.results;
});
return this.usuarios;
}
getRemoteTeste(tipo) {
console.log(tipo);
return ('teste executado 2');
}
}
Tks.
You cannot do like that:
getRemoteUsers(tipo) {
this.http.get('https://randomuser.me/api/?results=10').
map(res => res.json()
).subscribe(data => {
console.log(data.results);
console.log(tipo);
this.usuarios = data.results;
});
return this.usuarios;
}
That could finish return statement before async call has finished. Because you made an asynchronous http request via Observable. You should read more about that here
Instead please try something like this:
getRemoteUsers(tipo) {
return this.http
.get('https://randomuser.me/api/?results=10')
.map(res => res.json())
}
Then you should use it like this:
ionViewDidLoad() {
this.conexaoServico.getRemoteUsers('Pegando os usuários').subscribe((data) => {
this.users = data;
console.log(this.users);
});
}

Angular multiselect

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!

Resources