我正在从localhost:4200
服务Angular,从localhost:3000
服务Express。在我的localhost:4200/sign-up
路线中,我有一个<form action=https://localhost:3000/users/sign-up>
的表格。
<form #form [formGroup]="userForm" method="POST" action="https://localhost:3000/users/sign-up">
. . .
. . .
<input type="submit" value="Submit" (click)="form.submit()" [disabled]="!userForm.valid || !usernameIsUnique">
</form>
提交表单后,我在Express中处理提交的内容,保存数据(使用user.save()
),只是为了测试事情的进展,请通过发送带有res.json(JSON.parse('{"foo": "bar"}')
的响应来完成路由。
exports.user_create_post = [
. . .
(req,res,next) => {
var user = new UserObj(
{ . . . });
user.save(function(err) { . . . });
res.json(JSON.parse('{"foo": "bar"}'));
}]
在res.json()
之后,我的URL保持在https://localhost:3000/users/sign-up
,并且出现控制台错误:
Content Security Policy: The page’s settings blocked the loading of a resource at https://localhost:3000/favicon.ico (“default-src”).
(为使内容简单,我将尝试在另一篇文章中解决Content Security Policy
问题)。
假设没有错误,URL应该保留在https://localhost:3000/users/sign-up
之后的res.json()
还是浏览器URL还原为referrer
,即localhost:4200/sign-up
?换句话说,在正常情况下没有错误,表单提交实际上会将浏览器URL更改为表单action
属性(https://localhost:3000/users/sign-up
)中列出的URL吗?还是将数据提交到action
属性URL(https://localhost:3000/users/sign-up
),但将浏览器URL保留在referrer
URL(localhost:4200/sign-up
)上?
如果确实表单提交将URL更改为https://localhost:3000/users/sign-up
,是否意味着还将res.json()
发送到https://localhost:3000/users/sign-up
(而不是localhost:4200/sign-up
)?如果是这样,这将解释为什么我遇到问题。此外,假设将res.json()
发送到https://localhost:3000/users/sign-up
而不是引荐来源网址(localhost:4200/sign-up
),我如何重定向到localhost:4200/sign-up
并向{{ 1}}(假设每个请求只允许一个响应)?
谢谢!
==========================
我进行了@John Mgbako建议的出色更改,但是现在有了一个新问题,即myu服务器尝试针对每个localhost:4200/sign-up
请求两次保存相同的文档(请在下面查看更多详细信息)。我现在有:
模板
POST
组件
<form #f="ngForm" [formGroup]="userForm" (ngSubmit)="doSignUp(f)">
<ng-container *ngFor="let key of keyArr; index as i">
<label [hidden]='key[1]'> {{key[2]}}
<div>
<input *ngIf="key[1]==='password' type='password' [formControlName]='key[0]' id={{key[0]}} name={{key[0]}}/>
<input *ngIf="key[1]!=='password' type='text' [formControlName]='key[0]' id={{key[0]}} name={{key[0]}}/>
</div>
</label>
<br/>
</ng-container>
<button>Submit</button>
</form>
授权服务(会员服务)
@Injectable()
export class SignupComponent implements OnInit, OnChanges {
data: Object;
keys: string[];
keyArr: any[] = [];
formCtls: any = {};
userForm: FormGroup;
cookieValue: string;
constructor(private member: MemberService,
private fb: FormBuilder,
private cs: CookieService,
private router: Router,
private route: ActivatedRoute) { }
ngOnChanges() {
// IF COOKIE EXISTS THEN ALREADY SIGNED UP, SO REDIRECT . . .
console.log("IN ON_CHANGES");
const cookie = this.cs.get("SESSIONID");
if (cookie) {
console.log(`ID: ${JSON.stringify(cookie)}`);
debugger;
this.router.navigateByUrl('../two-fa',{relativeTo: this.route})
}
}
ngOnInit() {
this.member.getSignupForm().subscribe((data)=>{
this.data = data;
this.keys = Object.keys(data["authData"]);
this.keys.forEach((key,idx) => {
let datArr = [];
this.formCtls[key] = new FormControl('', {updateOn: 'blur'});
if (key !== '_id') {
datArr.push(key,
data["authData"][key]["attr"]["hidden"],
data["authData"][key]["attr"]["label"]);
} else {
datArr.push(key,true,'');
}
if (key === 'password' || key === 'hash') {
let datArr = [];
this.formCtls['password2'] = new FormControl('');
datArr.push('password2', false, 'Password (again): ');
this.keyArr.push(datArr);
}
});
this.userForm = new FormGroup(this.formCtls);
if (this.data) this.got_data = true;
console.log("OnInit Data: " + this.data_string);
this.cs.set('member-cookie','{login-name: ' + data["authData"]["username"]["value"].pop() + '}');
this.cookieValue = this.cs.get('member-cookie');
console.log ("Cookie: " + this.cookieValue);
//Set up observable on the Username Input, to check if Unique
this.formCtls['username'].valueChanges.pipe(
filter((text: String) =>text.length > 2),
tap(text=>console.log("Text 1: " + text)),
debounceTime(10),
distinctUntilChanged(),
switchMap((text: String) =>this.member.getUnique(text.toLowerCase()))
).subscribe(r=> {console.log(`Result from unique get(): ${r}`);
this.usernameIsUnique = (r > 0) ? false : true; // r is # of instances in DB
console.log(`Unique? (length: ${r}): ${this.usernameIsUnique}`); });
});
}
doSignUp(f: NgForm) {
this.member.postSignupForm(f).subscribe((res)=>console.log(`RES: ${JSON.stringify(res)}`));
}
}
服务器路由
@Injectable({
providedIn: 'root'
})
export class MemberService {
host_url: string = "https://localhost:3000/users/";
signup_url: string = this.host_url+"sign-up";
data: string = '';
constructor(private http: HttpClient) { }
getSignupForm(): Observable<any> {
return this.http.get(this.signup_url,{responseType: 'json'})
}
postSignupForm(f: NgForm): Observable<any> {
return this.http.post(this.signup_url, f.value);
}
getUserIdFromRoute(route: ActivatedRoute): Observable<string> {
return route.paramMap.pipe(
map((params: ParamMap) => params.get('id'))
)
}
getUnique(s: String): Observable<any> {
return this.http
.get(`${this.host_url}unique?username=${s}`, {responseType: 'json'})
}
}
**问题现已存在:
- 要尝试按照每次发布请求将相同文档保存2倍的服务器项。 (它正确地保存在MongoDB中,然后过一会儿,当服务器尝试再次保存同一文档时,我收到服务器端Dup Key错误。)
- 没有
. . . . . . user.save().then(()=> { res.cookie("SESSIONID", user.generateJWT(), {httpOnly:true, secure:true}); . . . . . .
元素,点击返回输入元素时没有发生- 如果使用
BUTTON
元素,则该表单几乎是两次提交的表单。击中BUTTON
/从最后一个ENTER
中冒出来是一种要求,而在单击INPUT
按钮时又是一种要求? (顺便说一句,表格如何知道按钮是“提交”按钮?)。- 我期望的行为是,仅当选中
SUBMIT
按钮时,才提交表单,而不是在用户输入SUBMIT
时输入表单。 **
有什么主意如何解决这种奇怪的行为,即服务器在每个RETURN
请求中尝试两次保存文档?
最终更新
添加POST
res.cookie()`解决了该问题(即后端不再尝试两次保存文档)!
答案 0 :(得分:2)
这个问题有几个方面。
当可以使用Angular的form
时,为什么要使用action
的{{1}}属性来提交form
?这样,当您从Express API获得成功响应后,就可以更好地控制将用户重定向到何处。
理想情况下,应该将用户导航到某种着陆页。这既可以是仪表板页面,也可以是用户在注册/登录后应该登陆的主应用程序登陆页面,或者也可以是向您显示一条消息的页面,表明用户已将电子邮件发送到其注册电子邮件。带有确认链接或类似名称的地址。
考虑到您提到了HttpClient
路由,可以肯定地假设您还实现了路由。因此,您可以很好地将Router
作为依赖项注入并在其上调用navigate
,以便在成功响应后导航用户。
最后,由于Angular应用程序和Express应用程序在单独的PORT上运行,您可能会收到CORS错误。因此浏览器将阻止CORS。您可以使用cors
中间件来处理它。
答案 1 :(得分:2)
首先,您不以角的形式使用action属性,您可以反应性地使用表单或模板,例如
<form #form [formGroup]="userForm" method="POST" action="https://localhost:3000/users/sign-up">
应为:
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
在组件中添加一个onSubmit()函数,它将使用角度HttpClient
处理验证和所有请求调用
第二个内容安全策略:与以下事实有关:您的后端代码没有设置CORS
或CORS
来允许某些URL。您可以访问此链接Express CORS,了解如何在后端代码中实施。您还可以使用代理配置来处理您的Angular项目上的CORS,请访问此链接Proxy Config,了解如何实现。