Habilitar a autenticação em seu próprio aplicativo Angular usando o Azure Active Directory B2C
Este artigo mostra como adicionar a autenticação do Azure Active Directory B2C (Azure AD B2C) a seu próprio aplicativo Angular de página única (SPA). Saiba como integrar um aplicativo Angular com a biblioteca de autenticação do MSAL Angular.
Use este artigo com o artigo relacionado intitulado Configurar autenticação em um exemplo de aplicativo Angular de página única. Substitua o aplicativo Angular exemplo por seu próprio aplicativo Angular. Depois de concluir as etapas neste artigo, seu aplicativo aceitará as credenciais por meio do Azure AD B2C.
Pré-requisitos
Conclua as etapas no artigo Configurar autenticação em um exemplo de aplicativo Angular de página única.
Criar um projeto de aplicativo Angular
É possível usar um projeto de aplicativo Angular existente ou criar um novo. Execute os comandos a seguir para criar um novo projeto.
Os comandos:
- Instale a CLI do Angular usando o gerenciador de pacotes npm.
-
Crie um espaço de trabalho Angular com um módulo de roteamento. O nome do aplicativo é
msal-angular-tutorial
. É possível alterá-lo para qualquer nome de aplicativo Angular válido, comocontoso-car-service
. - Altere para a pasta de diretório do aplicativo.
npm install -g @angular/cli
ng new msal-angular-tutorial --routing=true --style=css --strict=false
cd msal-angular-tutorial
Instalar as dependências
Para instalar as bibliotecas do MSAL Navegador e do MSAL Angular em seu aplicativo, execute os comandos a seguir no shell de comando:
npm install @azure/msal-browser @azure/msal-angular
Instale a biblioteca de componentes Angular Material (opcional, para a interface do usuário):
npm install @angular/material @angular/cdk
Adicionar os componentes de autenticação
O código de exemplo é composto pelos componentes a seguir:
Componente | Tipo | Descrição |
---|---|---|
auth-config.ts | Constantes | Esse arquivo de configuração contém informações sobre o seu provedor de identidade do Azure AD B2C e o serviço da API Web. O aplicativo Angular usa essas informações para estabelecer uma relação de confiança com o Azure AD B2C, conectar e desconectar o usuário, adquirir tokens e validá-los. |
app.module.ts | Módulo Angular | Este componente descreve como as partes do aplicativo se encaixam. Esse é o módulo raiz que é usado para inicializar e abrir o aplicativo. Neste tutorial, você vai adicionar alguns componentes ao módulo app.module.ts e iniciar a biblioteca MSAL com o objeto de configuração MSAL. |
app-routing.module.ts | Módulo de roteamento Angular | Este componente permite a navegação interpretando uma URL do navegador e carregando o componente correspondente. Neste tutorial, você vai adicionar alguns componentes ao módulo de roteamento e proteger os componentes com a MSAL Guard. Somente usuários autorizados podem acessar os componentes protegidos. |
app.component.* | Componente Angular | O comando ng new criou um projeto Angular com um componente raiz. Neste tutorial, você vai alterar o componente do aplicativo para hospedar a barra de navegação superior. A barra de navegação contém vários botões, incluindo botões de logon e de saída. A classe app.component.ts lida com os eventos de logon e de saída. |
home.component.* | Componente Angular | Neste tutorial, você vai adicionar o componente home para renderizar a página inicial para acesso anônimo. Este componente demonstra como verificar se um usuário entrou. |
profile.component.* | Componente Angular | Neste tutorial, você vai adicionar o componente profile para saber como ler as declarações de token de ID. |
webapi.component.* | Componente Angular | Neste tutorial, você vai adicionar o componente webapi para saber como chamar uma API Web. |
Para adicionar os componentes seguintes ao seu aplicativo, execute os seguintes comandos do CLI do Angular. Os comandos generate component
:
- Crie uma pasta para cada componente. A pasta contém os arquivos TypeScript, HTML, CSS e os arquivos de teste.
- Atualize os arquivos
app.module.ts
eapp-routing.module.ts
com referências aos novos componentes.
ng generate component home
ng generate component profile
ng generate component webapi
Adicionar as configurações do aplicativo
As configurações para o provedor de identidade do Azure AD B2C e a API Web são armazenadas no arquivo auth-config.ts. Na pasta src/app, crie um arquivo chamado auth-config.ts que contenha o código a seguir. Em seguida, altere as configurações conforme descrito em 3.1 Configurar o exemplo do Angular.
import { LogLevel, Configuration, BrowserCacheLocation } from '@azure/msal-browser';
const isIE = window.navigator.userAgent.indexOf("MSIE ") > -1 || window.navigator.userAgent.indexOf("Trident/") > -1;
export const b2cPolicies = {
names: {
signUpSignIn: "b2c_1_susi_reset_v2",
editProfile: "b2c_1_edit_profile_v2"
},
authorities: {
signUpSignIn: {
authority: "https://your-tenant-name.b2clogin.com/your-tenant-name.onmicrosoft.com/b2c_1_susi_reset_v2",
},
editProfile: {
authority: "https://your-tenant-name.b2clogin.com/your-tenant-name.onmicrosoft.com/b2c_1_edit_profile_v2"
}
},
authorityDomain: "your-tenant-name.b2clogin.com"
};
export const msalConfig: Configuration = {
auth: {
clientId: '<your-MyApp-application-ID>',
authority: b2cPolicies.authorities.signUpSignIn.authority,
knownAuthorities: [b2cPolicies.authorityDomain],
redirectUri: '/',
},
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: isIE,
},
system: {
loggerOptions: {
loggerCallback: (logLevel, message, containsPii) => {
console.log(message);
},
logLevel: LogLevel.Verbose,
piiLoggingEnabled: false
}
}
}
export const protectedResources = {
todoListApi: {
endpoint: "http://localhost:5000/api/todolist",
scopes: ["https://your-tenant-name.onmicrosoft.com/api/tasks.read"],
},
}
export const loginRequest = {
scopes: []
};
Inicie as bibliotecas de autenticação
Os aplicativos cliente públicos não são confiáveis para manter com segurança os segredos do aplicativo, portanto, não contêm segredos do cliente. Na pasta src/app, abra o app.module.tse faça as seguintes alterações:
- Importe as bibliotecas MSAL Angular e MSAL Browser.
- Atualize o módulo de configuração do Azure AD B2C.
- Importe
HttpClientModule
. O cliente HTTP é usado para chamar as APIs Web. - Importe o interceptador HTTP Angular. A MSAL usa o Interceptor para injetar o token de portador no cabeçalho de autorização HTTP.
- Adicione os materiais Angular essenciais.
- Crie uma instância de MSAL usando um objeto de aplicativo de cliente público de várias contas. A inicialização do MSAL inclui a passagem:
- Do objeto de configuração para o auth-config.ts.
- Do objeto de configuração para a cláusula de proteção de roteamento.
- Do objeto de configuração para o interceptador do MSAL. A classe do interceptador adquire automaticamente tokens para solicitações de saída que usam a classe HttpClient Angular para recursos protegidos conhecidos.
- Configure o
HTTP_INTERCEPTORS
eMsalGuard
provedores do Angular . - Adicione
MsalRedirectComponent
à inicialização do Angular.
Na pasta src/app, edite o app.module.ts e faça as modificações mostradas no trecho de código a seguir. As alterações são sinalizadas com "As alterações começam aqui" e "As alterações terminam aqui".
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
/* Changes start here. */
// Import MSAL and MSAL browser libraries.
import { MsalGuard, MsalInterceptor, MsalModule, MsalRedirectComponent } from '@azure/msal-angular';
import { InteractionType, PublicClientApplication } from '@azure/msal-browser';
// Import the Azure AD B2C configuration
import { msalConfig, protectedResources } from './auth-config';
// Import the Angular HTTP interceptor.
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { ProfileComponent } from './profile/profile.component';
import { HomeComponent } from './home/home.component';
import { WebapiComponent } from './webapi/webapi.component';
// Add the essential Angular materials.
import { MatButtonModule } from '@angular/material/button';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatListModule } from '@angular/material/list';
import { MatTableModule } from '@angular/material/table';
/* Changes end here. */
@NgModule({
declarations: [
AppComponent,
ProfileComponent,
HomeComponent,
WebapiComponent
],
imports: [
BrowserModule,
AppRoutingModule,
/* Changes start here. */
// Import the following Angular materials.
MatButtonModule,
MatToolbarModule,
MatListModule,
MatTableModule,
// Import the HTTP client.
HttpClientModule,
// Initiate the MSAL library with the MSAL configuration object
MsalModule.forRoot(new PublicClientApplication(msalConfig),
{
// The routing guard configuration.
interactionType: InteractionType.Redirect,
authRequest: {
scopes: protectedResources.todoListApi.scopes
}
},
{
// MSAL interceptor configuration.
// The protected resource mapping maps your web API with the corresponding app scopes. If your code needs to call another web API, add the URI mapping here.
interactionType: InteractionType.Redirect,
protectedResourceMap: new Map([
[protectedResources.todoListApi.endpoint, protectedResources.todoListApi.scopes]
])
})
/* Changes end here. */
],
providers: [
/* Changes start here. */
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true
},
MsalGuard
/* Changes end here. */
],
bootstrap: [
AppComponent,
/* Changes start here. */
MsalRedirectComponent
/* Changes end here. */
]
})
export class AppModule { }
Configurar rotas
Nesta seção, configure as rotas para o seu aplicativo Angular. Quando um usuário seleciona um link na página para mover-se dentro de seu aplicativo de página única ou insere uma URL na barra de endereços, as rotas mapeiam a URL para um componente Angular. A interface de roteamento angular canActivate usa o MSAL Guard para verificar se o usuário está conectado. Se o usuário não estiver conectado, o MSAL leva o usuário ao Azure AD B2C para que ele seja autenticado.
Na pasta src/app, edite o app-routing.module.ts e faça as modificações mostradas no trecho de código a seguir. As alterações são sinalizadas com "As alterações começam aqui" e "As alterações terminam aqui".
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { MsalGuard } from '@azure/msal-angular';
import { HomeComponent } from './home/home.component';
import { ProfileComponent } from './profile/profile.component';
import { WebapiComponent } from './webapi/webapi.component';
const routes: Routes = [
/* Changes start here. */
{
path: 'profile',
component: ProfileComponent,
// The profile component is protected with MSAL Guard.
canActivate: [MsalGuard]
},
{
path: 'webapi',
component: WebapiComponent,
// The profile component is protected with MSAL Guard.
canActivate: [MsalGuard]
},
{
// The home component allows anonymous access
path: '',
component: HomeComponent
}
/* Changes end here. */
];
@NgModule({
/* Changes start here. */
// Replace the following line with the next one
//imports: [RouterModule.forRoot(routes)],
imports: [RouterModule.forRoot(routes, {
initialNavigation:'enabled'
})],
/* Changes end here. */
exports: [RouterModule]
})
export class AppRoutingModule { }
Adicionar os botões de entrada e saída
Nesta seção, você vai adicionar os botões de entrada e saída do componente do app. Na pasta src/app, abra o app.component.tse faça as seguintes alterações:
Importe os componentes obrigatórios.
Altere a classe para implementar o método OnInit. O método
OnInit
assina o evento observávelinProgress$
do MSAL MsalBroadcastService. Use esse evento para saber o status das interações do usuário, especialmente para verificar se as interações foram concluídas.Antes das interações com o objeto de conta MSAL, verifique se a
InteractionStatus
propriedade retornaInteractionStatus.None
. O eventosubscribe
chama o métodosetLoginDisplay
para verificar se o usuário está autenticado.Adicione variáveis de classe.
Adicione o método
login
que inicia o fluxo de autorização.Adicione o método
logout
que desconecta o usuário.Adicione o método
setLoginDisplay
que verifica se o usuário está autenticado.Adicione o método ngOnDestroy para limpar o evento de assinatura
inProgress$
.
Após as alterações, o código deve ser semelhante ao seguinte trecho de código:
import { Component, OnInit, Inject } from '@angular/core';
import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular';
import { InteractionStatus, RedirectRequest } from '@azure/msal-browser';
import { Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
/* Changes start here. */
export class AppComponent implements OnInit{
title = 'msal-angular-tutorial';
loginDisplay = false;
private readonly _destroying$ = new Subject<void>();
constructor(@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration, private broadcastService: MsalBroadcastService, private authService: MsalService) { }
ngOnInit() {
this.broadcastService.inProgress$
.pipe(
filter((status: InteractionStatus) => status === InteractionStatus.None),
takeUntil(this._destroying$)
)
.subscribe(() => {
this.setLoginDisplay();
})
}
login() {
if (this.msalGuardConfig.authRequest){
this.authService.loginRedirect({...this.msalGuardConfig.authRequest} as RedirectRequest);
} else {
this.authService.loginRedirect();
}
}
logout() {
this.authService.logoutRedirect({
postLogoutRedirectUri: 'http://localhost:4200'
});
}
setLoginDisplay() {
this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
}
ngOnDestroy(): void {
this._destroying$.next(undefined);
this._destroying$.complete();
}
/* Changes end here. */
}
Na pasta src/app, edite o app.component.htmle faça as seguintes alterações:
- Adicione um link para o perfil e os componentes da API Web.
- Adicione o botão de logon com o atributo de evento de clique definido como o método
login()
. Esse botão só aparecerá se a variável de classeloginDisplay
forfalse
. - Adicione o botão de logout com o atributo de evento de clique definido como o método
logout()
. Esse botão só aparecerá se a variável de classeloginDisplay
fortrue
. - Adicione um elemento router-outlet .
Após as alterações, o código deve ser semelhante ao seguinte trecho de código:
<mat-toolbar color="primary">
<a class="title" href="/">{{ title }}</a>
<div class="toolbar-spacer"></div>
<a mat-button [routerLink]="['profile']">Profile</a>
<a mat-button [routerLink]="['webapi']">Web API</a>
<button mat-raised-button *ngIf="!loginDisplay" (click)="login()">Login</button>
<button mat-raised-button *ngIf="loginDisplay" (click)="logout()">Logout</button>
</mat-toolbar>
<div class="container">
<router-outlet></router-outlet>
</div>
Opcionalmente, atualize o app.component.css com o seguinte trecho de CSS:
.toolbar-spacer {
flex: 1 1 auto;
}
a.title {
color: white;
}
Manipular os redirecionamentos de aplicativo
Ao estiver usando redirecionamentos com o MSAL, deverá adicionar a diretiva app-redirect ao arquivo index.html. Na pasta src, edite o index.html conforme mostrado no trecho de código a seguir:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>MsalAngularTutorial</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
<!-- Changes start here -->
<app-redirect></app-redirect>
<!-- Changes end here -->
</body>
</html>
Definir CSS do aplicativo (opcional)
Na pasta /src, atualize o styles.css com o seguinte trecho de código do CSS:
@import '~@angular/material/prebuilt-themes/deeppurple-amber.css';
html, body { height: 100%; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
.container { margin: 1%; }
Dica
Neste ponto, você pode executar seu aplicativo e testar a experiência de entrada. Para executar o aplicativo, consulte a seção Executar o aplicativo Angular.
Verificar se um usuário está autenticado
O arquivo home.component demonstra como verificar se um usuário está autenticado. Na pasta src/app/home, atualize home.component.ts com o trecho de código a seguir.
O código:
- Assina os evento observáveis
msalSubject$
einProgress$
do MSAL MsalBroadcastService. - Garante que o evento
msalSubject$
grave o resultado da autenticação no console do navegador. - Garante que o evento
inProgress$
verifique se um usuário está autenticado. O métodogetAllAccounts()
retorna um ou mais objetos.
import { Component, OnInit } from '@angular/core';
import { MsalBroadcastService, MsalService } from '@azure/msal-angular';
import { EventMessage, EventType, InteractionStatus } from '@azure/msal-browser';
import { filter } from 'rxjs/operators';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
loginDisplay = false;
constructor(private authService: MsalService, private msalBroadcastService: MsalBroadcastService) { }
ngOnInit(): void {
this.msalBroadcastService.msalSubject$
.pipe(
filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_SUCCESS),
)
.subscribe((result: EventMessage) => {
console.log(result);
});
this.msalBroadcastService.inProgress$
.pipe(
filter((status: InteractionStatus) => status === InteractionStatus.None)
)
.subscribe(() => {
this.setLoginDisplay();
})
}
setLoginDisplay() {
this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
}
}
Na pasta src/app/home, atualize o arquivo home.component.html com o trecho HTML a seguir. A diretiva *ngIf verifica a variável de classe loginDisplay
para mostrar ou ocultar as mensagens de boas-vindas.
<div *ngIf="!loginDisplay">
<p>Please sign-in to see your profile information.</p>
</div>
<div *ngIf="loginDisplay">
<p>Login successful!</p>
<p>Request your profile information by clicking Profile above.</p>
</div>
Ler as declarações de token de ID
O arquivo profile.component demonstra como acessar as declarações de token de ID do usuário. Na pasta src/app/home, atualize profile.component.ts com o trecho de código a seguir.
O código:
- Importa os componentes obrigatórios.
- Assina o evento observável
inProgress$
do MSAL MsalBroadcastService. O evento carrega a conta e lê as declarações do token de ID. - Garante que o método
checkAndSetActiveAccount
verifique e defina a conta ativa. Esta ação é comum quando o aplicativo interage com vários fluxos de usuário Azure AD B2C ou com políticas personalizadas. - Certifique-se de que o método
getClaims
obtém as declarações de token de ID do objeto de conta MSAL ativo. Em seguida, o método adiciona as declarações à matrizdataSource
. A matriz é renderizada para o usuário com a associação de modelo do componente.
import { Component, OnInit } from '@angular/core';
import { MsalBroadcastService, MsalService } from '@azure/msal-angular';
import { EventMessage, EventType, InteractionStatus } from '@azure/msal-browser';
import { Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.css']
})
export class ProfileComponent implements OnInit {
displayedColumns: string[] = ['claim', 'value'];
dataSource: Claim[] = [];
private readonly _destroying$ = new Subject<void>();
constructor(private authService: MsalService, private msalBroadcastService: MsalBroadcastService) { }
ngOnInit(): void {
this.msalBroadcastService.inProgress$
.pipe(
filter((status: InteractionStatus) => status === InteractionStatus.None || status === InteractionStatus.HandleRedirect),
takeUntil(this._destroying$)
)
.subscribe(() => {
this.checkAndSetActiveAccount();
this.getClaims(this.authService.instance.getActiveAccount()?.idTokenClaims)
})
}
checkAndSetActiveAccount() {
let activeAccount = this.authService.instance.getActiveAccount();
if (!activeAccount && this.authService.instance.getAllAccounts().length > 0) {
let accounts = this.authService.instance.getAllAccounts();
this.authService.instance.setActiveAccount(accounts[0]);
}
}
getClaims(claims: any) {
let list: Claim[] = new Array<Claim>();
Object.keys(claims).forEach(function(k, v){
let c = new Claim()
c.id = v;
c.claim = k;
c.value = claims ? claims[k]: null;
list.push(c);
});
this.dataSource = list;
}
ngOnDestroy(): void {
this._destroying$.next(undefined);
this._destroying$.complete();
}
}
export class Claim {
id: number;
claim: string;
value: string;
}
Na pasta src/app/profile, atualize profile.component.html com o trecho HTML a seguir:
<h1>ID token claims:</h1>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!-- Claim Column -->
<ng-container matColumnDef="claim">
<th mat-header-cell *matHeaderCellDef> Claim </th>
<td mat-cell *matCellDef="let element"> {{element.claim}} </td>
</ng-container>
<!-- Value Column -->
<ng-container matColumnDef="value">
<th mat-header-cell *matHeaderCellDef> Value </th>
<td mat-cell *matCellDef="let element"> {{element.value}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
Chamar uma API da Web
Para chamar uma API Web de autorização baseada em token, o aplicativo precisa ter um token de acesso válido. O provedor MsalInterceptor adquire automaticamente tokens para solicitações de saída que usam a classe HttpClient Angular para recursos protegidos conhecidos.
Importante
O método de inicialização MSAL (na classe app.module.ts
) mapeia recursos protegidos, como APIs Web, aos escopos de aplicativo necessários usando o objeto protectedResourceMap
. Se seu código precisar chamar outra API Web, adicione o URI da API Web e o método HTTP da API Web com os escopos correspondentes ao objeto protectedResourceMap
. Para saber mais, confira Mapa de recursos protegido.
Quando o objeto HttpClient chama uma API Web, o provedor MsalInterceptor executa as seguintes etapas:
Adquire um token de acesso com as permissões necessárias (escopos) para o ponto de extremidade da API Web.
Passa o token de acesso como um token de portador no cabeçalho de autorização da solicitação HTTP usando este formato:
Authorization: Bearer <access-token>
O arquivo webapi.component demonstra como chamar a API Web. Na pasta src/app/webapi, atualize webapi.component.ts com o trecho de código a seguir.
O código:
- Usa a classe HttpClient Angular para chamar a API Web.
- Lê o elemento
auth-config
da classeprotectedResources.todoListApi.endpoint
. Esse elemento especifica o URI da API Web. Com base no URI da API Web, o interceptador MSAL adquire um token de acesso com os escopos correspondentes. - Obtém o perfil da API Web e define a variável de classe
profile
.
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { protectedResources } from '../auth-config';
type ProfileType = {
name?: string
};
@Component({
selector: 'app-webapi',
templateUrl: './webapi.component.html',
styleUrls: ['./webapi.component.css']
})
export class WebapiComponent implements OnInit {
todoListEndpoint: string = protectedResources.todoListApi.endpoint;
profile!: ProfileType;
constructor(
private http: HttpClient
) { }
ngOnInit() {
this.getProfile();
}
getProfile() {
this.http.get(this.todoListEndpoint)
.subscribe(profile => {
this.profile = profile;
});
}
}
Na pasta src/app/webapi, atualize o arquivo webapi.component.html com o trecho HTML a seguir. O modelo do componente renderiza o nome que a API Web retorna. Na parte inferior da página, o modelo renderiza o endereço da API Web.
<h1>The web API returns:</h1>
<div>
<p><strong>Name: </strong> {{profile?.name}}</p>
</div>
<div class="footer-text">
Web API: {{todoListEndpoint}}
</div>
Opcionalmente, atualize o arquivo webapi.component.css com o seguinte trecho de CSS:
.footer-text {
position: absolute;
bottom: 50px;
color: gray;
}
Executar o aplicativo Angular
Execute o comando a seguir:
npm start
A janela de console exibe o número da porta em que o aplicativo está hospedado.
Listening on port 4200...
Dica
Como alternativa para executar o comando npm start
, você pode usar o depurador do Visual Studio Code. O depurador ajuda a acelerar o loop de edição, compilação e depuração.
Acesse http://localhost:4200
no navegador para exibir o aplicativo.