Merge pull request #18 from artlowel/ngrx

Ngrx
This commit is contained in:
William Welling
2016-12-13 07:37:01 -06:00
committed by GitHub
15 changed files with 286 additions and 73 deletions

View File

@@ -62,6 +62,11 @@
"@angularclass/bootloader": "1.0.1", "@angularclass/bootloader": "1.0.1",
"@angularclass/idle-preload": "1.0.4", "@angularclass/idle-preload": "1.0.4",
"@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.14", "@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.14",
"@ngrx/core": "^1.2.0",
"@ngrx/effects": "^2.0.0",
"@ngrx/router-store": "^1.2.5",
"@ngrx/store": "^2.2.1",
"@ngrx/store-devtools": "^3.2.2",
"angular2-express-engine": "2.1.0-rc.1", "angular2-express-engine": "2.1.0-rc.1",
"angular2-platform-node": "2.1.0-rc.1", "angular2-platform-node": "2.1.0-rc.1",
"angular2-universal": "2.1.0-rc.1", "angular2-universal": "2.1.0-rc.1",

View File

@@ -1,21 +1,4 @@
<header> <ds-header></ds-header>
<nav class="navbar navbar-dark bg-inverse">
<button class="navbar-toggler hidden-sm-up" type="button" (click)="toggle()" aria-controls="collapsingNav" aria-expanded="false" aria-label="Toggle navigation">
<i class="fa fa-bars fa-fw" aria-hidden="true"></i>
</button>
<div [ngClass]="{'clearfix': !isNavBarCollaped()}">
<a class="navbar-brand" routerLink="/home">
<!-- TODO: add logo here -->{{ 'title' | translate }}</a>
</div>
<div [ngbCollapse]="isNavBarCollaped()" class="collapse navbar-toggleable-xs" id="collapsingNav">
<ul class="nav navbar-nav">
<li class="nav-item">
<a class="nav-link" routerLink="/home" routerLinkActive="active"><i class="fa fa-home fa-fw" aria-hidden="true"></i> {{ 'nav.home' | translate }}<span class="sr-only">(current)</span></a>
</li>
</ul>
</div>
</nav>
</header>
<div class="container-fluid"> <div class="container-fluid">
<main> <main>

View File

@@ -1,13 +1 @@
@import '../styles/variables.scss';
header nav.navbar {
border-radius: 0rem;
}
header nav.navbar .navbar-toggler {
float: right;
}
header nav.navbar .navbar-toggler:hover {
cursor: pointer;
}

View File

