Settings - VeenaPD/Expense-Management-System GitHub Wiki

Settings component

The user can update his total budget and add or remove expense categories in settings component as shown below:

Settings

Data models used for settings component are Settings and ExpenseCategory

export interface Settings {
  budget: number;
  updatedAt: Date;
}
export interface ExpenseCategory {
  id: string;
  name: string;
  isDeleted: boolean;
}

Actions

The actions that will dispatch changes to the store using the application state and reducers we just defined. Actions should be treated as events, and live close to where they are dispatched from. It’s also a good idea to include the name of the page that the action is being fired from when you declare the action type. onUpdateBudget() dispatch new UpdateBudgetAction

import { Action } from '@ngrx/store';

export const UPDATE_BUDGET = '[SETTINGS] UPDATE_BUDGET';

export class UpdateBudgetAction implements Action {
  readonly type = UPDATE_BUDGET;
  constructor(public payload: number) { }
}
export type SettingsActionType = UpdateBudgetAction;

Reducers always return the accumulation of the state (based on all previous and current actions).Therefore, they act as a reducer of state. Each time a redux reducer is called, the state is passed in with the action (state, action). This state is then reduced (or accumulated) based on the action, and then the next state is returned. This is one cycle of the classic fold or reduce function. The below is the settings.reducer.ts file.

import * as settingsActions from '../actions/settings.actions';
import { Settings } from '../models/Settings';

export interface SettingsState extends Settings{};

const initialState: SettingsState = {
  budget: 100,
  updatedAt: new Date()
}

export function settingsReducer(state = initialState, action: settingsActions.SettingsActionType): SettingsState {
  switch (action.type) {
    case settingsActions.UPDATE_BUDGET: {
      return {
        ...state,
        budget: action.payload,
        updatedAt: new Date()
      };
    }
    default: {
      return state;
    }
  }

}

The service file fetch data from server, here we are using store local storage settings.service.ts settings.selector.ts

import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';

import { UpdateBudgetAction } from '../actions/settings.actions';
import { State } from '../reducers';
import { getTotalBudgetSelector } from '../selectors/settings.selector';

@Injectable({
  providedIn: 'root'
})
export class SettingsService {

  constructor(public store: Store<State>) { }
  /**
  Get total budget of user by getTotalBudgetSelector
   */
  getTotalBudget(){
    return this.store.select(getTotalBudgetSelector)
  }
   /**
  Update budget of user by dispatching updateBudgetAction
  @param newBudget number
   */
  updateBudget(newBudget){
    this.store.dispatch(new UpdateBudgetAction(newBudget));
  }
}

Bootstrap Form is used to take input from users as below in settings.component.html

<div class="card">
  <div class="card-body">
    <form>
        <label for="targetBudgetInput"> Target Budget </label>
        <div class="col-sm-12">
        <input type="text" class="form-control mb-2 mr-sm-2" id="targetBudgetInput" placeholder="10,000" name="newBudget" [(ngModel)]="budget" >
          <div id="button-container">
            <button type="button" class="btn btn-primary" (click)="onUpdateBudget($event)">Update budget</button>
          </div>
      </div>
    </form>
  </div>
</div>
<div class="card">
  <div class="card-body">
    <form>
        <label for="categoryInput"> Categories </label>
          <div class="col-sm-12">
          <input type="text" [(ngModel)]="newCategoryName" class="form-control mb-2 mr-sm-2" id="categoryInput" name="newCat" placeholder="Cat 1">
          <div id="button-container">
              <button type="button" class="btn btn-primary btn-block" (click)="onAddExpenseCategory($event)">Add category</button>
            </div>
          </div>
      </form>
  </div>
</div>

<div class="card mb-3">
    <div class="card-body">
      <div class="card-title"> Categories List </div>
        <div class="row" *ngFor="let expenseCat of getExpenseCategories() | async">
            <div class="btn-group ml-sm-3 mb-2">
              <button type="button" class="btn btn-success btn-block"> <span [ngClass]="!expenseCat.isDeleted ? 'null' : 'strikethrough'">{{expenseCat.name}}</span> </button>
              <button *ngIf="!expenseCat.isDeleted" type="button" class="btn btn-success" (click)="onRemoveExpenseCategory(expenseCat.id)"> - </button>
              <button *ngIf="expenseCat.isDeleted"type="button" class="btn btn-success" (click)="onRemoveExpenseCategory(expenseCat.id)"> + </button>
            </div>
        </div>
    </div>
  </div>

settings.component.css consists of few stylings for HTML its shown as below:

.card {
  margin: 16px 15%;
}
#button-container{
  margin-top: 16px;
  float: right;
}
.card-item{
  display: inline;
}

In settings.component.ts

import { Component, OnInit, Input } from '@angular/core';
import { ExpenseCategory } from 'src/app/models/ExpenseCategory';
import { SettingsService } from 'src/app/services/settings.service';
import { ExpenseService } from 'src/app/services/expense.service';
import { ExpenseCategoryService } from 'src/app/services/expense-category.service';

@Component({
  selector: 'app-settings',
  templateUrl: './settings.component.html',
  styleUrls: ['./settings.component.scss']
})
export class SettingsComponent implements OnInit {
  budget;
  newCategoryName:string = '';
  constructor(public expenseCatService:ExpenseCategoryService,public settingsService:SettingsService,public expenseService:ExpenseService) { }
  /**
   * onInit subscribes for current budget
   */
  ngOnInit() {
    this.getCurrentBudget().subscribe(b => {
      this.budget = b;
    })
  }
  /**
   * Fetch all expense categories
   */
  getExpenseCategories(){
    return this.expenseCatService.getAllExpenseCategories()
  }
  /**
   * Fetch current budget
   */
  getCurrentBudget(){
    return this.settingsService.getTotalBudget()
  }
  /**
   * Adds expense category
   * @param name
   */
  addExpenseCategory(name:string){
    this.expenseCatService.addExpenseCategory(name);
  }
  /**
   * Removes expense category
   * @param id expenseId
   */
  removeExpenseCategory(id:string){
    this.expenseCatService.removeExpenseCategory(id);
  }
  /**
   * Update budget value
   */
  onUpdateBudget(){
    console.log(this.budget);
    this.settingsService.updateBudget(parseInt(this.budget));
  }
  /**
   * Add new expense category
   */
  onAddExpenseCategory(){
    console.log(this.newCategoryName);
    this.expenseCatService.addExpenseCategory(this.newCategoryName);
    this.newCategoryName = '';
  }
  /**
   * Remove expense category
   * @param id  expenseCategoryId
   */
  onRemoveExpenseCategory(id:string){
    this.expenseCatService.removeExpenseCategory(id);
  }
}
⚠️ **GitHub.com Fallback** ⚠️