如何在不刷新整个页面的情况下更新组件 - Angular

55

我的页面结构是:

<app-header></app-header>
<router-outlet></router-outlet>
<app-footer></app-footer>

如何在不刷新整个页面的情况下更新/刷新app-header组件?

我想在用户成功登录后,在标题中隐藏“登录”链接。标题在所有组件/路由中都是相同的。


1
我已经发布了答案,你可以使用ngIf和服务来加载你的头部。 - Rohan Fating
7个回答

92

你可以使用BehaviorSubject在整个应用程序中不同的组件之间进行通信。你可以定义一个包含BehaviorSubject的数据共享服务,向其订阅并发出更改。

定义一个数据共享服务

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable()
export class DataSharingService {
    public isUserLoggedIn: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
}

DataSharingService添加到您的AppModule提供程序中。

接下来,在<app-header>和执行登录操作的组件中导入DataSharingService。在<app-header>中订阅对isUserLoggedIn主题的更改:

import { DataSharingService } from './data-sharing.service';

export class AppHeaderComponent { 
    // Define a variable to use for showing/hiding the Login button
    isUserLoggedIn: boolean;

    constructor(private dataSharingService: DataSharingService) {

        // Subscribe here, this will automatically update 
        // "isUserLoggedIn" whenever a change to the subject is made.
        this.dataSharingService.isUserLoggedIn.subscribe( value => {
            this.isUserLoggedIn = value;
        });
    }
}

在您的 <app-header> HTML 模板中,您需要添加 *ngIf 条件,例如:

<button *ngIf="!isUserLoggedIn">Login</button> 
<button *ngIf="isUserLoggedIn">Sign Out</button>

最后,只需在用户登录后触发该事件,例如:

someMethodThatPerformsUserLogin() {
    // Some code 
    // .....
    // After the user has logged in, emit the behavior subject changes.
    this.dataSharingService.isUserLoggedIn.next(true);
}

@Faisal 用户登录后,如果我刷新浏览器,它会重定向到登录页面。如何解决这个问题? - Vignesh
@Vignesh,你是如何进行身份验证的?有令牌吗? - FAISAL
2
您可以将令牌保存在本地存储中,然后将CanActivate Guard添加到登录组件路由中。在守卫中,检查令牌是否存在,然后将用户带到所需的路由。 - FAISAL
1
这将在页面刷新时创建一个问题...默认情况下,它会在每次页面刷新时设置为false。 - Parth Ghiya
@ParthGhiya 当然,整个应用程序将在页面刷新时重新加载。这就是单页应用程序的工作方式。 - FAISAL
1
所以,如果您再次阅读评论,我是说在页面刷新时,它应该从内存中读取而不是默认为false。 - Parth Ghiya

3
为了定期刷新组件,我发现以下是最佳方法。 在 ngOnInit 方法中使用 setTimeOut 函数。
ngOnInit(): void {
  setTimeout(() => { this.ngOnInit() }, 1000 * 10)
}
//10 is the number of seconds

1
这是刷新任何组件的正确代码。 - user1685883
这是正确的方式吗? - Kumaresan Sd
无法工作-只需调用该方法-不会重新初始化组件-不会触发其他钩子-> https://stackblitz.com/edit/angular-j84j29?file=src%2Fapp%2Ftop-bar%2Ftop-bar.component.ts - Kumaresan Sd

2

其中一种解决方案是创建一个@Injectable()类,该类保存您想要在标题中显示的数据。其他组件也可以访问此类并更改此数据,从而有效地更改标题。

另一个选项是设置@Input()变量和@Output() EventEmitters,您可以使用它们来更改标题数据。

编辑 按您的要求进行示例:

@Injectable()
export class HeaderService {
    private _data;
    set data(value) {
        this._data = value;
    }
    get data() {
        return this._data;
    }
}

在其他组件中:

constructor(private headerService: HeaderService) {}

// Somewhere
this.headerService.data = 'abc';

在头部组件中:

let headerData;

constructor(private headerService: HeaderService) {
    this.headerData = this.headerService.data;
}

我其实没有试过这个。如果get/set不起作用,你可以改成使用Subject();

// Simple Subject() example:
let subject = new Subject();
this.subject.subscribe(response => {
  console.log(response); // Logs 'hello'
});
this.subject.next('hello');

这个没问题,但是我该怎么样在登录后更改页眉中的登录链接,以便隐藏登录链接...问题在于页眉是登录页面和所有其他页面共用的。 - Aravinthan M

2

您只需要使用ChangeDetectorRef通知Angular更新组件。因此,在您的头部组件中:

constructor(private cd: ChangeDetectorRef) {}

loggedUserEvent(user: User): void {
  this.username = user.username; 
  this.enableDisconnectButton();

  //... Add more logic to it

  this.cd.detectChanges();
}

这应该足够了


1
Angular会在检测到变量改变时自动更新组件。
因此,你只需要确保标题具有对新数据的引用,它就会"刷新"。这可以通过在header.component.ts中订阅或通过@Input变量实现...
一个例子...

