【问题标题】:HTML template doesn't update unless I call detectChanges() using changeDetection onpush除非我使用 changeDetection onpush 调用 detectChanges(),否则 HTML 模板不会更新
【发布时间】:2018-05-02 23:34:55
【问题描述】:

我有一个使用推送更改检测的 Angular 组件。 组件有一个输入,当路由更新时组件会修改该输入。 如果我将输入分配给新引用并对其进行修改,则模板中的值不会更新。我以为只要您分配了一个新对象,就会检测到更改,但除非我在 ChangeDetectorRef 上调用 detectChanges()。

@Component({
    selector: 'app-nav-links',
    templateUrl: './nav-links.component.html',
    styleUrls: ['./nav-links.component.less'],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class NavLinksComponent implements OnInit {
    @Input() links: Link[] = [];

    private currentPath: string;

    constructor(private router: Router,
                private route: ActivatedRoute,
                private cdr: ChangeDetectorRef) { }

    ngOnInit() {
        this.router.events.subscribe(() => {
            this.updateActiveRoute();
        });
    }

    private updateActiveRoute() {
        this.currentPath = this.route.snapshot.children[0].routeConfig.path;

        this.links = [...this.links].map(link => {
            link.active = link.url === this.currentPath;
            return link;
        });

        // Why is this required if I am already using the spread operator?
        this.cdr.detectChanges();
    }
}

【问题讨论】:

    标签: angular


    【解决方案1】:

    只要发生浏览器事件(或 Angular 事件),就会发生更改检测。在这种情况下,变化检测正在发生。但是,问题在于来自父组件的原始引用实际上并没有改变(从子组件的角度来看它确实改变了)。

    换句话说,通过覆盖组件内的@Input() 参数,您实际上破坏了父组件与子组件的输入参数之间的绑定。

    根据更改检测的工作方式,从上到下检查引用,当引用似乎没有更改(从父组件的角度)时,组件没有更新也就不足为奇了。

    为了保持绑定同步,请使用EventEmitter 设置双向绑定:

    export class NavLinksComponent implements OnInit {
        @Input() links: Link[] = [];
        @Output() linksChange: EventEmitter<Link[]>;
    
        constructor() {
            this.linksChange = new EventEmitter<Link[]>();
        }
    
        ngOnInit() {
           this.router.events.subscribe(() => {
               this.updateActiveRoute();
    
           });
        }
        private updateActiveRoute() {
            this.currentPath = this.route.snapshot.children[0].routeConfig.path;
    
            this.links = [...this.links].map(link => {
                link.active = link.url === this.currentPath;
                return link;
            });
            // notify the parent component that the reference has changed
            this.linksChange.next(this.links);
        }
     }
    

    在你调用组件的模板中,设置双向绑定,这样当内部引用被修改时,它会通知父组件也更新它的引用:

    <app-nav-links [(links)]="links" />
    

    这样,当自上而下检查引用时,更改检测器将确定引用已更改,并为其组件正确触发更改检测(对于使用 OnPush 策略的组件应该如此)。

    这对于默认的更改检测器来说不是问题,因为默认情况下,更改检测器会检查所有绑定的引用,而不管 @Input 引用是否已更改。

    【讨论】:

    • 谢谢你的详细解释,现在我终于明白它是如何工作的了。只是一个问题,使用事件发射器VS在ChangeDetectorRef上调用detectChanges是好还是坏?
    • detectChanges() 也有效。但这通常意味着缺乏理解,并且可能导致两个模型不同步的意外副作用。处理这个问题的正确方法是通过双向绑定。
    【解决方案2】:

    问题在于对嵌套对象的更改检测,或者更确切地说是缺少它。最简单的解决方案之一是在设置链接和路径之前JSON.parse(JSON.stringify()) 您的对象:

        this.currentPath = JSON.parse(JSON.stringify(this.route.snapshot.children[0].routeConfig.path));
    
        this.links = JSON.parse(JSON.stringify([...this.links].map(link => {
            link.active = link.url === this.currentPath;
            return link;
        })));
    

    这是您的对象的顶级更改,并且会自动触发更改。另一个选项是使用ChangeDetectorRef 手动处理,它会执行完整的对象扫描并注意到更改(如您已实现的那样)。

    基本思路: 嵌套对象不在更改检测器的范围内,因此只会注意到整个对象的更改(而不是仅更改嵌套部分时)。

    【讨论】:

      猜你喜欢
      • 2021-05-14
      • 2021-08-23
      • 1970-01-01
      • 1970-01-01
      • 2015-09-26
      • 2023-03-24
      • 1970-01-01
      • 2014-10-19
      • 2018-12-21
      相关资源
      最近更新 更多