We also started with simple checks like oldValue != newValue and newValue != undefined, but at some point we didn't want all those similar checks to be implemented in every $watch handler.
Independent to the $watch handlers, we also had code spread across our app, which had to be run in a particular order (at least during initialization), due to functional constraints. Our app is cut into several modules, which we try to be as encapsulated as possible, so that we needed some way to synchronize shared state by using shared models. Some change events (say: $watch triggers or $on events) could trigger expensive backend calls, so we focused on issuing them only when the user triggers them. The init service helps us to collect resolved values and to pass them back to our controllers, without triggering new change events. Just as you said, we use promises and resolves, but we extracted the logic of ordering runtime dependencies on resolved data into a central place without moving controller specific code into a common service.
As mentioned in the article, we also suggest thinking about a dedicated init phase, because it minimizes confusion in our code and especially in our tests. We need some more tests or different test setup, but by explicitly testing "not initialized" and "initialized" application state, we could reduce some code parts checking for issues like oldValue != newValue in $watch handlers. By conceptually splitting both phases, we could focus on relevant checks according to each phase. The readability of our code has improved, too.
I wouldn't say that we only avoided the first $watch call, but we also changed our mind implementing application logic. Nevertheless, I wouldn't recommend a dedicated initialization phase, when the app was too small. Splitting phases can make code more complex, but I guess our app was big enough for such a concept. I didn't have any chance to compare our app to other ones, regarding size or complexity. Just as a rough hint: our app has about 1.7K statements, according to our coverage tool (karma-coverage/istanbul), spread across 25 modules.
There are similar discussions about the topic with slightly different motivation, e.g. see the
pull request about factories returning promises. I guess there's some need to improve AngularJS' handling of "asynchronous initialization", but most requirements only overlap, so it's hard to find a common solution for everyone.
What is your experience with a more complex module structure or bigger applications? Did you have similar problems and addressed them just by checks in your $watch handlers or did you add some more logic?