This error occurs for you because you are making a change that invalidates the previously rendered component view.
Quote from this documentation
Angular's unidirectional data flow rule forbids updates to the view after it has been composed. Both of these hooks fire after the component's view has been composed.
What this means for your situation:
When Angular first renders/composes the view of this component (prior ngAfterViewInit
) the width of the element is set to an initial value. This code changes the width of an element, which causes a change to the view.
I think this example using background color makes this more obvious. In the first pass rendering the view, the color is red
(and you can briefly see this on the screen). In your situation, the width binding is undefined on the first pass through.
Then, the ngAfterViewInit
causes a change that makes the previously created view invalid, changing the width or the color, triggering the error. I translate this error as Angular saying "I did a bunch of work and made a view that was perfect, then you did something that made that work worthless. You shouldn't do that, because it interrupts some performance optimizations/assumptions I have".
This can be fixed by ensuring the component change happens after the ngAfterViewInit
method has finished running, by using setTimeout
. Fixed example Or by ensuring the component change happens before the view is rendered by moving it to ngOnInit
.
You may notice that I do not have detectChanges
in the examples I created. This is intentional, as that is a red herring and actually has no relation to the problem (though you correctly state this is necessary for the template binding to work). When the host binding is commented out in your example, there is no expression problem because this.width
has no impact on the rendered view of the component.
There is no issue when the binding is in the template because this is causing changes to the content of the component - not the component's own view.
Speculation / Things I don't fully understand:
I believe this behavior boils down to changes to the Shadow DOM vs Light DOM (I'm making guesses based on info from this SO question). At the time ngAfterViewInit
is running, I believe there is already an empty tag <my-component></my-component>
in the Light DOM (IE actually on the page). Changing the child content does not cause an issue, because at this point it doesn't actually exist on the page and is part of the Shadow DOM. However, changing a host binding triggers a change to the real elements on the page (part of the Light DOM) - hence the error.