Angular 10:代码不等待订阅完成

时间:2020-08-27 12:08:32

标签: angular typescript http

我有一个按钮,当我单击它时,它将通过API调用将用户登录到数据库中,但是直到第二次单击按钮,代码似乎才等待响应。

HTML:

<button class="login-button" [disabled]='!loginForm.valid' (click)="login()">Login User</button>

Login.Component.ts:

login() {
    this.getLoginDetails();

    // After the first click of the button this is 'undefined' but after the second it returns the details
    var details = this.loginResponse;

    // Store details in session
    localStorage.setItem('token', details.token);   // Throws exception on first click due to 'details' being undefined
    localStorage.setItem('refreshToken', details.refreshToken);
    localStorage.setItem('userId', details.userId.toString());

    this.router.navigate(['/subscribers']);
}

getLoginDetails(){
    var data = JSON.stringify({
        "EmailAddress": this.loginForm.controls['userName'].value,
        "Password": this.loginForm.controls['password'].value 
    });

    const httpOptions = {
        headers: new HttpHeaders({
            'Content-Type':  'application/json'
        })
    };

    // After the first click this is undefined but after the second it returns a value
    let resp = this.http.post("https://localhost:8080/api/auth/login", data, httpOptions);

    resp.subscribe((response: LoginResponse) => {
        if(response) {
            this.loginResponse = response
        } 
    });
}

问题似乎是,第一次单击后,该帖子没有返回undefined,但在第二次单击之后,它返回了一个值。

我认为问题在于程序没有在等待API返回值,但是我不明白为什么在第二次单击后它会起作用。

有了Angular 10,有人知道如何确保post等待吗?

5 个答案:

答案 0 :(得分:2)

相反,您应该返回resp并通过login()方法进行订阅:

login() {
  this.getLoginDetails().subscribe((response: LoginResponse) => {
      // assign the response here
      var details = response;

      // Store details in session
      localStorage.setItem('token', details.token);   // Throws exception on first click due to 'details' being undefined
      localStorage.setItem('refreshToken', details.refreshToken);
      localStorage.setItem('userId', details.userId.toString());

      this.router.navigate(['/subscribers']);
   });
}

getLoginDetails(){
  // other implementation

  return resp;
}

答案 1 :(得分:2)

那是异步数据的本质。它是异步分配的,因此当您尝试使用它时,不能假定它已经分配了值。相反,您需要等待分配它。换句话说,在需要响应的地方订阅观察者。

尝试以下

login() {
  this.getLoginDetails().subscribe(
    (details: LoginResponse) => {
      if(details) {
        // Store details in session
        localStorage.setItem('token', details.token);
        localStorage.setItem('refreshToken', details.refreshToken);
        localStorage.setItem('userId', details.userId.toString());
        this.router.navigate(['/subscribers']);
      }
    },
    error => {
      // always good practice to handle errors from HTTP observables
    }
  );
}

getLoginDetails(): Observable<any> {     // <-- return the observable here
  var data = JSON.stringify({
    "EmailAddress": this.loginForm.controls['userName'].value,
    "Password": this.loginForm.controls['password'].value 
  });

  const httpOptions = {
    headers: new HttpHeaders({
      'Content-Type':  'application/json'
    })
  };

  return this.http.post("https://localhost:8080/api/auth/login", data, httpOptions);
}

您可以找到有关从异步调用here返回数据的更多信息。

答案 2 :(得分:1)

