1

I'm learning the basics of angular with a simple todo app. I have a simple spring boot backend which works fine. I'm currently struggling the best way to pass the date from a angular bootstrap datepicker to the backend. As the code is now the datatypes do not match.

Is the ideal way to convert it to seconds and convert the seconds back to a java date on the backend?

My ToDo entity:

@Id
    @GeneratedValue
    private long id;
    private @NonNull
    String taskName;
    private Date dueDate;
    private String extraNote;
    private boolean taskCompleted;

Where I get the user input when he creates a new todo:

@Input() toDoData = { taskName: '', taskCompleted: false, extraNote: '', dueDate: Date};

  addToDo() {
    this.todoService.addToDo(this.toDoData).subscribe((result) => {
      this.todoService.addToDo(this.toDoData);
    });
  }

Add todo part of my todoService:

addToDo(todo): Observable<any> {
        console.log(todo);
        return this.http.post<any>(this.API + 'todos', JSON.stringify(todo), this.httpOptions).pipe(
            tap((todo) => console.log(`added todo w/ id=${todo.id}`)),
            catchError(this.handleError<any>('addTodo'))
        );
    }

Thanks for any help!

EDIT (Added ToDoController):

@RestController
public class ToDoController {
    private ToDoRepository repository;

    public ToDoController(ToDoRepository repository) {
        this.repository = repository;
    }

    @GetMapping("/todos")
    List<ToDo> all() {
        return repository.findAll();
    }

    @PostMapping("/todos")
    ToDo newToDo(@RequestBody ToDo newToDo) {
        return repository.save(newToDo);
    }

    @GetMapping("/todos/{id}")
    ToDo one(@PathVariable Long id) {
        return repository.findById(id)
                .orElseThrow(() -> new ToDoNotFoundException(id));
    }

    @PutMapping("/todos/{id}")
    ToDo replaceToDo(@RequestBody ToDo newToDo, @PathVariable Long id) {

        return repository.findById(id)
                .map(toDo -> {
                    toDo.setTaskName(newToDo.getTaskName());
                    toDo.setDueDate(newToDo.getDueDate());
                    toDo.setExtraNote(newToDo.getExtraNote());
                    toDo.setTaskCompleted(newToDo.getTaskCompleted());
                    return repository.save(toDo);
                })
                .orElseGet(() -> {
                    newToDo.setId(id);
                    return repository.save(newToDo);
                });
    }

    @DeleteMapping("/todos/{id}")
    void deleteToDo(@PathVariable Long id) {
        repository.deleteById(id);
    }

    @GetMapping("/deleteall")
    @CrossOrigin(origins = "http://localhost:4200")
    public void deleteAll() {
        repository.deleteAll();
    }

    @GetMapping("/init")
    @CrossOrigin(origins = "http://localhots:4200")
    public void createDefaults() {
        Date date = new Date();
        repository.save(new ToDo("PMB", date, false));
        repository.save(new ToDo("GMDU", date, false));
        repository.save(new ToDo("INMA", date, true));
        repository.save(new ToDo("SLGP", date, false));
    }
}
2
  • Can you share the rest controller signature from your spring boot application with the endpoint todos? It would depend greatly on your requirements with the dueDate field to determine what is 'ideal'. Commented Dec 20, 2018 at 14:26
  • Thanks for answering Mr. Wong, I've added the spring controller, is this what you wanted? Thanks Commented Dec 20, 2018 at 14:30

3 Answers 3

2

First of all. When using dates you should take into account summer/winter time issues and therefore I would suggest to use a LocalDate(Time) class.

That said: I would create a

@Configuration
public class JacksonConfig {

    @Bean
    @Primary
    public ObjectMapper serializingObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();

        // Mirror the default Spring Boot Jackson settings
        objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateSerializer());
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateDeserializer());
        objectMapper.registerModule(javaTimeModule);
        return objectMapper;
    }

    public static class LocalDateSerializer extends JsonSerializer<LocalDateTime> {
        @Override
        public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            gen.writeNumber(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        }
    }
    public static class LocalDateDeserializer extends JsonDeserializer<LocalDateTime> {
        @Override
        public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());
        }
    }
}

that converts all dates/times etc to milliseconds and from milliseconds back to localDate(Time). You can find plenty examples on the internet.

This acts as a filter for all dates/times that pass the endpoints in your application. That way you do not need to concern yourself with any conversion issues anymore.

Then you will need to implement a dateToMillisecond and visa versa routine in Angular and use it where you need to convert te dates. You could intercept http traffic and do the same, but that might be a bit more complicated to implement.

Sample

function timeFromMillis(millis) {
    if (_.isEmpty(millis)) {
        return undefined;
    }
    const momentTimeStamp = moment.unix(millis / 1000);
    if (!momentTimeStamp.isValid()) {
        return undefined;
    }
    return momentTimeStamp.toDate();
}

function timeToMillis(obj) {
    if (!_.isEmpty(obj)) {
        if (obj instanceof moment) {
            return obj.valueOf();
        } else if (obj instanceof Date) {
            return obj.getTime();
           else if (angular.isString(obj)) {
            return parseDateString(obj).getTime();
        } else {
           return angular.undefined;
        }
    }
}

function parseDateString(dateString) {
    if (angular.isDefined(dateString) && dateString) {
        return moment(dateString, 'YYYY-MM-DD').toDate();
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for your help, where would I cast the date using timeToMillis(obj) as this doesnt work? <input class="form-control" placeholder="yyyy-mm-dd" name="dp" [(ngModel)]="convertToMilliseconds(toDoData.dueDate)" ngbDatepicker #d="ngbDatepicker">
And I would convert the date not in your input control, but yust before sending it to the back-end. I usually have a transformer service that transforms data that comes from and goes to the back-end and makes sure it is the required format for both ends.
something like: function save(toDoData) { toDoData.dueDate = convertToMilliseconds(toDoData.dueDate); backend.save(toDoData); } the above example is not actual working code, but explains my idea.
0

you have 2 options using timeStamp as long pasing long from angular to beckend and from back to angular This is the way i prefer because timestamp is unique Or you can use custom serializer deserializer

public class CustomDateTimeSerializer extends StdSerializer<DateTime> {

private static DateTimeFormatter formatter =
        DateTimeFormat.forPattern("yyyy-MM-dd HH:mm");

public CustomDateTimeSerializer() {
    this(null);
}

protected CustomDateTimeSerializer(Class<DateTime> t) {
    super(t);
}

@Override
public void serialize(DateTime value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
    gen.writeString(formatter.print(value));
}


public class CustomDateTimeDeserializer extends JsonDeserializer<DateTime> {

@Override
public DateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
    DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm");
    DateTime dateTime = formatter.parseDateTime(jsonParser.getText());
    return dateTime;
}
}

ps: i have used yoda time in serializer and deserializer

Comments

0

I ran into this and it turned out to be timezone issue. From frontend it was sending json with "2019-04-01" but backend was converting it to "2019-03-31"

A lot of the code is not using java.time so I found adding below to application.properties file to be best solution. I also added global date format, but change timezone to what you need.

spring.jackson.date-format=yyyy-MM-dd
spring.jackson.time-zone=America/Chicago

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.