3

This is part of my form:

<form name="form.activityForm" class="form-horizontal" ng-submit="save(form.activityForm.$valid)"
          enctype="multipart/form-data" novalidate>
    <fieldset>
        <div class="form-group" ng-if="!activity._id">
            <label for="gpxData" ng-class="{ 'disabled': gpxFileName }" class="btn btn-primary btn-file">Upload GPX
                <input type="file" nv-file-select name="gpxData" id="gpxData" ng-model="gpxData" uploader="uploader"
                           onchange="angular.element(this).scope().setFileName()" hidden>
            </label>
            <span ng-if="gpxFileName" ng-bind="gpxFileName"></span>
        </div>

        <div class="form-group" show-errors>
            <label class="control-label" for="name">Name</label>
            <input name="name" type="text" ng-model="activity.name" id="name" class="form-control"
                       ng-disabled="!gpxFileName" required>
            <div role="alert" class="alert alert-danger" ng-if="(activity.name === 'N/A') && (gpxFileName)">
                <p>This activity does not have a <strong>name</strong>. Please select one.</p>
            </div>
            <div ng-messages="form.activityForm.name.$error" role="alert">
                <p class="help-block error-text" ng-message="required">Activity name is required.</p>
            </div>
        </div>

        ...
    </fieldset>
</form>

setFileName()

$scope.setFileName = () => {
    let fr = new FileReader(),
        gpxFile = document.getElementById("gpxData").files[0],
        filename = gpxFile.name;

    fr.onload = () => {
        let gpxDataXml = parseXmlFromString(fr.result);

        setNameTypeDescription(gpxDataXml.documentElement);
    };
    fr.readAsText(gpxFile);
    $scope.gpxFileName = filename;
};

setNameTypeDescription()

let setNameTypeDescription = (gpxDataXmlDocumentElement) => {
    let activityType = getSingleTagData(gpxDataXmlDocumentElement, "type");

    $scope.activity.name = getSingleTagData(gpxDataXmlDocumentElement, "name");
    $scope.activity.description = getSingleTagData(gpxDataXmlDocumentElement, "descr");

    $scope.activityTypes.forEach((type) => {
        if (type.name.toLowerCase() === activityType.toLowerCase()) {
            $scope.activity.type = type;
        }
    });
};

When I log out the values (i.e. $scope.activity.name) after they have been changed in the controller, I get the expected output. However, the values (i.e. ng-model="activity.name") in the template do not update until there is focus again on the page (i.e. clicking a button)

EDIT:

To add, I need to use ng-model as I need 2 way binding. Even though the value is originally set int the controller, the user can also change the value in the template and, on form submit, that value gets saved into mongo.

Also, ng-bind is not working in this scenario.

wmash
  • 4,032
  • 3
  • 31
  • 69
  • try ng-model-options doc can be found here https://docs.angularjs.org/api/ng/directive/ngModelOptions – MMK Feb 06 '17 at 11:20
  • I am not sure what attributes to set using `ng-model-options` and I have read the docs – wmash Feb 06 '17 at 11:31
  • Try injecting `$timeout` to the controller, and change `$scope.activity.name = getSingleTagData(gpxDataXmlDocumentElement, "name");` to `$timeout(function() { $scope.activity.name = getSingleTagData(gpxDataXmlDocumentElement, "name"); });` – Alon Eitan Feb 10 '17 at 19:35
  • You are using `nv-file-select` directive but I dont see any logic inside your controller.. I recomand to learn how to use angular file upload https://github.com/nervgh/angular-file-upload/wiki/Module-API – Merlin Feb 11 '17 at 09:12
  • @AlonEitan this has worked. Post as an answer and I will accept – wmash Feb 11 '17 at 12:03

2 Answers2

4

Your problem is that you're updating the values that are bound to the view but angular doesn't "know" that they were updates. Angular updates the view on each digest cycle, which happens a lot of times - When the user interacts with the view, or after any change on the scope that angular is able to track.

In your case you call the setNameTypeDescription after the file changes (Using the onchange event on the file input. Since onchange is not a native angular directive, you must let angular know that something was changed, manually.

It worked when focused again on the page because it triggered a digest cycle that updated the view, but you can do it from the controller like this:

$timeout(function() { 
    $scope.activity.name = getSingleTagData(gpxDataXmlDocumentElement, "name"); 

    // You can also put here other view related changes, you don't need a separate $timeout for every change 
});

Of course - Don't forget to inject $timeout to the controller!

Note that you can also wrap the code with $scope.$apply(function() { ... });, but $timeout already does it for you, and I prefer $timeout because you won't get errors like $digest already in progress when calling $scope.$apply() as explained in this question.

Community
  • 1
  • 1
Alon Eitan
  • 11,997
  • 8
  • 49
  • 58
0

Try using ng-bind instead of ng-model

  • That is only 1 way binding. I need 2 way binding – wmash Feb 06 '17 at 11:12
  • I have also tried to use the `ng-bind` directive and doesn't work *(even on page refocus)* – wmash Feb 06 '17 at 11:19
  • Set the value in controller by using the id of input instead of $scope – shiva prasad patil Feb 06 '17 at 11:23
  • This is working for the `name` and `description`. However, both alert boxes display even though the ``'s now have values in them. Also, the ` – wmash Feb 06 '17 at 11:44
  • Add a dummy input field with opacity 0 and focus it after you change the values in controller In controller angular.element('#dummy).focus(); – shiva prasad patil Feb 06 '17 at 12:39
  • Not working. It's a very weird error. Values seem to update when I focus in on the select box and then lose focus on it – wmash Feb 06 '17 at 12:50