Недавно я удалил zone.js
из своего проекта Angular 18, чтобы оптимизировать производительность, и теперь столкнулся с проблемой, связанной с тем, что крючок жизненного цикла ngAfterViewInit
не срабатывает последовательно в компоненте с ChangeDetectionStrategy.OnPush
. Мой текущий обходной путь включает использование ApplicationRef.tick()
, но я ищу более подходящее решение, которое соответствует реактивной парадигме и не полагается на триггеры обнаружения изменений вручную.
Контекст:
zone.js
, но это привело к тому, что перехватчики жизненного цикла не сработали должным образом.MatSidenav
Angular Material и контролируется с помощью реактивных сигналов от @angular/core
.admin.comComponent.ts:
@Component({
selector: 'app-admin',
templateUrl: './admin.component.html',
styleUrls: ['./admin.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export default class AdminComponent implements OnDestroy, AfterViewInit {
sidenav = viewChild.required<MatSidenav>(MatSidenav);
private destroy$ = new Subject<void>();
private observer = inject(BreakpointObserver);
private router = inject(Router);
private accountService = inject(AccountService);
private cdr = inject(ChangeDetectorRef);
private appRef = inject(ApplicationRef);
constructor() {
this.router.events.pipe(
filter(e => e instanceof NavigationEnd), takeUntil(this.destroy$)
).subscribe(() => {
this.appRef.tick();
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
ngAfterViewInit() {
this.observer
.observe(['(max-width: 800px)'])
.pipe(takeUntil(this.destroy$), delay(1))
.subscribe((res: { matches: boolean }) => {
this.setSidenav(res.matches);
this.cdr.detectChanges();
});
this.router.events
.pipe(
takeUntil(this.destroy$),
filter((e) => e instanceof NavigationEnd)
)
.subscribe(() => {
if (this.sidenav().mode === 'over') {
this.setSidenav(false);
}
});
}
logout() {
this.accountService.logout();
}
private setSidenav(matches: boolean) {
if (matches) {
this.sidenav().mode = 'over';
this.sidenav().close();
} else {
this.sidenav().mode = 'side';
this.sidenav().open();
}
}
}
admin.comComponent.html:
<mat-toolbar color = "primary" class = "mat-elevation-z8">
<button mat-icon-button *ngIf = "sidenav.mode === 'over'" (click) = "sidenav.toggle()">
<mat-icon *ngIf = "!sidenav.opened">menu</mat-icon>
<mat-icon *ngIf = "sidenav.opened">close</mat-icon>
</button>
<span class = "admin-panel-title">Admin Panel</span>
</mat-toolbar>
<mat-sidenav-container>
<mat-sidenav #sidenav = "matSidenav" class = "mat-elevation-z8">
<div class = "logo-container">
<img routerLink = "/" alt = "logo" class = "avatar mat-elevation-z8 logo-admin" src = "../../../../assets/img/logo-s.png" />
</div>
<mat-divider></mat-divider>
<ul class = "nav-list">
<li class = "nav-item">
<a routerLink = "/admin" routerLinkActive = "active" [routerLinkActiveOptions] = "{exact:true}">
<mat-icon class = "nav-icon">home</mat-icon>
<span>Dashboard</span>
</a>
</li>
<li class = "nav-item">
<a routerLink = "/admin/products" routerLinkActive = "active">
<mat-icon class = "nav-icon">library_books</mat-icon>
<span>Products</span>
</a>
</li>
<li class = "nav-item">
<a routerLink = "/admin/brands" routerLinkActive = "active">
<mat-icon class = "nav-icon">branding_watermark</mat-icon>
<span>Brands</span>
</a>
</li>
<li class = "nav-item">
<a routerLink = "/admin/product-types" routerLinkActive = "active">
<mat-icon class = "nav-icon">branding_watermark</mat-icon>
<span>Types</span>
</a>
</li>
<li class = "nav-item">
<a routerLink = "/admin/users" routerLinkActive = "active">
<mat-icon class = "nav-icon">supervisor_account</mat-icon>
<span>Users</span>
</a>
</li>
<li class = "nav-item">
<a (click) = "logout()" class = "nav-item-logout" routerLinkActive = "active">
<i class = "fa fa-sign-out fa-2x nav-icon-logout"></i>
<span>Logout</span>
</a>
</li>
</ul>
<mat-divider></mat-divider>
</mat-sidenav>
<mat-sidenav-content>
<div class = "content mat-elevation-z8">
<router-outlet></router-outlet>
</div>
</mat-sidenav-content>
</mat-sidenav-container>
ngAfterViewInit()
в моем AdminComponent
регистрируется только тогда, когда я явно вызываю this.appRef.tick()
. Однако я бы предпочел не использовать ApplicationRef
для этой цели и ищу решение, которое позволит Angular более естественно обрабатывать обновления, не возвращаясь к zone.js
.
Проблема:
ngAfterViewInit()
не срабатывает должным образом без ручного вмешательства через ApplicationRef.tick()
.zone.js
, что указывает на проблему с механизмом обнаружения изменений Angular в среде без зон.Вопрос:
Как я могу гарантировать, что перехватчики жизненного цикла, такие как ngAfterViewInit
, активируются соответствующим образом в приложении Angular с помощью ChangeDetectionStrategy.OnPush
, когда zone.js
удаляется? Существуют ли какие-либо рекомендуемые методы или шаблоны для управления обнаружением изменений вручную в таких случаях?
Что я пробовал:
ApplicationRef.tick()
для запуска обнаружения изменений вручную.ChangeDetectorRef.detectChanges()
внутри подписок.NgZone.run()
для выполнения кода, обновляющего представление.Ни один из этих методов не смог полностью интегрироваться в реактивную архитектуру, к которой я стремлюсь. Я ищу более интегрированный или рекомендованный Angular подход, который повышает производительность без ущерба для реактивности и удобства сопровождения.
Я нашел решение своей проблемы. Проблема была связана с запуском моего приложения Angular без zone.js
и использованием компонентов Angular Material, в частности MatSidenav
.
Чтобы решить эту проблему, мне нужно было использовать функцию provideExperimentalZonelessChangeDetection()
в массиве поставщиков моего AppModule
. Эта настройка гарантирует, что Angular сможет правильно обрабатывать обнаружение изменений в бесзональной конфигурации.
AppModule
:
app.module.ts
import { NgModule, provideExperimentalZonelessChangeDetection } from
@NgModule({
declarations: [
AppComponent,
],
bootstrap: [AppComponent],
imports: [
],
providers: [
provideExperimentalZonelessChangeDetection(), // Add this line
]
})
export class AppModule { }
provideExperimentalZonelessChangeDetection()
в массив поставщиков, Angular настраивается на правильную обработку обнаружения изменений без zone.js
.MatSidenav
, которые для правильной работы полагаются на обнаружение изменений.После внесения этого изменения мое приложение отлично работает без zone.js
.
пожалуйста, поделитесь репозиторием stackblitz или github с проблемой, воспроизведенной для отладки, а также объясните шаги по репликации сценария.