factory không trả về đúng giá trị


  • Angel

    Mình có 1 factory như sau:

    app.factory("getApiUrl", function (httpq) {
        var obj = {};
        obj.getResponse = function (hurl) {
            return httpq.get(hurl).then(function (response) {
                obj = response.data;
                return obj;
            });
        }
        obj.getOneSong = function (index) {
            return obj.data[index];
        }
        return obj;
    });
    

    Sau đó mình xây dựng ui khi click vào item trong list bài hát thì sẽ tới trang detail của bài hát:
    Hiện tại mình cho thử click vào index thôi:

    <a ng-href="/#/detail/{{$index}}"><img src="images/ico-heart.jpg"></a>
    

    Trong detail.js mình viết hàm để lấy thông tin bài hát:

    app.controller("DetailCtr", function ($scope, $stateParams, ngAudio,  getApiUrl, getLyric) {
        var test = $stateParams.id;
        $scope.song = getApiUrl.getOneSong(test);
    });
    

    Tuy nhiên mình gặp 1 vấn đề là sau khi mình xuất thành công list object ra giao diện, mình click vào item trong list items lần đầu tiên thì obj trong factory trả về undefine nên khi đổ sang trang detail lại không có giá trị.
    Mình click quay lại trang list object và click lại lần 2, thì obj mới có danh sách và mình get đc thông tin để đổ vào trang detail.



  • Vấn đề nằm ở chỗ lần đầu bạn truy cập, ajax chưa được thực thi xong, nên biến obj đang bị undefined, đoạn code:

    obj.getOneSong = function (index) {
            return obj.data[index];
        }
    

    sẽ trả về undefined là đúng rồi.

    Để fix bug này? Trước tiên hãy fix bug trong TƯ DUY của bạn đã nhé:

    • Ajax là một thao tác async, nên không thể nào dùng return để lấy kết quả của nó. Nên cuộc tình dù đúng hay sai bạn LUÔN LUÔN cần một callback function để handle dữ liệu trả về =)

    • Promise là một đại diện cho một KẾT QUẢ ASYNC, chứ không phải là QUÁ TRÌNH thực thi async. Trong trường hợp của bạn, nếu là mình mình sẽ viết như sau:

    app.factory("getApiUrl", function (httpq) {
        var obj = {};
        obj.getOneSong = function (songId) {
            var hurl = 'build URL with given ' + songId;
            return httpq.get(hurl);
        };
        return obj;
    });
    

    That's it!

    Khi đó, api của bạn sẽ thành:

    $scope.loadingSong = true;
    $scope.song = null;
    api.getOneSong('baby-one-more-time').then(function (song) {
        $scope.song = song;
    }).finally(function() {
        $scope.loadingSong = false;
    });
    

    Bạn có thể disable các input có thể tương tác với bài hát, show loading icon, cho đến khi cờ $scope.loadingSong === false

    Bạn nên tìm hiểu thêm bài viết này, của ad Mikky, viết hay và nguy hiểm lắm ;))



  • @Rik-Ky đã nói:
    Cảm ơn bạn, nhưng bạn cho mình bết tại sao lần 2 khi click vào thì obj không bị vậy nữa bạn. Với lại mình thấy trong nhiều example trên mạng thì họ click vào lần thứ nhất hay lần 2 đều ok. Có khi nào họ xài 1 cách code factory nào không bạn?



  • Vì lần click thứ 2 bạn, ajax đã được thực thi xong, nên bạn lấy được dữ liệu. Nếu ai đó mà hướng dẫn bạn làm như trên, cứ giơ thẳng ngón giữa ra và nói thật to "Fuck you"!


  • Angel

    à, tại mình làm theo ví dụ này, cái này của ionic:
    http://mcgivery.com/ionic-using-factories-and-web-services-for-dynamic-data/
    Mình thấy nó cũng gần tương tự và đều sử dụng angularjs nên làm theo thôi.


  • Angel

    @rikky à, bạn ơi, với cái list object, có một số thông tin có thể sử dụng lại trong detail, vậy có cách nào dùng factory để lấy thông tin đó và truyền vào cho detail item ko bạn? Thay vì phải lấy thông tin từ $http về.


  • Angel

    @Rik-Ky đã nói:

    $scope.loadingSong = true;
    $scope.song = null;
    api.getOneSong('baby-one-more-time').then(function (song) {
    $scope.song = song;
    }).finally(function() {
    $scope.loadingSong = false;
    });

    mình làm thử cách của bạn, nhưng vẫn bị một chỗ $scope.song, hàm đã trả ra giá trị, nhưng cái scope van khong truyền cho giao diện dù mình click bao nhiêu lần đi nữa, hình như cái scope không tự thay đổi khi nó nằm trong 1 hàm?

    getApiUrl.getOneSong(test).then(function (song) {
    song = song.data.data[test - 0];
    $scope.song = song;

    }).finally(function () {
        $scope.loadingSong = false;
    });

  • Angel

    Mình đã tìm ra nguyên nhân, tuy nhiên mình vẫn không hiểu là tại sao hàm

    api.getOneSong('baby-one-more-time').then(function (song) {
        $scope.song = song;
    }).finally(function() {
        $scope.loadingSong = false;
    });
    

    lại được run cuối cùng, dẫn đến việc mình có dùng một cái $scope.audio = ngAudio.load($scope.song.file_name); (----scope này được bỏ dưới cùng controller nhưng vẫn chạy đầu tiên----) cho nên khi hàm getOneSong chưa gọi thì cái scope.audio không load được, dẫn đến việc không load lên được toàn bộ scope.
    ------P/s.cho mình một lời giải thích hoặc cách khắc phục điều này nha.


  • Angel

    @Chí-Nguyễn-Thiện Như @Rik-Ky có nói rồi đó bạn, thằng js nó chạy ko đồng bộ mà vì thế TH của bạn ko phải được run cuối cùng mà cái promise của nó trả về lâu hơn thôi.


Log in to reply