Недавно я удалил 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 с проблемой, воспроизведенной для отладки, а также объясните шаги по репликации сценария.