Antonio Antonio - 5 months ago 178
AngularJS Question

Angular/Ionic: how can I refresh a directive when I update variables in the controller?

I have 'Question' controller which contains a set of directives in the HTML for types of inputs:

<my-text ng-attr-ref="{{ref}}" ng-if="type=='text'"></my-text>
<my-integer ng-attr-ref="{{ref}}" ng-if="type=='integer'"></my-integer>
<my-decimal ng-attr-ref="{{ref}}" ng-if="type=='decimal'"></my-decimal>


etc

To navigate between each question, I just update the 'ref' to the next question 'ref' and then update the type of the question etc, and angular displays the corresponding type of input, based on the directives above, and hides the previous input.

I use a link function in my directives for each of the above, to set up the question text and bind to the answer for each question.

angular.module('question.directives')
/**
* Text directive
*/
.directive('myText', function ($compile, PARAMETERS) {
return {
restrict: 'E',
templateUrl: PARAMETERS.COMPONENTS_PATH + 'question/inputs/text/my-text.html',
scope: true,
link: function (scope, element, attrs) {
// Get closest parent scope
var parent = scope.$parent;
// Question input details
var inputDetails = parent.inputs[attrs.ref].data;
scope.question = inputDetails.question;
// Set the answer for this question (based on answers stored in parent scope)
scope.answer = parent.answers[attrs.ref];
}
};
});


This works fine, until I navigate from one text input, for example, to another text input, and the directive is not updated and so I see the same question twice in a row.
Navigating from one type to another type works fine.

This was in an attempt to reduce the flickering of dynamic variables, as I was previously using $state.go() to go to the same controller, reloading the view, with the updated question details.
This way, I reuse the same html and am only updating the directives I need to, rather than creating the whole view again.

Has anyone else had this problem with Ionic variables flickering in views, or can anyone suggest a way I can force the controller to update the directive?
I've set
cache-view="false"
already in the view.

Here's a simple plunker. As you'll notice, when I click 'next' from question 3 to question 4, I see question 3 twice, because I'm using the my-text directive twice in a row. Similarly, clicking 'previous' from question 5, shows me question 4 twice in a row.

Thanks!

EDIT:
So what I ended up doing was adding an object
params
to the controller scope and adding
currentIndex
as a property to it. When I watched that via
scope.$watch('params.currentIndex', update);
in the link function, it correctly called the update function which refreshed the params in my directive.

I've updated the plunker to reflect the changes I made.

Answer

Okay so you're using the link function to update the displayed value, but the link function is only called once at compile time. So on first page load your directive is compiled and link is called. Then you move to the next question which has a different type, which causes the ng-if to trigger. Now ng-if removes the first directive from the dom and adds the second one to the dom, which now calls the link method.

When you keep your first directive though, it won't call the link function because you reuse the directive.

I would suggest just using standard angular bindings and not the link function because it will just auto update for you.

If you need more help with that I will add example code.

EDIT:

angular
    .module('question.directives')
    .directive('myText', MyTextDirective);

function MyTextDirective($compile, PARAMETERS) {
    return {
        restrict: 'E',
        scope: {
            ref: '&'
        },
        templateUrl: PARAMETERS.COMPONENTS_PATH + 'question/inputs/text/my-text.html',
        controller: MyTextController
    };
}

function MyTextController($scope) {
    $scope.$watch(() => this.ref, update);

    function update() {
        $scope.question = $scope.$parent.inputs[this.ref].question;
        $scope.answer   = $scope.$parent.answers[this.ref];
    }
}

What this does is it registers a directive with the attribute 'ref' and a controller. The controller registers a watcher for the attribute 'ref' and calls update() every time the value of 'ref' changes.

update() just does the same as you did in your link function, just with different formatting.

Now this isn't super efficient but it should work. Depending on your angular version and the surrounding code you could implement a component which gets the question and the answer instead of a reference, which would remove the need for a watcher.

You could also expose a function which gets called when the next question should be displayed and then remove the watcher.