javascriptangularjsangularjs-scopeangularjs-watch

How can I update a parentScope array when it changes in the childScope in Angular?


I have a main controller with a nested child controller

<div class='main' ng-controller='mainController'>
    <div class='search' ng-controller='searchController'>
        <input type='text' ng-model='keyword'>
        <button ng-click='search(keyword)'>Search!</button
    </div>
</div>

In my main controller, I attempt to create a main-scoped variable to store the results of a search.

var app = angular.module('mapApp', [])
    .controller('mainController', [
        '$scope', 
        '$searchFactory',
        ($scope, searchFactory)=>{

            $scope.results = [];

            $scope.$watchCollection('results', (newVal, oldVal)=>{
                console.log('results updated to, ', newVal);
            }, true);

        }]);

Then I try to update the main controller's $scope.results variable in my child controller.

 app.controller('searchController', ['$scope', 'searchFactory', ($scope, searchFactory)=>{
        $scope.search = function(keyword, type){
            let request = {
                location: $scope.location,
                radius: '500',
                type: type ? type : undefined,
                keyword: keyword
            };
            searchFactory.search(request, (result)=>{
                console.log('result from search was', result);
                $scope.results = result;
            });
        };
    }]);

My $watch function doesn't get invoked, which I believe is because the $scope.results is not being updated. I've tried using the three type of $watch functions, as illustrated by the Angular Docs under the $scope.watch depths section. I read this article on switching to prototype inheritance style for scopes but I'm not sure it's the best way to solve my problem, because I'm not sure I'm going down the right path. Perhaps I should be using event emitters/broadcasters instead to achieve my desired effect?

How can I update a results variable in searchController's parent scope (mainController) so that I can then have it accessible by siblings of searchController?

EDIT/SOLVED So, this is really weird, and if you can explain why this is the case, gold star for you.

Here is the mainController when the code doesn't work:

.controller('mainController', [
        '$scope', 
        'searchFactory', 
        ($scope,searchFactory)=>{
            $scope.searchData = {results:[]};

            $scope.setResults = function(newResults){
                $scope.searchData.results = newResults;
                console.log('new results is', $scope.searchData.results);
            };

            //this function doesn't invoke except on initiation
            $scope.$watch('searchData.results', (newVal, oldVal)=>{
                console.log('results updated to, ', newVal);
            }, true);

        }]);

Here is the mainController when the code does work:

.controller('mainController', [
        '$scope', 
        'searchFactory', 
        ($scope,searchFactory)=>{
            $scope.searchData = {results:[]};

            //comment out the evil console log and... it works??!!
            $scope.setResults = function(newResults){
                $scope.searchData.results = newResults;
                //console.log('new results is', $scope.searchData.results);
            };

            //this invokes now, because black magic? 
            $scope.$watch('searchData.results', (newVal, oldVal)=>{
                console.log('results updated to, ', newVal);
            }, true);

        }]);

Why...


Solution

  • Using $scope.results = result; sets a new results property on the child scope which shadows the results property of the parent.

    To fix this problem, you can use an object to hold the results property:

    $scope.searchData = {results: []};
    
    $scope.$watchCollection('searchData.results', (newVal, oldVal)=>{
        console.log('results updated to, ', newVal);
    }, true);
    

    and then in your child controller set only this property:

    $scope.searchData.results = result;