@@ -1,7 +1,14 @@
import { Component, HostListener, Input, ChangeDetectionStrategy, ViewEncapsulation, OnDestroy, OnInit } from '@angular/core'; import {
import { Event, NavigationEnd, Router } from '@angular/router'; Component,
ChangeDetectionStrategy,
import { TranslateService } from 'ng2-translate'; ViewEncapsulation,
OnDestroy,
OnInit, HostListener
} from "@angular/core";
import { TranslateService } from "ng2-translate";
import { HostWindowState } from "./shared/host-window.reducer";
import { Store } from "@ngrx/store";
import { HostWindowActions } from "./shared/host-window.actions";
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.Default, changeDetection: ChangeDetectionStrategy.Default,
@@ -11,14 +18,6 @@ import { TranslateService } from 'ng2-translate';
styleUrls: ['./app.component.css'] styleUrls: ['./app.component.css']
}) })
export class AppComponent implements OnDestroy, OnInit { export class AppComponent implements OnDestroy, OnInit {
// TODO: move header and all related properties into its own component
private navCollapsed: boolean;
private routerSubscription: any;
private translateSubscription: any; private translateSubscription: any;
example: string; example: string;
@@ -26,32 +25,25 @@ export class AppComponent implements OnDestroy, OnInit {
data: any = { data: any = {
greeting: 'Hello', greeting: 'Hello',
recipient: 'World' recipient: 'World'
} };
constructor(public translate: TranslateService, private router: Router) { constructor(
private translate: TranslateService,
private store: Store<HostWindowState>
) {
// this language will be used as a fallback when a translation isn't found in the current language // this language will be used as a fallback when a translation isn't found in the current language
translate.setDefaultLang('en'); translate.setDefaultLang('en');
// the lang to use, if the lang isn't available, it will use the current loader to get them // the lang to use, if the lang isn't available, it will use the current loader to get them
translate.use('en'); translate.use('en');
this.collapse();
} }
ngOnInit() { ngOnInit() {
this.routerSubscription = this.router.events.subscribe((event: Event) => {
if (event instanceof NavigationEnd) {
this.collapse();
}
});
this.translateSubscription = this.translate.get('example.with.data', { greeting: 'Hello', recipient: 'DSpace' }).subscribe((translation: string) => { this.translateSubscription = this.translate.get('example.with.data', { greeting: 'Hello', recipient: 'DSpace' }).subscribe((translation: string) => {
this.example = translation; this.example = translation;
}); });
} }
ngOnDestroy() { ngOnDestroy() {
if (this.routerSubscription) {
this.routerSubscription.unsubscribe();
}
if (this.translateSubscription) { if (this.translateSubscription) {
this.translateSubscription.unsubscribe(); this.translateSubscription.unsubscribe();
} }
@@ -59,23 +51,9 @@ export class AppComponent implements OnDestroy, OnInit {
@HostListener('window:resize', ['$event']) @HostListener('window:resize', ['$event'])
private onResize(event): void { private onResize(event): void {
this.collapse(); this.store.dispatch(
} HostWindowActions.resize(event.target.innerWidth, event.target.innerHeight)
);
private collapse(): void {
this.navCollapsed = true;
}
private expand(): void {
this.navCollapsed = false;
}
public toggle(): void {
this.navCollapsed ? this.expand() : this.collapse();
}
public isNavBarCollaped(): boolean {
return this.navCollapsed;
} }
} }

6
src/app/app.effects.ts Normal file
View File

@@ -0,0 +1,6 @@
import { EffectsModule } from "@ngrx/effects";
import { HeaderEffects } from "./header/header.effects";
export const effects = [
EffectsModule.run(HeaderEffects)
];

View File

@@ -5,14 +5,54 @@ import { SharedModule } from './shared/shared.module';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { HeaderComponent } from './header/header.component';
import { StoreModule } from "@ngrx/store";
import { RouterStoreModule } from "@ngrx/router-store";
import { StoreDevtoolsModule } from "@ngrx/store-devtools";
import { rootReducer } from './app.reducers';
import { effects } from './app.effects';
@NgModule({ @NgModule({
declarations: [AppComponent], declarations: [
AppComponent,
HeaderComponent
],
imports: [ imports: [
SharedModule, SharedModule,
HomeModule, HomeModule,
AppRoutingModule AppRoutingModule,
/**
* StoreModule.provideStore is imported once in the root module, accepting a reducer
* function or object map of reducer functions. If passed an object of
* reducers, combineReducers will be run creating your application
* meta-reducer. This returns all providers for an @ngrx/store
* based application.
*/
StoreModule.provideStore(rootReducer),
/**
* @ngrx/router-store keeps router state up-to-date in the store and uses
* the store as the single source of truth for the router's state.
*/
RouterStoreModule.connectRouter(),
/**
* Store devtools instrument the store retaining past versions of state
* and recalculating new states. This enables powerful time-travel
* debugging.
*
* To use the debugger, install the Redux Devtools extension for either
* Chrome or Firefox
*
* See: https://github.com/zalmoxisus/redux-devtools-extension
*/
StoreDevtoolsModule.instrumentOnlyWithExtension(),
effects
],
providers: [
] ]
}) })
export class AppModule { export class AppModule {

20
src/app/app.reducers.ts Normal file
View File

@@ -0,0 +1,20 @@
import { combineReducers } from "@ngrx/store";
import { routerReducer, RouterState } from "@ngrx/router-store";
import { headerReducer, HeaderState } from './header/header.reducer';
import { hostWindowReducer, HostWindowState } from "./shared/host-window.reducer";
export interface AppState {
router: RouterState;
hostWindow: HostWindowState;
header: HeaderState;
}
export const reducers = {
router: routerReducer,
hostWindow: hostWindowReducer,
header: headerReducer
};
export function rootReducer(state: any, action: any) {
return combineReducers(reducers)(state, action);
}

View File

@@ -0,0 +1,24 @@
import { Action } from "@ngrx/store";
export class HeaderActions {
static COLLAPSE = 'dspace/header/COLLAPSE';
static collapse(): Action {
return {
type: HeaderActions.COLLAPSE
}
}
static EXPAND = 'dspace/header/EXPAND';
static expand(): Action {
return {
type: HeaderActions.EXPAND
}
}
static TOGGLE = 'dspace/header/TOGGLE';
static toggle(): Action {
return {
type: HeaderActions.TOGGLE
}
}
}

View File

@@ -0,0 +1,18 @@
<header>
<nav class="navbar navbar-dark bg-inverse">
<button class="navbar-toggler hidden-sm-up" type="button" (click)="toggle()" aria-controls="collapsingNav" aria-expanded="false" aria-label="Toggle navigation">
<i class="fa fa-bars fa-fw" aria-hidden="true"></i>
</button>
<div [ngClass]="{'clearfix': !(isNavBarCollapsed | async)}">
<a class="navbar-brand" routerLink="/home">
<!-- TODO: add logo here -->{{ 'title' | translate }}</a>
</div>
<div [ngbCollapse]="(isNavBarCollapsed | async)" class="collapse navbar-toggleable-xs" id="collapsingNav">
<ul class="nav navbar-nav">
<li class="nav-item">
<a class="nav-link" routerLink="/home" routerLinkActive="active"><i class="fa fa-home fa-fw" aria-hidden="true"></i> {{ 'nav.home' | translate }}<span class="sr-only">(current)</span></a>
</li>
</ul>
</div>
</nav>
</header>

View File

@@ -0,0 +1,13 @@
@import '../../styles/variables.scss';
header nav.navbar {
border-radius: 0rem;
}
header nav.navbar .navbar-toggler {
float: right;
}
header nav.navbar .navbar-toggler:hover {
cursor: pointer;
}

View File

@@ -0,0 +1,39 @@
import { Component, OnInit } from "@angular/core";
import { Store } from "@ngrx/store";
import { HeaderState } from "./header.reducer";
import { HeaderActions } from "./header.actions";
import { Observable } from "rxjs";
import 'rxjs/add/operator/filter';
@Component({
selector: 'ds-header',
styleUrls: ['header.component.css'],
templateUrl: 'header.component.html'
})
export class HeaderComponent implements OnInit {
public isNavBarCollapsed: Observable<boolean>;
constructor(
private store: Store<HeaderState>
) {
}
ngOnInit(): void {
this.isNavBarCollapsed = this.store.select('header')
//unwrap navCollapsed
.map(({ navCollapsed }: HeaderState) => navCollapsed);
}
private collapse(): void {
this.store.dispatch(HeaderActions.collapse());
}
private expand(): void {
this.store.dispatch(HeaderActions.expand());
}
public toggle(): void {
this.store.dispatch(HeaderActions.toggle());
}
}

View File

@@ -0,0 +1,21 @@
import { Injectable } from "@angular/core";
import { Effect, Actions } from '@ngrx/effects'
import { HeaderActions } from "./header.actions";
import { HostWindowActions } from "../shared/host-window.actions";
import { routerActions } from "@ngrx/router-store";
@Injectable()
export class HeaderEffects {
constructor(
private actions$: Actions
) { }
@Effect() resize$ = this.actions$
.ofType(HostWindowActions.RESIZE)
.map(() => HeaderActions.collapse());
@Effect() routeChange$ = this.actions$
.ofType(routerActions.UPDATE_LOCATION)
.map(() => HeaderActions.collapse());
}

View File

@@ -0,0 +1,39 @@
import { Action } from "@ngrx/store";
import { HeaderActions } from "./header.actions";
export interface HeaderState {
navCollapsed: boolean;
}
const initialState: HeaderState = {
navCollapsed: true
};
export const headerReducer = (state = initialState, action: Action): HeaderState => {
switch (action.type) {
case HeaderActions.COLLAPSE: {
return Object.assign({}, state, {
navCollapsed: true
});
}
case HeaderActions.EXPAND: {
return Object.assign({}, state, {
navCollapsed: false
});
}
case HeaderActions.TOGGLE: {
return Object.assign({}, state, {
navCollapsed: !state.navCollapsed
});
}
default: {
return state;
}
}
};

View File

@@ -0,0 +1,14 @@
import { Action } from "@ngrx/store";
export class HostWindowActions {
static RESIZE = 'dspace/host-window/RESIZE';
static resize(newWidth: number, newHeight: number): Action {
return {
type: HostWindowActions.RESIZE,
payload: {
width: newWidth,
height: newHeight
}
}
}
}

View File

@@ -0,0 +1,25 @@
import { Action } from "@ngrx/store";
import { HostWindowActions } from "./host-window.actions";
export interface HostWindowState {
width: number;
height: number;
}
const initialState: HostWindowState = {
width: null,
height: null
};
export const hostWindowReducer = (state = initialState, action: Action): HostWindowState => {
switch (action.type) {
case HostWindowActions.RESIZE: {
return Object.assign({}, state, action.payload);
}
default: {
return state;
}
}
};