Skip to content

Instantly share code, notes, and snippets.

@willSonic
Last active July 3, 2016 23:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save willSonic/474d063e4f8633ccc3337b353939ed21 to your computer and use it in GitHub Desktop.
Save willSonic/474d063e4f8633ccc3337b353939ed21 to your computer and use it in GitHub Desktop.
Pieces of my plunker http://plnkr.co/edit/RLKNdm?p=preview that I am using to help answer questions about notifying a web component of a state change
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mapTo';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/switchMapTo';
import 'rxjs/add/operator/toArray';
import 'rxjs/add/observable/of';
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Effect, StateUpdates, toPayload } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { AppState, AudioAlbumState } from '../reducers';
import { AudioItemServices } from '../services/audioItemServices';
import { WebAudioServices } from '../services/webAudioServices';
import { ArtistService } from '../services/artistService';
import { AudioArtistActions} from '../actions/audioArtistActions';
import { AudioItemActions} from '../actions/audioItemActions';
import { AudioAlbumActions} from '../actions/audioAlbumActions';
import { AudioArtist, AudioItem} from '../models';
@Injectable()
export class AudioArtistEffects {
constructor(
private updates$: StateUpdates<AppState>,
private audioItemServices:AudioItemServices,
private artistService: ArtistService,
private webAudioServices:WebAudioServices;
private audioItemActions: AudioItemActions,
private audioArtistActions: AudioArtistActions,
private audioAlbumActions:AudioAlbumActions
) { }
@Effect() loadAudioArtistsInit$ = Observable.of(this.audioArtistActions.fetchAudioArtist());
@Effect() fetchAudioArtists = this.updates$
.whenAction(AudioArtistActions.FETCH_AUDIO_ARTIST)
.mergeMap(() => this.artistService.requestArtist())
.map((audioArtists:AudioArtist[]) => this.audioArtistActions.fetchAudioArtistComplete(audioArtists))
.catch(() => Observable.of(this.audioArtistActions.fetchAudioArtistComplete([]) ));
@Effect() createAudioAlbums = this.updates$
.whenAction(AudioArtistActions.AUDIO_ARTIST_FETCH_COMPLETE)
.map<AudioArtist[]>(toPayload)
.map((audioArtists:AudioArtist[]) => this.audioAlbumActions.createAudioAlbumList(audioArtists));
@Effect() createAudioItems = this.updates$
.whenAction(AudioAlbumActions.CREATE_AUDIOALBUM_LIST)
.mergeMap(() =>this.audioItemServices.getAudioItems())
.map((audioAlbums:AudioAlbum[]) => this.audioItemActions.createAudioItemList(audioAlbums));
@Effect() downLoadAudioItem = this.updates$
.whenAction(AudioItemActions.BEGIN_AUDIOITEM_DOWNLOAD)
.map<AudioItem>(toPayload)
.mergeMap((audioItem:AudioItem) => this.webAudioServices.downLoadAudioItem(audioItem))
.map(audioItem => this.audioItemActions.audioItemDownLoadComplete(audioItem)) );
}
import { Component, Input,Output, EventEmitter } from '@angular/core';
import { AudioItemViewComponent, AudiItemInput } from './audioItemViewComponent';
export type AudioItemsInput = AudioItemInput[];
@Component({
selector: 'audioitems-list',
directives: [ AudioItemViewComponent ],
template: `
<audioitem-view
(changeState) = "changeState.emit($event)"
*ngFor="let audioItem of audioItems" [audioItem]="audioItem"></audioitem-view>
`,
styles: [`
.demo-list-icon {
width: 400px;
}
`]
})
export class AudioItemListComponent {
@Input() audioItems: AudioItemsInput;
@Output() changeState = new EventEmitter<Album>();
}
import '@ngrx/core/add/operator/select';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Observable';
import { Action } from '@ngrx/store';
import { uuid } from '../utils/uuid';
import { AudioAlbum } from '../models/index';
import { AudioItemActions } from '../actions/audioItemActions';
export interface AudioItemState {
ids: string[];
entities: { [id: string]: AudioItem };
};
const initialState: AudioItemState = {
ids: [],
entities: {}
};
export default function(state = initialState, action: Action): AudioItemState {
switch (action.type) {
case AudioItemActions.CREATE_AUDIOITEM_LIST:{
const audioAlbums:AudioAlbum[] = action.payload;
const audioItems:AudioItems[] = audioAlbums.map(audioAlbum => {
return Object.assign( {}, {id:uuid(),
audioAlbumId:audioAlbum.id,
albumImgSrc: audioAlbum.albumImgSrc,
trackURL:audioAlbum.trackURL,
artistAudioBuffer:null,
loadProgress:0;
downloadComplete:false,
isPlaying:false,
currentPosition:0})});
const newAudioItemIds = audioItems.map(audioItem => audioItem.id);
const newAudioItemEntities = audioItems.reduce((entities: { [id: string]: AudioItem }, audioItem: AudioItem) => {
return Object.assign(entities, {
[audioItem.id]: audioItem
});
}, {});
return {
ids: [ ...state.ids, ...newAudioItemIds ],
entities: Object.assign({}, state.entities, newAudioItemEntities)
};
}
case AudioItemActions.BEGIN_AUDIOITEM_DOWNLOAD:{
const audioItem: AudioItem = action.payload;
// console.log('[audioItemReducer.ts]--- BEGIN_AUDIOITEM_DOWNLOAD--- audioItem',audioItem);
if (state.ids.includes(audioItem.id)) {
return state;
}
}
case AudioItemActions.PROGRESS_OF_AUDIOITEM_DOWNLOAD:{
const audioItem: AudioItem = action.payload;
if (state.ids.includes(audioItem.id)) {
console.log('[audioItemReducer.ts]--- PROGRESS_OF_AUDIOITEM_DOWNLOAD--- audioItem.loadProgress='+audioItem.loadProgress);
return {
ids: [ ...state.ids],
entities: Object.assign({}, state.entities, { [audioItem.id]: audioItem})
};
}
}
case AudioItemActions.AUDIOITEM_DOWNLOAD_COMPLETE:{
const audioItem: AudioItem = action.payload;
if (state.ids.includes(audioItem.id)) {
console.log('[audioItemReducer.ts]--- AUDIOITEM_DOWNLOAD_COMPLETE--- audioItem = ', audioItem);
return {
ids: [ ...state.ids],
entities: Object.assign({}, state.entities, { [audioItem.id]: audioItem})
};
}
}
default: {
return state;
}
}
}
export function getAudioItemEntities() {
return (state$: Observable<AudioItemState>) => state$
.select(s => s.entities);
};
export function getAudioItemIds() {
return (state$: Observable<AudioItemState>) => state$
.select(s => s.ids);
};
export function getAudioItem(id: string) {
return (state$: Observable<AudioItemState>) => state$
.select(s => s.entities[id]);
};
export function getAudioItems(audioItemIds: string[]) {
return (state$: Observable<AudioItemState>) => state$
.let(getAudioItemEntities())
.map(entities => audioItemIds.map(id => entities[id]));
}
export function hasAudioItem(id: string) {
return (state$: Observable<AudioItemState>) => state$
.select(s => s.ids.includes(id));
}
import {Component, ChangeDetectionStrategy, Output, Input, SimpleChange, OnInit, EventEmitter, ChangeDetectorRef,OnChanges } from "@angular/core";
import { AudioItem } from "../models/audio-item-model";
import { Observable } from 'rxjs/Observable';
export type AudioItemInput = AudioItem;
export type ChangeStateOutput = AudioItem;
@Component({
selector: 'audioitem-view',
inputs: ['audioItem'],
styles: [`
.mdl-list__item-icon, .mdl-list__item-icon.material-icons{
color:#7db500;
}
.mdl-list__item-primary-content{
color:#65788b;
}
img {
width: 64px;
height:64px;
margin-left: 2px;
}
label.base{
color:#7db500;
}
label.loading{
color:#E54028;
}
label.loaded{
color:#4c78a4;
}
.mdl-button--raised.mdl-button--colored:hover,
.mdl-button--raised.mdl-button--colored {
background-color: #7F8D9E;
}
}
`],
template: `
<li class="mdl-list__item">
<span class="mdl-list__item-primary-content">
<img [src]="thumbnail"/>
</span>
<div class="mdl-cell mdl-cell--4-col">
<button class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored"
(click)="changeState.emit(audioItem)">
{{buttonLabel}}
</button>
</div>
<div class="mdl-cell mdl-cell--4-col">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<label class="mdl-textfield__label" [ngClass]="setClasses()"> {{audioItemState}}</label>
</div>
</div>
</li>
`
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AudioItemViewComponent implements onChanges{
@Input('audioItem') _audioItem: AudioItemInput;
audioItem:AudioItemInput;
@Output() changeState: EventEmitter<AudioItem> = new EventEmitter<AudioItem>();
buttonLabel:string;
audioItemState:String;
set _audioItem(value:AudioItem){
this.audioItem = Object.assign({}, value);
this.setClasses();
}
get thumbnail():string{
return this.audioItem.albumImgSrc;
}
downloadComplete(){
this.buttonLabel = (this.audioItem.downloadComplete)? "LOADING":"LOAD";
this.audioItemState = (this.audioItem.loadProgress==0)? "...":this.audioItem.loadProgress;
return this.audioItem.downloadComplete;
}
isPlaying(){
if(this.audioItem.downloadComplete){
this.buttonLabel = (this.audioItem.isPlaying)? "STOP":"PLAY";
this.audioItemState = 'position :'+this.audioItem.currentPosition;
}
return this.audioItem.isPlaying;
}
setClasses() {
let classes = {
base: (this.downloadComplete() == false),
loading: (this.downloadComplete() == true),
loader: (this.isPlaying() == true)
};
return classes;
}
ngOnChanges(changes: {[propName: string]: SimpleChange}): void {
console.log('[AudioItemViewComponent&&&&&&&&&Changes', changes);
console.log('[AudioItemViewComponent]************this.audioItems', this.audioItem);
}
}
import { provideStore, Store } from '@ngrx/store';
import { provide, Input, Directive,HostBinding } from '@angular/core';
import { runEffects } from '@ngrx/effects';
import { Component, Injectable, Output, Input} from '@angular/core';
import { AudioItemServices } from './services/audioItemServices';
import { AppState,AudioItemState, getAudioArtists, getAudioAlbums, getAudioItems, getAudioItemState } from './reducers/index';
import { AudioArtistListComponent, AudioArtistsInput } from './components/audioArtistListComponent';
import { AudioAlbumListComponent, AudioAlbumsInput } from './components/audioAlbumListComponent';
import { AudioItemListComponent, AudioItemsInput } from './components/audioItemListComponent';
@Component({
selector: 'app',
directives: [
AudioArtistListComponent,
AudioAlbumListComponent,
AudioItemListComponent
],
styles:[`
*{font-family: Monaco, Consolas;}
a{
font-size: 9pt;
color: black;
text-decoration: none;
padding: 2px;
}
a:hover{color: blue;}
a::before{content: ">";}
.list{
display: flex;
flex-direction: column;
}
.headline{
color:#2aa4c9;
padding-left:10px;
}
`],
template: `
<div mdl class="demo-layout mdl-layout mdl-js-layout mdl-layout--fixed-drawer mdl-layout--fixed-header">
<header class="demo-header mdl-layout__header mdl-color--grey-100 mdl-color-text--grey-600">
<div class="mdl-layout__header-row">
<span class="mdl-layout-title">NGRX Harness For Plunker</span>
</div>
</header>
<main class="mdl-layout__content mdl-color--grey-12">
<div class="mdl-grid">
<div class="demo-graphs mdl-shadow--2dp mdl-color--white mdl-cell mdl-cell--5-col">
<h5 class="headline"> List of Artists</h5>
<audioartist-list [audioArtists]="audioArtists$ | async"
class="demo-list-icon mdl-list"></audioartist-list>
</div>
<div class="demo-graphs mdl-shadow--2dp mdl-color--white mdl-cell mdl-cell--5-col">
<h5 class="headline">List of Albums</h5>
<audioalbum-list [audioAlbums]="audioAlbums$ | async"
class="demo-list-icon mdl-list"></audioalbum-list>
</div>
<div class="demo-graphs mdl-shadow--2dp mdl-color--white mdl-cell mdl-cell--5-col">
<h5 class="headline">List of Audio Items</h5>
<audioitems-list [audioItems]="audioItems$ | async"
(changeState)="changeStateOfAudioItem($event)"
class="demo-list-icon mdl-list"></audioitems-list>
</div>
</div>
</main>
</div>
`
})
export default class App{
audioArtists$: Observable<AudioArtistsInput>;
audioAlbums$: Observable<AudioAlbumsInput>;
audioItems$: Observable<AudioItemsInput>;
audioItemState$: Observable<AudioItemState>;
constructor(private store: Store<AppState>, private audioItemServices:AudioItemServices) {
this.audioArtists$ = store.let(getAudioArtists());
this.audioAlbums$ = store.let(getAudioAlbums());
this.audioItems$ = store.let(getAudioItems());
this.audioItemState$ = store.let(getAudioItemState());
this.audioItems$.subscribe(state =>{
console.log("[Main.ts] ----App--- this.audioItems$.subscribe state =",state);
});
}
changeStateOfAudioItem(audioItem:AudioItem){
this.audioItemServices.updateAudioItemState(audioItem);
}
}
import {Injectable, bind} from '@angular/core';
import {Http, BrowserXHhr} from "@angular/http";
import {Subject} from "rxjs/Subject";
import {Observable} from 'rxjs/Observable';
import {AudioItem} from "../models/audio-item-model";
import {Store, State, Action} from '@ngrx/store';
import {Subject} from "rxjs/Subject";
import {Observable} from 'rxjs/Observable';
import {AudioItemActions} from '../actions/audioItemActions';
@Injectable()
export class WebAudioServices{
private audioContext: AudioContext;
private audioNode:AudioBufferSourceNode;
private audioBuffer: AudioBuffer;
private playbackRate: number = 1.0;
private gainNode:GainNode;
private gain: number = .01;
constructor( private _store: Store, private audioItemActions:AudioItemActions) {
this.audioContext = new AudioContext();
}
downLoadAudioItem(audioItem:AudioItem): Observable<any> {
console.log("[WebAudioServices]---- audioItem =", audioItem);
let storeRef = this._store;
return Observable.create(observer=> {
let req = new XMLHttpRequest();
req.open('GET', audioItem.trackURL, true );
req.responseType = "arraybuffer";
req.onreadystatechange = function () {
if (req.readyState == 4 && req.status == 200) {
audioItem.artistAudioBuffer = req.response;
audioItem.downloadComplete = true;
observer.next(audioItem);
observer.complete();
}
};
req.onprogress = function(evt)
{
console.log("[WebAudioServices] loadAudio -- req.onprogress-- =", evt);
if (evt.lengthComputable)
{ //evt.loaded the bytes browser receive
//evt.total the total bytes seted by the header
//
var percentComplete = (evt.loaded / evt.total) * 100;
audioItem.loadProgress = (evt.loaded / evt.total) * 100;
storeRef.dispatch({type:AudioItemActions.PROGRESS_OF_AUDIOITEM_DOWNLOAD , payload:audioItem });
}
};
req.send();
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment