0

I have an AngularJS app, and on one of the pages, I have a number of widgets, each one displaying some information about the status of a part of the system. I am currently working on adding the functionality to allow the user to 'hide' the heading of a given widget.

There is a 'Settings' button on the page where the widgets are displayed, which, when clicked, overlays a toolbar on top of each of the widgets. The toolbar has a number of buttons- one of which is another 'Settings' button, which opens up a dialog that allows the user to change the settings for that particular widget.

I have added a checkbox to the dialog, to enable the user to 'hide' the heading for that particular widget from view:

Configure Item dialog

When the checkbox is selected on the dialog, and the user clicks 'Preview', I am expecting (eventually- I'm still working on the implementation of the feature) the heading for that particular widget to be hidden. However, currently, when the user clicks 'Preview', whether the checkbox is selected or not, I am getting an error in the console that says:

TypeError: $scope.widget.toggleWidgetHeading is not a function

This error is coming from the $scope.preview function in ctrl.js called when the 'Preview' button is clicked on the dialog:

}).controller('WidgetPickerCtrl', function($scope, $timeout, fxTag, gPresets, appWidget) {
    ...
    $scope.preview = function(e) {
        $scope.widget.toggleWidgetHeading();
        ...
    };
    ...
});

I don't understand why I'm getting this console error, since toggleWidgetHeading() clearly is a function...

If I right-click on the function call above in Sublime, and select 'Go to definition', I am taken to the directive.js file where the function is defined:

.directive('appWidget', function($timeout, fxTag, appColorFilter) {
    return {
        ...
        link: function($scope, $element){
            ...
            var toggleWidgetHeading = function(){
                ...
            }
            ...
        }
    }
})

Also, clicking the 'Preview' button on the dialog no longer closes the dialog...

Why is it that I'm being told that this function call is not a function when it is clearly defined as one? Is the issue here something to do with the scope (i.e. the fact that I'm calling the function from ctrl.js, even though it's defined in directive.js)?

Noble-Surfer
  • 3,052
  • 11
  • 73
  • 118
  • What is `$scope.widget` and how does it relate to `appWidget` directive? Defining function inside the directive in a way you do doesn't guarantee it is somehow attached to the `$scope.widget` object. – Stanislav Kvitash Aug 21 '17 at 10:01
  • You can try using [events (or some other approaches) to call the directive methods from the parent controller](https://stackoverflow.com/questions/14883476/call-method-in-directive-controller-from-other-controller). – Stanislav Kvitash Aug 21 '17 at 10:08

1 Answers1

1

The definition of your directive, where you added ..., is actually a really relevant part about directives scopes.

Directives can implement several kind of scopes. You can actually inherit and access the parent scope, or you can have an isolated scope for example. You may read about that in the official documentation where is well explained:

https://docs.angularjs.org/guide/directive

However, whatever will be the scope you use, by default AngularJS implements the inheritance of scopes, as usually inheritance works: children can access parent methods, but parent cannot access children's methods.

Here it seems that from the parent scope (the controller) you are trying to access the directive's scope, which is actually no possible. (even if in the link function you define the toggleWidgetHeading as private variable, and not associated to the $scope itself - but it won't work either).

So you have few options in these cases:

  1. Define your "visible properties" inside a service and inject the service inside the directive and the controller. Then use the service in order to access and change these values, so that they will be sync between the controller and the directive
  2. Add a scope parameter to the directive as callback & and provide a function from the controller which returns the chosen visibility of the widget, so from the directive you can call that function and get back the value of widget's visibility
  3. Add a scope parameter as two way data binding = in the directive, which is bound to the widget's visibility of the controller, so that you have that always sync between your controller and your directive
  4. Use events in order to communicate between the controller and the directive, broadcasting an event from the controller when the visibility changes, and reading the event from the directive getting the widget's visibility value

I hope it makes sense

quirimmo
  • 9,800
  • 3
  • 30
  • 45