Benutzer:MovGP0/Angular/Redux

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen
   MovGP0        Über mich        Hilfen        Artikel        Weblinks        Literatur        Zitate        Notizen        Programmierung        MSCert        Physik      

Redux mit Angular (NgRedux)

[Bearbeiten | Quelltext bearbeiten]
  • Code based on [1].
npm install ng2-redux
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { CourseComponent } from './courses/course.component';
import { CourseService } from './courses/course.service';
import { CourseListComponent } from './courses/course-list.component';

import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryStoryService } from '../api/in-memory-story.service';
import { AppRoutingModule } from './app-routing.module';

import { RouterModule }   from '@angular/router';
import { FilterTextComponent, FilterService } from './blocks/filter-text';
import { ToastComponent, ToastService } from './blocks/toast';
import { SpinnerComponent, SpinnerService } from './blocks/spinner';
import { ModalComponent, ModalService } from './blocks/modal';
import { ExceptionService } from './blocks/exception.service';

import { NgReduxModule, NgReduxModule } from 'ng2-redux';
import { store, IAppState } from './store';
import { CourseActions } from './courses/course.actions';

@NgModule({
  declarations: [
    AppComponent,
    CourseComponent,
    CourseListComponent,
    FilterTextComponent,
    ToastComponent,
    SpinnerComponent,
    ModalComponent,
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    InMemoryWebApiModule.forRoot(InMemoryStoryService, { delay: 500 }),
    AppRoutingModule, 
    NgReduxModule
  ],
  providers: [
    CourseService,
    FilterService,
    ToastService,
    SpinnerService,
    ModalService,
    ExceptionService,
    CourseActions
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
  constructor(ngRedux: NgRedux<IAppState>){
    ngRedux.provideStore(store);
  }
}
store/IAppState.ts
import { Course } from '../courses/course';

export interface IAppState{
    courses: Course[], 
    filteredCourses: Course[]
}
store/reducer.ts
import { Course } frem '../courses/course';
import { IAppState } from './IAppState';
import { FILTER_COURSES } from './courses/course.actions';

const courses = [];

const initialState: IAppState = {
    courses, // all courses
    filteredCourses: courses // initially unfiltered
};

function filterCourses(state, action): IAppState{
  // create immutable object
  return Object.assign({}, state, { 
    filteredCourses: state.courses.filter(c => c.name.toLowerCase().indexOf(action.searchText.toLowerCase()) > -1), 
    
  });
}

// gets called when courses are returned from the service
function storeCourses(state, action): IAppState{
  return Object.assign({}, state, { 
    courses: action.courses,
    filteredCourses: action.courses, 
  });
}

export function reducer(state = initialState, action){
  switch(action.type){
    case FILTER_COURSES: 
      return filterCourses(state, action);
    case REQUEST_COURSES_SUCCESS: 
      return storeCourses(state, action);
    default: 
      return state;   
  }
}
store/store.ts
import { createStore } from 'redux';
import { reducer } from './reducer';
import { IAppState } from './IAppState';

// check if dev tools are installed
declare var window:any;
const devToolsExtension: GenericStoreEnhancer = window.devToolsExtension
  ? window.dexToolsExtension()
  : (f) => f;

// create an Redux store
export const store = createStore<IAppState>(reducer);
store/index.ts
export * from './store';
export * from './IAppState';
export * from './actions';
courses/actions.ts
import { Injectable } from '@angular/core';
import { NgRedux } from 'ng2-redux';
import { IAppState } from '../store';
import { CourseService } from '../courses/course.service.ts';

export const FILTER_COURSES = 'courses/FILTER';
export const REQUEST_COURSES_SUCCESS = 'courses/REQUEST_SUCCESS';

@Injectable
export class CourseActions {
  constructor(private ngRedux: NgRedux<IAppState>, private courseService: CourseService){
  }

  getCourses(){
    this.courseService.getCourses().subsribe(courses => {
      this.ngRedux.dispatch({
        type: REQUEST_COURSES_SUCCESS, 
        searchText,
      })
    });
  }

  function filterCourses(searchText:string){
    this.ngRedux.dispatch({
      type: FILTER_COURSES, 
      searchText,
    });
  }
}
courses/course-list.compontent.ts
import { Component, OnInit } from '@angular/core';
import { Course } from './course';
import { FilterTextComponent } from '../blocks/filter-text';
import { IAppState } from '../store';
import { NgRedux, select } from 'ng2-redux';
import { Observable } from 'rxjs/Observable';
import { CourseActions } from './course.actions';

@Component({
  selector: 'app-course-list',
  templateUrl: './course-list.component.html',
  styleUrls: ['./course-list.component.css']
})
export class CourseListComponent implements OnInit {
  @select('filteredCourses') filteredCourses$ : Observable<Course>; // $ signifies Observable

  constructor(private ngRedux: NgRedux<IAppState>, private courseActions: CourseActions) {
  }

  filterChanged(searchText: string) {
    console.log('user searched: ', searchText);
    this.courseActions.filterCourses(searchText);
  }

  updateFromState(){
    this.filteredCourses = store.getState().filteredCourses;
  }

  ngOnInit() {
    this.courseActions.getCourses();
    componentHandler.upgradeDom();
  }
}
courses/course-list.compontent.html
  <h4>All courses</h4>
  <button [routerLink]="['Course', {id: 'new'}]">Add</button>
  <filter-text (changed)="filterChanged($event)"></filter-text>
  <ul class="courses">
    <!-- async is important when subscribing to Observable -->
    <li *ngFor="let course of filteredCourses$ | async">
      <div>
        <div>
          <h2>{{course.id}}.{{course.name}}</h2>
        </div>
        <div>
         <button [routerLink]="['Course', {id: course.id}]">
            <i>edit</i>
          </button>
        </div>
      </div>
    </li>
  </ul>
reducer.spec.ts
import { reducer } from './reducer';
import { FILTER_COURSES } from '../courses/course.actions';

describe('Reducer', => {
  it('should have the correct initial value', () => {
    const state = reducer(undefined, {});
    expect(state.courses.length).toBe(0);
    expect(state.filteredCourses.length).toBe(0);
  });

  describe('filterCourses', () => {
    const courses = [...];
    
    it('should filter out all courses when searchText matches nothing', () => {
      const state = { courses, filteredCourses: courses };
      const action = {type: FILTER_COURSES, searchText: 'nothing'}
      const adaptedState = reducer(state, action);
      expect(adaptedState.courses.length).toBe(3);
      expect(adaptedState.filteredCourses.length).toBe(0);
    });
  });

    it('should filter out redux course when searchText matches redux', () => {
      const state = { courses, filteredCourses: courses };
      const action = {type: FILTER_COURSES, searchText: 'redux'}
      cons adaptedState = reducer(state, action);
      expect(adaptedState.courses.length).toBe(3);
      expect(adaptedState.filteredCourses.length).toBe(1);
    });
  });
});

Enforce Immutability

[Bearbeiten | Quelltext bearbeiten]
  • use Immutable collections
  • use Object.freeze({ ... })
    • use Deep Freeze for freezing nested JSON objects
      import { deepFreeze } from 'deepFreeze';
      Object.prototype.deepFreeze = deepFreeze;
      Object.deepFreeze({ ... });
      
    • use Redux Freeze for deep freezing Redux State
      import { freeze } from 'redux-freeze';
      import { Middleware } from 'redux'
      
      // ...
      
      constructor(private reduxMiddleware:Middleware){}
      
      if (__DEV__) {
        reduxMiddleware.push(freeze)
      }
      
  • Redux. Abgerufen am 31. März 2017 (englisch).
  • Redux DevTools extension. In: GitHub. Abgerufen am 31. März 2017 (englisch, Browser Erweiterung zur Inspektion des Redux-States).

|}