简体   繁体   中英

How to trigger an angular change from a non-angular context e.g. from a D3 event?

I'm using AngularJS 1.6.x and build a table using ng-repeat as shown below. However, now I need to show a new column depending on some dynamic boolean condition ie isDynamicVisible :

<table>
    <thead>
    <tr>
        <th id="name">Name</th>
        <th id="mean">Mean</th>
        <th ng-if="isDynamicVisible">Dynamic</th>
    </tr>
    </thead>
    <tbody>
    <tr ng-repeat="data in displayedPathStatistics" ng-class="{selected: (histogramData.selected === data.name)}"
        ng-click="selectPathOutputRow(data.name)">
        <td>{{data.displayName}}</td>
        <td>{{data.Mean}}</td>
        <td ng-if="isDynamicVisible">{{dynamicVal}}</td>
    </tr>
    </tbody>
</table>

On the controller side:

constructor(private $window: IWindowService, private $rootScope: IRootScopeService, private $scope:IReportCtrlScope, private $location:ng.ILocationService, private remoteServices: RemoteServices) {
    $scope.isDynamicVisible = false;

    // ...
    objects.selectAll(".dot")
    .data(data)
    .enter().append("circle")
    .classed("dot", true)
    .attr("r", function (d) {
            return 6 * Math.sqrt(2.0 / Math.PI);
    })
    .attr("transform", transform)
    .style("fill", colorVal)
    .on("mouseover", function(d) {
        $scope.isDynamicVisible = true;
        return tip.show(d);
    })
    .on("mouseout", function(d) {
        $scope.isDynamicVisible = false;
        return tip.hide(d);
    });

The problem is that the condition is evaluated only once at the beginning and upon constructing the table, later no matter that the isDynamicVisible scope variable changes it will stay how it initially was. I have also tried using ng-show without success.

UPDATE: the isDynamicVisible is changed from the Controller specifically when the user hovers over a data point of a D3 JS scatter plot.

You issue is caused by the way change detection works in angularjs. When you bind your code to something else than an angular event, angular is not aware that you updated some properties, and does not trigger his $digest cycle, which updates views

you need to use the directive ngMouseOver inside your template :

<div ng-mouseover="isDynamicVisible = true" ng-mouseout="isDynamicVisible = false">
   some html element
</div>

If, this div is out of your controller (which should not happen) you should use

.on("mouseout", function(d) {
    $timeout(function () {
        $scope.isDynamicVisible = false;
    }
    return tip.hide(d);
});

If your listeners are fired from a non-angular context (ie from D3), you have to wrap your $scope attributes modifications under a $scope.$apply call:

.on("mouseover", function(d) {
    $scope.$apply(function () {
        $scope.isDynamicVisible = true;
    }
    return tip.show(d);
})
.on("mouseout", function(d) {
    $scope.$apply(function () {
        $scope.isDynamicVisible = false;
    }
    return tip.hide(d);
})

Hence, the Angular digest cycle will be started and your view will update itself.

You can try to use $timeout . It'll guarantee, that you'll have at least one another digest loop, so you'll not be forsed to use $apply . From the user point of view, there will be no difference.

.on("mouseover", function(d) {
    $timeout(function() {
      $scope.isDynamicVisible = true;
      return tip.show(d);
    })
})

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM