我成功实施了 @angular-redux/store
和 redux
在我最小的Angular 4样板中。
我确实理解Redux循环背后的想法,但我很难让我超越简单的计数器增量按钮示例。
目前我已经构建了一个简单的登录表单作为需要从我的API中获取JWT令牌的组件。
我不想让它太复杂,所以现在我不想将表单状态存储在商店中,因为表单组件不会影响其他组件,对吧?
所以,当我点击提交按钮时,我的 login.component.ts
将处理对我的API的验证和http请求。
但表单提交也是一个动作,所以Redux什么时候进来玩?
没有必要在商店中保持表单状态。使用被动形式来处理验证和更改。你不必把所有东西都留在商店里。
当需要采取行动时,您将按照以下方式发送:
this.store.dispatch({ type: ACTION_TYPE, payload: this.form.value });
这个动作会触发一个 影响,它将调用登录API。成功或错误后,您需要发送新操作来处理它。
如果您的呼叫成功,您将从此效果中分配一个动作,例如 LOGIN_SUCCESS
您将在另一个效果中处理,例如将获取您将在商店中保存的“获取用户数据”API。至于 jwt
,在效果中你可以保存它 localStorage
所以应用程序会记住登录状态。在您的代码中,您可能有一个 AuthenticationService
有可观察的财产 status
,告诉用户是否已登录。
如果发生错误,您需要将其保存在商店的某些错误属性中并将其显示在表单上。
表单是收集用户数据的基本要素,所有验证都类似于您对该数据执行的检查,以确保它可用于您的应用程序以供将来处理。
对于这种情况,反应形式是您可以使用的最佳形式,它将为您提供在表单中添加验证器和逻辑并将所有内容保存在一个位置所需的所有灵活性。
只有在您点击提交后完成所有验证和检查后,您才能将整个表单数据作为有效负载发送到状态作为对象。
喜欢这个 this.store.dispatch({ type: Form_Data , payload : this.form.value});
然后,您将在您的应用程序中移动。进一步处理。作为你国家的一部分。
有关使用反应形式的更多信息,请检查此 链接。
更多关于ngrx 链接
整个应用程序构建在ngrx v4工作 例 它的回购 链接
我知道现在已有几个月了,但我恭敬地不同意以前的答案,因为使用Reactive Forms方法来管理表单会产生很多挑战,因为那时你并没有将你的商店用作真相的核心和单一位置 - 你将Redux哲学与Angular哲学相结合,以管理国家,无论何时你这样做,它都会变得混乱。
如果您没有将表单状态保留在商店中,那么将表单状态恢复为更具挑战性,例如,如果用户处于流程中并且前进并返回到他们刚刚访问的页面。保存表单状态后,您只需读取表单状态即可将其恢复。否则,您必须提出一些其他方式来存储状态,这意味着通过父组件和子组件传递数据(假设首先是单页应用程序样式) - 这也是值得注意的,与您已经管理的redux商店分开。
如果你要使用redux,我经过实战测试的建议是专注于让组件变得愚蠢,让你的商店驱动所有东西[尽可能纯粹的减少];意思是,所有组件都是从商店读取,分配本地变量和调用服务(其中一些是调度操作)。您可以使用Reactive Forms(例如FormBuilder和FormGroups),但具有由redux和您的商店驱动的表单验证(例如,您可以实现@ angular-redux / form并拦截 @@angular-redux/form/FORM_CHANGED
用你自己的减速机打电话 action.payload.path
区分正在调用哪个表单来执行验证等 - 这也存储在每个用户操作的商店中,并不意味着如果没有按下完成/提交按钮则不存储任何内容,这将发生在拉胡尔的案子)。
虽然之前给出的答案可行,但我认为它们并不能提供最佳的开发人员或用户体验。如果你使用redux,那么在管理状态和用户输入的时候,坚持使用redux哲学优于Angular,你会发现Angular和Redux之间的结合是一个非常神奇的联盟。
直接回答您的具体问题:“所以,当我点击提交按钮时,我的login.component.ts将处理验证和http请求到我的API。但表单提交也是一个动作,所以Redux什么时候进入在这玩吗?“我构建这个的方法是让你的登录组件进行一个服务调用,然后进行一个http调用,返回一个observable然后处理你的商店的响应,就像一个服务调用一起调度一个带有效负载的动作(更改页面状态或存储返回的信息)。下面的极简主义模拟示例[注意:请记住您使用https的安全做法,在适当时清除商店,等等,如果您正在实施真正的登录]:
login.component.ts:
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
stylesUrls: ['./login.component.css']
}
export class LoginComponent implements OnInit {
form: FormGroup;
username: string;
password: string;
login_failed: boolean;
login_failed_message: string;
@select(['main','login']) getLogin Observable<ILogin>;
constructor(private loginService: LoginService, private fb: FormBuilder) {
}
ngOnInit() {
// Watch Changes on state
this.getLogin.subscribe(login => {
// Read Login
if (login) {
this.username = login.username;
this.password = login.password;
this.login_failed = login.failed;
this.login_failed_message = login.message;
}
}
// Initialize Login Form
this.form = this.fb.group({
username: [this.username],
password: [this.password],
});
}
onSubmit() {
this.loginService.submitLogin(this.login, this.password)
.map(res => res.json())
.subscribe(res => {
if (res.status === 'success') {
this.loginService.loadHomePage();
} else {
this.loginService.setLoginFailed();
}
},
error => {
this.loginService.setLoginServerCommunicationFailed(error);
});
}
}
login.service.ts:
export class LoginService {
constructor(private ngRedux: NgRedux<IAppState>, private http: Http) {
}
submitLogin(login, password) {
const jsonBody = {
login: login,
password: password,
}
return this.http.post(`/myApiAddress`, jsonBody);
}
loadHomePage() {
this.ngRedux.dispatch({ type: 'LOGIN::HOME_PAGE'});
}
setLoginFailed() {
this.ngRedux.dispatch({ type: 'LOGIN::LOGIN_FAILED'});
}
setLoginServerCommunicationFailed(message: string) {
this.ngRedux.dispatch({ type: 'LOGIN::LOGIN_SERVER_UNREACHABLE', payload: message});
}
}
登录-form.reducer.ts:
import * as _ from 'lodash';
export function loginFormReducer(state: ILoginState, action: any) {
switch (action.type) {
case '@@angular-redux/form/FORM_CHANGED':
// only acts on login form actions sent from @angular-redux/form
if (_.isEqual(action.payload.path, ['login']) {
// Real-time login validation
}
return state;
default:
return state;
}
}
login.reducer.ts:
export function loginReducer(state: ILoginState, action: any) {
switch (action.type) {
case 'LOGIN::LOGIN_FAILED':
return {
...state,
login: {
...state!.login,
failed: true,
message: action.payload,
}
}
case 'LOGIN::LOGIN_SERVER_UNREACHABLE':
return {
...state,
login: {
...state!.login,
failed: true,
message: 'Server Unreachable',
}
}
default:
return state;
}
}
navigation.reducer.ts:
export function navigationReducer(state: IAppState, action: any) {
switch (action.type) {
case 'LOGIN::HOME_PAGE':
return {
...state,
router: '/home',
}
default:
return state;
}
}
login.interface.ts:
export interface ILogin {
username: string;
password: string;
failed: boolean;
message: string;
}
store.ts
// Other Reducers
import { routerReducer } from '@angular-redux/router';
import { navigationReducer } from '...';
import { mainReducer } from '...';
// Redux Imports
import { composeReducers, defaultFormReducer } from '@angular-redux/form';
import { combineReducers, applyMiddleware } from 'redux';
import { routerReducer } from '@angular-redux/router';
export const rootReducer = composeReducers(
defaultFormReducer(),
navigationReducer,
combineReducers({
router: routerReducer,
main: mainReducer,
})
);
main.reducer.ts
import { loginReducer } from '...';
import { loginFormReducer } from '...';
export const mainReducer =
combineReducers({
login: composeReducers(
loginReducer,
loginFormReducer,
)
})