除非我使用changeDetection onpush调用detectChanges(),否则HTML模板不会更新

时间:2018-05-02 23:34:56

标签: angular

我有一个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();
    }
}

2 个答案:

答案 0 :(得分:3)

每当发生浏览器事件(或角度事件)时都会发生更改检测。在这种情况下,正在进行更改检测。但是,问题是来自父组件的原始引用实际上没有改变(它确实从子组件的角度改变了)。

换句话说,通过覆盖组件中的@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引用是否已更改,更改检测器都将检查所有绑定引用。

答案 1 :(得分:0)

问题是嵌套对象的更改检测,或者更确切地说是缺少它。在设置链接和路径之前,最简单的解决方案之一是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手动执行,它执行完整的对象扫描并注意到更改(正如您已实现的那样)。

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