在您的import UIKit import AVFoundation func printMetadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], previewLayer: AVCaptureVideoPreviewLayer) { for object in metadataObjects { let visualCodeObject = previewLayer.transformedMetadataObject(for: object) guard let object = visualCodeObject, let barcode = object as? AVMetadataMachineReadableCodeObject else { NSLog("Ignoring object that is not AVMetadataMachineReadableCodeObject") continue } guard let barcodeString = barcode.stringValue else { NSLog("Captured something that's not a string") continue } NSLog("Captured string %@", barcodeString) } } class CaptureView: UIView, AVCaptureMetadataOutputObjectsDelegate { private let previewLayer = AVCaptureVideoPreviewLayer() func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { printMetadataOutput(output, didOutput: metadataObjects, previewLayer: self.previewLayer) } override func layoutSubviews() { super.layoutSubviews() self.previewLayer.frame = self.frame } init(frame: CGRect, delegate: AVCaptureMetadataOutputObjectsDelegate) { guard let captureDevice = AVCaptureDevice.default(for: .video) else { fatalError("Couldn't find default capture device") } guard let captureDeviceInput = try? AVCaptureDeviceInput(device: captureDevice) else { super.init(frame: frame) return } let captureSession = AVCaptureSession() captureSession.addInput(captureDeviceInput) self.previewLayer.session = captureSession self.previewLayer.videoGravity = .resizeAspectFill super.init(frame: frame) self.layer.addSublayer(self.previewLayer) self.previewLayer.frame = self.frame captureSession.startRunning() let metadataOutput = AVCaptureMetadataOutput() // metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) metadataOutput.setMetadataObjectsDelegate(delegate, queue: DispatchQueue.main) metadataOutput.rectOfInterest = CGRect(x: 0, y: 0, width: 1, height: 1) if captureSession.canAddOutput(metadataOutput) { captureSession.addOutput(metadataOutput) } else { fatalError("Can't add metadata output to capture session") } metadataOutput.metadataObjectTypes = [.qr] } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } class MetadataDelegate: NSObject, AVCaptureMetadataOutputObjectsDelegate { func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { guard let previewLayer = connection.videoPreviewLayer else { print("previewLayer was nil") return } printMetadataOutput(output, didOutput: metadataObjects, previewLayer: previewLayer) } } class ViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate { private let metadataDelegate = MetadataDelegate() override func viewDidLoad() { let captureView = CaptureView(frame: CGRect(), delegate: self.metadataDelegate) captureView.frame = self.view.frame captureView.autoresizingMask = [.flexibleHeight, .flexibleWidth] self.view.addSubview(captureView) } } 方法上,您在进行订阅的地方调用login()。订阅是事件侦听器,因此仅当您的getLoginDetails()呈现值时,变量this.loginResponse才会被填充。

最后,在您的resp方法中,您可以归因于详细说明尚未填充的内容。

所以我建议你这样做

login()

答案 3 :(得分:1)

问题是您在分配值之前没有等待响应。这是发生了什么。

您发送请求。开始等待响应。将undefined分配给details。响应到来,loginResponse生效。您发送第二个请求。将第一个请求的响应分配给details

这是解决方案:

login() {
  this.getLoginDetails().subscribe((details: LoginResponse) => {
    if(details) {
      localStorage.setItem('token', details.token); 
      localStorage.setItem('refreshToken', details.refreshToken);
      localStorage.setItem('userId', details.userId.toString());

      this.router.navigate(['/subscribers']);
    } 
  });
}

getLoginDetails(){
    var data = JSON.stringify({
        "EmailAddress": this.loginForm.controls['userName'].value,
        "Password": this.loginForm.controls['password'].value 
    });

    const httpOptions = {
        headers: new HttpHeaders({
            'Content-Type':  'application/json'
        })
    };

    return this.http.post("https://localhost:8080/api/auth/login", data, httpOptions);
}

答案 4 :(得分:1)

您可以使用异步并等待。 异步: 函数前的“异步”一词意味着一件简单的事情:函数总是返回承诺。其他值会自动包装在已解决的Promise中。

您的代码:-

    async login() {
   await this.getLoginDetails();
}

async getLoginDetails(){
    


//other implementation

    let resp = await this.http.post("https://localhost:8080/api/auth/login", data, httpOptions);

    resp.subscribe((response: LoginResponse) => {
    if(response) {
        this.loginResponse = response
    } 
  });