I'm trying something new and that would be using ASP.NET Core as my backend while using Angular for the frontend. While the backend was a breeze to configure, the frontend haven't been as close. When I try running the application, I receive the following errors in teh console:
> integrafechado@0.0.0 prestart
> node aspnetcore-https
> integrafechado@0.0.0 start
> run-script-os
> integrafechado@0.0.0 start:windows
> ng serve --port 44488 --ssl --ssl-cert "%APPDATA%\ASP.NET\https\%npm_package_name%.pem" --ssl-key "%APPDATA%\ASP.NET\https\%npm_package_name%.key"
Initial Chunk Files | Names | Raw Size
vendor.js | vendor | 3.03 MB |
styles.css, styles.js | styles | 440.48 kB |
polyfills.js | polyfills | 434.76 kB |
main.js | main | 181.56 kB |
runtime.js | runtime | 6.53 kB |
| Initial Total | 4.07 MB
Build at: 2023-06-12T15:15:32.894Z - Hash: bdb0de9435970ca7 - Time: 2757ms
Error: src/api-authorization/login/login.component.html:6:13 - error NG8002: Can't bind to 'formGroup' since it isn't a known property of 'form'.
6 [formGroup]="loginForm"
~~~~~~~~~~~~~~~~~~~~~~~
src/api-authorization/login/login.component.ts:16:15
16 templateUrl: "./login.component.html",
~~~~~~~~~~~~~~~~~~~~~~~~
Error occurs in the template of component LoginComponent.
Error: src/api-authorization/login/login.component.html:6:26 - error TS2339: Property 'loginForm' does not exist on type 'LoginComponent'.
6 [formGroup]="loginForm"
~~~~~~~~~
src/api-authorization/login/login.component.ts:16:15
16 templateUrl: "./login.component.html",
~~~~~~~~~~~~~~~~~~~~~~~~
Error occurs in the template of component LoginComponent.
Error: src/api-authorization/login/login.component.html:7:25 - error TS2341: Property 'login' is private and only accessible within class 'LoginComponent'.
7 (ngSubmit)="login(loginForm.value)"
~~~~~
src/api-authorization/login/login.component.ts:16:15
16 templateUrl: "./login.component.html",
~~~~~~~~~~~~~~~~~~~~~~~~
Error occurs in the template of component LoginComponent.
Error: src/api-authorization/login/login.component.html:7:31 - error TS2339: Property 'loginForm' does not exist on type 'LoginComponent'.
7 (ngSubmit)="login(loginForm.value)"
~~~~~~~~~
src/api-authorization/login/login.component.ts:16:15
16 templateUrl: "./login.component.html",
~~~~~~~~~~~~~~~~~~~~~~~~
Error occurs in the template of component LoginComponent.
Error: src/api-authorization/login/login.component.html:69:1 - error NG8001: 'app-footer' is not a known element:
1. If 'app-footer' is an Angular component, then verify that it is part of this module.
2. If 'app-footer' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
69 <app-footer></app-footer>
~~~~~~~~~~~~
src/api-authorization/login/login.component.ts:16:15
16 templateUrl: "./login.component.html",
~~~~~~~~~~~~~~~~~~~~~~~~
Error occurs in the template of component LoginComponent.
** Angular Live Development Server is listening on localhost:44488, open your browser on https://localhost:44488/ **
× Failed to compile.
Here's the login.component.html file:
<div class="login-container">
<div id="container-main" class="login-container-main">
<label id="login-label" for="login-form" class="login-text">Login</label>
<div id="container-form" class="login-container-form">
<form id="login-form"
[formGroup]="loginForm"
(ngSubmit)="login(loginForm.value)"
name="login-form"
target="self"
enctype="application/x-www-form-urlencoded"
class="login-form">
<div class="login-container-labels">
<div id="email-label-div" class="login-container1">
<label id="email-label" for="email-input" class="login-text1">
E-mail
</label>
</div>
<div id="senha-label-div" class="login-container2">
<label id="password-label" for="password-input" class="login-text2">
Senha
</label>
</div>
<div id="register-div" class="login-container3">
<label id="noregister-label"
for="register-button"
class="login-text3">
Não tem conta?
</label>
<button id="register-button"
name="register-button"
type="button"
class="login-button Link button">
Registrar
</button>
</div>
</div>
<div class="login-container-inputs">
<div id="email-input-div" class="login-container4">
<input formControlName="email"
type="email"
id="email-input"
required
autofocus
placeholder="exemplo@exemplo.com"
class="input" />
</div>
<div id="senha-input-div" class="login-container5">
<input formControlName="password"
type="password"
id="password-input"
required
minlength="8"
placeholder="Sua senha"
class="input" />
</div>
<div id="login-div" class="login-container6">
<button type="submit" class="login-button1 button">
<span class="login-text4">
<span class="login-text5">Logar</span>
<br />
</span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<app-footer></app-footer>
login.module.ts:
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { FooterComponent } from "./footer.component";
@NgModule({
declarations: [
FooterComponent
],
imports: [
CommonModule
],
exports: [
FooterComponent
]
})
export class FooterModule { }
login.component.ts:
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-useless-escape */
/* eslint-disable no-case-declarations */
import { Component, OnInit } from "@angular/core";
import { AuthorizeService, AuthenticationResultStatus } from "../authorize.service";
import { ActivatedRoute, Router } from "@angular/router";
import { BehaviorSubject } from "rxjs";
import { LoginActions, QueryParameterNames, ApplicationPaths, ReturnUrlType } from "../api-authorization.constants";
// The main responsibility of this component is to handle the user's login process.
// This is the starting point for the login process. Any component that needs to authenticate
// a user can simply perform a redirect to this component with a returnUrl query parameter and
// let the component perform the login and return back to the return url.
@Component({
selector: "app-login",
templateUrl: "./login.component.html",
styleUrls: ["./login.component.css"]
})
export class LoginComponent implements OnInit {
public message = new BehaviorSubject<string | null | undefined>(null);
constructor(
private authorizeService: AuthorizeService,
private activatedRoute: ActivatedRoute,
private router: Router) { }
async ngOnInit() {
const action = this.activatedRoute.snapshot.url[1];
switch (action.path) {
case LoginActions.Login:
await this.login(this.getReturnUrl());
break;
case LoginActions.LoginCallback:
await this.processLoginCallback();
break;
case LoginActions.LoginFailed:
const message = this.activatedRoute.snapshot.queryParamMap.get(QueryParameterNames.Message);
this.message.next(message);
break;
case LoginActions.Profile:
this.redirectToProfile();
break;
case LoginActions.Register:
this.redirectToRegister();
break;
default:
throw new Error(`Invalid action '${action}'`);
}
}
private async login(returnUrl: string): Promise<void> {
const state: INavigationState = { returnUrl };
const result = await this.authorizeService.signIn(state);
this.message.next(undefined);
switch (result.status) {
case AuthenticationResultStatus.Redirect:
break;
case AuthenticationResultStatus.Success:
await this.navigateToReturnUrl(returnUrl);
break;
case AuthenticationResultStatus.Fail:
await this.router.navigate(ApplicationPaths.LoginFailedPathComponents, {
queryParams: { [QueryParameterNames.Message]: result.message }
});
break;
default:
throw new Error(`Invalid status result ${(result as any).status}.`);
}
}
private async processLoginCallback(): Promise<void> {
const url = window.location.href;
const result = await this.authorizeService.completeSignIn(url);
switch (result.status) {
case AuthenticationResultStatus.Redirect:
// There should not be any redirects as completeSignIn never redirects.
throw new Error("Should not redirect.");
case AuthenticationResultStatus.Success:
await this.navigateToReturnUrl(this.getReturnUrl(result.state));
break;
case AuthenticationResultStatus.Fail:
this.message.next(result.message);
break;
}
}
private redirectToRegister(): any {
this.redirectToApiAuthorizationPath(
`${ApplicationPaths.IdentityRegisterPath}?returnUrl=${encodeURI("/" + ApplicationPaths.Login)}`);
}
private redirectToProfile(): void {
this.redirectToApiAuthorizationPath(ApplicationPaths.IdentityManagePath);
}
private async navigateToReturnUrl(returnUrl: string) {
// It's important that we do a replace here so that we remove the callback uri with the
// fragment containing the tokens from the browser history.
await this.router.navigateByUrl(returnUrl, {
replaceUrl: true
});
}
private getReturnUrl(state?: INavigationState): string {
const fromQuery = (this.activatedRoute.snapshot.queryParams as INavigationState).returnUrl;
// If the url is coming from the query string, check that is either
// a relative url or an absolute url
if (fromQuery &&
!(fromQuery.startsWith(`${window.location.origin}/`) ||
/\/[^\/].*/.test(fromQuery))) {
// This is an extra check to prevent open redirects.
throw new Error("Invalid return url. The return url needs to have the same origin as the current page.");
}
return (state && state.returnUrl) ||
fromQuery ||
ApplicationPaths.DefaultLoginRedirectPath;
}
private redirectToApiAuthorizationPath(apiAuthorizationPath: string) {
// It's important that we do a replace here so that when the user hits the back arrow on the
// browser they get sent back to where it was on the app instead of to an endpoint on this
// component.
const redirectUrl = `${window.location.origin}/${apiAuthorizationPath}`;
window.location.replace(redirectUrl);
}
}
interface INavigationState {
[ReturnUrlType]: string;
}
and finally, footer.module.ts:
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { FooterComponent } from "./footer.component";
@NgModule({
declarations: [
FooterComponent
],
imports: [
CommonModule
],
exports: [
FooterComponent
]
})
export class FooterModule { }
I'm not completely sure about what is right or wrong, VS2022 creates the new project with the base for authentication in a folder called api-authorization, but I tried using Angular's forms, although ASP.NET Core handles authentication with the middlewares. As far as I've seen, you have to create the login template for the application. Is there a simpler way to use the created login html? I'd be happy to provide more details if needed