main.html

<app-header [header-data]="headerData"></app-header>

main.component.ts

public headerData:int = 0;

ngOnInit(){
    setInterval(()=>{this.headerData++;}, 250);
}

header.html

<p>{{data}}</p>

header.ts

@Input('header-data') data;

在上面的例子中,头部每250毫秒会接收新数据,从而更新组件。

关于Angular的生命周期钩子的更多信息,请参见:https://angular.io/guide/lifecycle-hooks


这个没问题,但是我该怎么样在登录后更改页眉中的登录链接,以便隐藏登录链接...问题在于页眉是登录页面和所有其他页面共用的。 - Aravinthan M
1
@Aravinthan 我建议使用一个服务,你可以订阅或监听事件。例如,当用户登录时,该服务可以触发一个事件,由 header.component 监听。然后 onChange 生命周期钩子将被触发,头部将更新。 - Zze

1
更新组件。
 @Injectable()
    export class LoginService{
    private isUserLoggedIn: boolean = false;

    public setLoggedInUser(flag) { // you need set header flag true false from other components on basis of your requirements, header component will be visible as per this flag then
    this.isUserLoggedIn= flag;
    }


public getUserLoggedIn(): boolean {
return this.isUserLoggedIn;
}

Login Component ts
            Login Component{
             constructor(public service: LoginService){}

public login(){
service.setLoggedInUser(true);
}
            }
Inside Header component

 Header Component ts
        HeaderComponent {
         constructor(public service: LoginService){}

         public getUserLoggedIn(): boolean { return this.service.getUserLoggedIn()}
        }

template of header component: Check for user sign in here

<button *ngIf="getUserLoggedIn()">Sign Out</button>
<button *ngIf="!getUserLoggedIn()">Sign In</button>

您可以使用多种方法,例如使用ngIf来显示或隐藏内容。

App Component ts
AppComponent {
 public showHeader: boolean = true;
}
App Component html
<div *ngIf='showHeader'> // you show hide on basis of this ngIf and header component always get visible with it's lifecycle hook ngOnInit() called all the time when it get visible
<app-header></app-header>
</div>
<router-outlet></router-outlet>
<app-footer></app-footer>

你也可以使用服务

@Injectable()
export class AppService {
private showHeader: boolean = false;

public setHeader(flag) { // you need set header flag true false from other components on basis of your requirements, header component will be visible as per this flag then
this.showHeader = flag;
}

public getHeader(): boolean {
return this.showHeader;
}
}

App Component.ts
    AppComponent {
     constructor(public service: AppService){}
    }

App Component.html
    <div *ngIf='service.showHeader'> // you show hide on basis of this ngIf and header component always get visible with it's lifecycle hook ngOnInit() called all the time when it get visible
    <app-header></app-header>
    </div>
    <router-outlet></router-outlet>
    <app-footer></app-footer>

1
不需要刷新页面,你只需要设置标志为true和false,标题将相应地显示或隐藏。请务必尝试,这会起作用的。 - Rohan Fating
1
这将隐藏整个标题,而不是OP在问题中所要求的。 - FAISAL
1
我根据评论修改了他的问题,请阅读。您的解决方案将适用于整个标题,但不是OP所要求的。此外,有更好的方法来实现他所寻找的内容。 - FAISAL
1
@AravinthanM 你需要在头部更新什么内容? - Rohan Fating
1
@AravinthanM:在这里使用服务,设置标志isUserLoggedIn为true或false,在标题组件中注入服务,然后根据您的isUserLoggedIn标志在模板中检查以显示/隐藏登录按钮。 - Rohan Fating
显示剩余4条评论

0

之前的所有答案都很好。但是为了多样性,这里提供一个不同的解决方案:

步骤1 - 在子组件.ts文件中添加一个新的事件发射器作为输出。

步骤2 - 当您的子组件执行所需操作时,触发该输出。

步骤3 - 将输出附加到父组件.html中的子选择器上。

步骤4 - 在父组件中添加必要的函数以更改数据。

完整代码将类似于:

// --- child.ts --- 

import { Component, OnInit,Input,Output,EventEmitter } from '@angular/core';

@Output() refreshData = new EventEmitter<{ update: boolean }>(); // note: the 'update' variable isn't needed in the code I've given you, but I've included it in case you want to know how to pass data in the event emitter

@Input() myData: boolean | undefined; 
 
signInOut(){
 this.refreshData.emit({update:true})
}

// --- child.html ---
<div>Signed in = {{myData}}</div>
<button (click) = signInOut()>Click me to sign in or out and refresh the data</button>

//  --- parent.html ---

<child-selector [myData] = "isSignedIn" (refreshData) = dataChange($event)></child-selector>

// --- parent.ts ---

isSignedIn = false

dataChange(eventData: { update: boolean }){
  console.log('test')
  this.isSignedIn = !this.isSignedIn 
}

希望这能帮到你!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接